Adds LAUNCHER_FOLDER_LABEL_CHANGED event.
Sample Log: https://docs.google.com/document/d/1CBP2yTcXdFhPdNG5ZmWFKSgd8mDbMevY-akVlUXPLDo/edit#bookmark=id.qwjknn6acmx6 Bug: 155410872 Bug: 152978018 Change-Id: Ib7641d3d42a3f4fd002d1dbb36dc4b9ea0f885fc
This commit is contained in:
parent
e0fef877e4
commit
6524cc7237
|
@ -95,7 +95,17 @@ message Task {
|
||||||
|
|
||||||
// Represents folder in a closed state.
|
// Represents folder in a closed state.
|
||||||
message FolderIcon {
|
message FolderIcon {
|
||||||
|
// Number of items inside folder.
|
||||||
optional int32 cardinality = 1;
|
optional int32 cardinality = 1;
|
||||||
|
|
||||||
|
// State of the folder label before the event.
|
||||||
|
optional FromState from_state = 2;
|
||||||
|
|
||||||
|
// State of the folder label after the event.
|
||||||
|
optional ToState to_state = 3;
|
||||||
|
|
||||||
|
// Populated only when folder label was suggested.
|
||||||
|
optional string label = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
//////////////////////////////////////////////
|
//////////////////////////////////////////////
|
||||||
|
@ -120,3 +130,71 @@ message FolderContainer {
|
||||||
HotseatContainer hotseat = 5;
|
HotseatContainer hotseat = 5;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Represents state of FolderLabel before editing.
|
||||||
|
enum FromState {
|
||||||
|
// Default value.
|
||||||
|
FROM_STATE_UNSPECIFIED = 0;
|
||||||
|
|
||||||
|
// FolderLabel was empty.
|
||||||
|
FROM_EMPTY = 1;
|
||||||
|
|
||||||
|
// FolderLabel was non-empty and manually entered by the user.
|
||||||
|
FROM_CUSTOM = 2;
|
||||||
|
|
||||||
|
// FolderLabel was non-empty and one of the suggestions.
|
||||||
|
FROM_SUGGESTED = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Represents state of FolderLabel after editing.
|
||||||
|
enum ToState {
|
||||||
|
// Default value.
|
||||||
|
TO_STATE_UNSPECIFIED = 0;
|
||||||
|
// User attempted to change the folder label, but was not changed.
|
||||||
|
UNCHANGED = 1;
|
||||||
|
|
||||||
|
// New label matches with primary(aka top) suggestion.
|
||||||
|
TO_SUGGESTION0 = 2;
|
||||||
|
|
||||||
|
// New label matches with second top suggestion even though the top suggestion was non-empty.
|
||||||
|
TO_SUGGESTION1_WITH_VALID_PRIMARY = 3;
|
||||||
|
|
||||||
|
// New label matches with second top suggestion given that top suggestion was empty.
|
||||||
|
TO_SUGGESTION1_WITH_EMPTY_PRIMARY = 4;
|
||||||
|
|
||||||
|
// New label matches with third top suggestion even though the top suggestion was non-empty.
|
||||||
|
TO_SUGGESTION2_WITH_VALID_PRIMARY = 5;
|
||||||
|
|
||||||
|
// New label matches with third top suggestion given that top suggestion was empty.
|
||||||
|
TO_SUGGESTION2_WITH_EMPTY_PRIMARY = 6;
|
||||||
|
|
||||||
|
// New label matches with 4th top suggestion even though the top suggestion was non-empty.
|
||||||
|
TO_SUGGESTION3_WITH_VALID_PRIMARY = 7;
|
||||||
|
|
||||||
|
// New label matches with 4th top suggestion given that top suggestion was empty.
|
||||||
|
TO_SUGGESTION3_WITH_EMPTY_PRIMARY = 8;
|
||||||
|
|
||||||
|
// New label is empty even though the top suggestion was non-empty.
|
||||||
|
TO_EMPTY_WITH_VALID_PRIMARY = 9;
|
||||||
|
|
||||||
|
// New label is empty given that top suggestion was empty.
|
||||||
|
TO_EMPTY_WITH_VALID_SUGGESTIONS_AND_EMPTY_PRIMARY = 10;
|
||||||
|
|
||||||
|
// New label is empty given that no suggestions were provided.
|
||||||
|
TO_EMPTY_WITH_EMPTY_SUGGESTIONS = 11;
|
||||||
|
|
||||||
|
// New label is empty given that suggestions feature was disabled.
|
||||||
|
TO_EMPTY_WITH_SUGGESTIONS_DISABLED = 12;
|
||||||
|
|
||||||
|
// New label is non-empty and does not match with any of the suggestions even though the top suggestion was non-empty.
|
||||||
|
TO_CUSTOM_WITH_VALID_PRIMARY = 13;
|
||||||
|
|
||||||
|
// New label is non-empty and not match with any suggestions given that top suggestion was empty.
|
||||||
|
TO_CUSTOM_WITH_VALID_SUGGESTIONS_AND_EMPTY_PRIMARY = 14;
|
||||||
|
|
||||||
|
// New label is non-empty and also no suggestions were provided.
|
||||||
|
TO_CUSTOM_WITH_EMPTY_SUGGESTIONS = 15;
|
||||||
|
|
||||||
|
// New label is non-empty and also suggestions feature was disable.
|
||||||
|
TO_CUSTOM_WITH_SUGGESTIONS_DISABLED = 16;
|
||||||
|
}
|
||||||
|
|
|
@ -18,23 +18,15 @@ package com.android.launcher3.folder;
|
||||||
|
|
||||||
import static android.text.TextUtils.isEmpty;
|
import static android.text.TextUtils.isEmpty;
|
||||||
|
|
||||||
import static androidx.core.util.Preconditions.checkNotNull;
|
|
||||||
|
|
||||||
import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
|
import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
|
||||||
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
|
|
||||||
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
|
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
|
||||||
import static com.android.launcher3.LauncherState.NORMAL;
|
import static com.android.launcher3.LauncherState.NORMAL;
|
||||||
import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
|
import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
|
||||||
import static com.android.launcher3.config.FeatureFlags.ALWAYS_USE_HARDWARE_OPTIMIZATION_FOR_FOLDER_ANIMATIONS;
|
import static com.android.launcher3.config.FeatureFlags.ALWAYS_USE_HARDWARE_OPTIMIZATION_FOR_FOLDER_ANIMATIONS;
|
||||||
import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
|
import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
|
||||||
import static com.android.launcher3.model.data.FolderInfo.FLAG_MANUAL_FOLDER_NAME;
|
import static com.android.launcher3.model.data.FolderInfo.FLAG_MANUAL_FOLDER_NAME;
|
||||||
import static com.android.launcher3.userevent.LauncherLogProto.Target.FromFolderLabelState.FROM_CUSTOM;
|
|
||||||
import static com.android.launcher3.userevent.LauncherLogProto.Target.FromFolderLabelState.FROM_EMPTY;
|
|
||||||
import static com.android.launcher3.userevent.LauncherLogProto.Target.FromFolderLabelState.FROM_FOLDER_LABEL_STATE_UNSPECIFIED;
|
|
||||||
import static com.android.launcher3.userevent.LauncherLogProto.Target.FromFolderLabelState.FROM_SUGGESTED;
|
|
||||||
|
|
||||||
import static java.util.Arrays.asList;
|
import static java.util.Arrays.asList;
|
||||||
import static java.util.Arrays.stream;
|
|
||||||
import static java.util.Optional.ofNullable;
|
import static java.util.Optional.ofNullable;
|
||||||
|
|
||||||
import android.animation.Animator;
|
import android.animation.Animator;
|
||||||
|
@ -94,12 +86,6 @@ import com.android.launcher3.model.data.FolderInfo.FolderListener;
|
||||||
import com.android.launcher3.model.data.ItemInfo;
|
import com.android.launcher3.model.data.ItemInfo;
|
||||||
import com.android.launcher3.model.data.WorkspaceItemInfo;
|
import com.android.launcher3.model.data.WorkspaceItemInfo;
|
||||||
import com.android.launcher3.pageindicators.PageIndicatorDots;
|
import com.android.launcher3.pageindicators.PageIndicatorDots;
|
||||||
import com.android.launcher3.userevent.LauncherLogProto.Action;
|
|
||||||
import com.android.launcher3.userevent.LauncherLogProto.ContainerType;
|
|
||||||
import com.android.launcher3.userevent.LauncherLogProto.ItemType;
|
|
||||||
import com.android.launcher3.userevent.LauncherLogProto.LauncherEvent;
|
|
||||||
import com.android.launcher3.userevent.LauncherLogProto.Target;
|
|
||||||
import com.android.launcher3.userevent.LauncherLogProto.Target.ToFolderLabelState;
|
|
||||||
import com.android.launcher3.userevent.nano.LauncherLogProto;
|
import com.android.launcher3.userevent.nano.LauncherLogProto;
|
||||||
import com.android.launcher3.util.Executors;
|
import com.android.launcher3.util.Executors;
|
||||||
import com.android.launcher3.util.Thunk;
|
import com.android.launcher3.util.Thunk;
|
||||||
|
@ -111,10 +97,7 @@ import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.OptionalInt;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.IntStream;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a set of icons chosen by the user or generated by the system.
|
* Represents a set of icons chosen by the user or generated by the system.
|
||||||
|
@ -213,8 +196,6 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
|
||||||
@Thunk int mScrollHintDir = SCROLL_NONE;
|
@Thunk int mScrollHintDir = SCROLL_NONE;
|
||||||
@Thunk int mCurrentScrollDir = SCROLL_NONE;
|
@Thunk int mCurrentScrollDir = SCROLL_NONE;
|
||||||
|
|
||||||
private String mPreviousLabel;
|
|
||||||
private boolean mIsPreviousLabelSuggested;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to inflate the Workspace from XML.
|
* Used to inflate the Workspace from XML.
|
||||||
|
@ -348,9 +329,9 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "onBackKey newTitle=" + newTitle);
|
Log.d(TAG, "onBackKey newTitle=" + newTitle);
|
||||||
}
|
}
|
||||||
|
mInfo.previousTitle = mInfo.title;
|
||||||
mInfo.title = newTitle;
|
mInfo.title = newTitle;
|
||||||
mInfo.setOption(FLAG_MANUAL_FOLDER_NAME, !getAcceptedSuggestionIndex().isPresent(),
|
mInfo.setOption(FLAG_MANUAL_FOLDER_NAME, !mInfo.getAcceptedSuggestionIndex().isPresent(),
|
||||||
mLauncher.getModelWriter());
|
mLauncher.getModelWriter());
|
||||||
mFolderIcon.onTitleChanged(newTitle);
|
mFolderIcon.onTitleChanged(newTitle);
|
||||||
mLauncher.getModelWriter().updateItemInDatabase(mInfo);
|
mLauncher.getModelWriter().updateItemInDatabase(mInfo);
|
||||||
|
@ -441,8 +422,6 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
|
||||||
}
|
}
|
||||||
mItemsInvalidated = true;
|
mItemsInvalidated = true;
|
||||||
mInfo.addListener(this);
|
mInfo.addListener(this);
|
||||||
Optional.ofNullable(mInfo.title).ifPresent(title -> mPreviousLabel = title.toString());
|
|
||||||
mIsPreviousLabelSuggested = !mInfo.hasOption(FLAG_MANUAL_FOLDER_NAME);
|
|
||||||
|
|
||||||
if (!isEmpty(mInfo.title)) {
|
if (!isEmpty(mInfo.title)) {
|
||||||
mFolderName.setText(mInfo.title);
|
mFolderName.setText(mInfo.title);
|
||||||
|
@ -1455,7 +1434,6 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
|
||||||
if (hasFocus) {
|
if (hasFocus) {
|
||||||
startEditingFolderName();
|
startEditingFolderName();
|
||||||
} else {
|
} else {
|
||||||
logCurrentFolderLabelState();
|
|
||||||
mFolderName.dispatchBackKey();
|
mFolderName.dispatchBackKey();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1653,148 +1631,4 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
|
||||||
public FolderPagedView getContent() {
|
public FolderPagedView getContent() {
|
||||||
return mContent;
|
return mContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void logCurrentFolderLabelState() {
|
|
||||||
LauncherEvent launcherEvent = LauncherEvent.newBuilder()
|
|
||||||
.setAction(Action.newBuilder().setType(Action.Type.SOFT_KEYBOARD))
|
|
||||||
.addSrcTarget(newEditTextTargetBuilder()
|
|
||||||
.setFromFolderLabelState(getFromFolderLabelState())
|
|
||||||
.setToFolderLabelState(getToFolderLabelState()))
|
|
||||||
.addSrcTarget(newFolderTargetBuilder())
|
|
||||||
.addSrcTarget(newParentContainerTarget())
|
|
||||||
.build();
|
|
||||||
mLauncher.getUserEventDispatcher().logLauncherEvent(launcherEvent);
|
|
||||||
mPreviousLabel = mFolderName.getText().toString();
|
|
||||||
mIsPreviousLabelSuggested = !mInfo.hasOption(FLAG_MANUAL_FOLDER_NAME);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Target.FromFolderLabelState getFromFolderLabelState() {
|
|
||||||
return mPreviousLabel == null
|
|
||||||
? FROM_FOLDER_LABEL_STATE_UNSPECIFIED
|
|
||||||
: mPreviousLabel.isEmpty()
|
|
||||||
? FROM_EMPTY
|
|
||||||
: mIsPreviousLabelSuggested
|
|
||||||
? FROM_SUGGESTED
|
|
||||||
: FROM_CUSTOM;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Target.ToFolderLabelState getToFolderLabelState() {
|
|
||||||
String newLabel =
|
|
||||||
checkNotNull(mFolderName.getText().toString(),
|
|
||||||
"Expected valid folder label, but found null");
|
|
||||||
if (newLabel.equals(mPreviousLabel)) {
|
|
||||||
return Target.ToFolderLabelState.UNCHANGED;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
|
|
||||||
return newLabel.isEmpty()
|
|
||||||
? ToFolderLabelState.TO_EMPTY_WITH_SUGGESTIONS_DISABLED
|
|
||||||
: ToFolderLabelState.TO_CUSTOM_WITH_SUGGESTIONS_DISABLED;
|
|
||||||
}
|
|
||||||
|
|
||||||
Optional<String[]> suggestedLabels = getSuggestedLabels();
|
|
||||||
boolean isEmptySuggestions = suggestedLabels
|
|
||||||
.map(labels -> stream(labels).allMatch(TextUtils::isEmpty))
|
|
||||||
.orElse(true);
|
|
||||||
if (isEmptySuggestions) {
|
|
||||||
return newLabel.isEmpty()
|
|
||||||
? ToFolderLabelState.TO_EMPTY_WITH_EMPTY_SUGGESTIONS
|
|
||||||
: ToFolderLabelState.TO_CUSTOM_WITH_EMPTY_SUGGESTIONS;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean hasValidPrimary = suggestedLabels
|
|
||||||
.map(labels -> !isEmpty(labels[0]))
|
|
||||||
.orElse(false);
|
|
||||||
if (newLabel.isEmpty()) {
|
|
||||||
return hasValidPrimary ? ToFolderLabelState.TO_EMPTY_WITH_VALID_PRIMARY
|
|
||||||
: ToFolderLabelState.TO_EMPTY_WITH_VALID_SUGGESTIONS_AND_EMPTY_PRIMARY;
|
|
||||||
}
|
|
||||||
|
|
||||||
OptionalInt accepted_suggestion_index = getAcceptedSuggestionIndex();
|
|
||||||
if (!accepted_suggestion_index.isPresent()) {
|
|
||||||
return hasValidPrimary ? ToFolderLabelState.TO_CUSTOM_WITH_VALID_PRIMARY
|
|
||||||
: ToFolderLabelState.TO_CUSTOM_WITH_VALID_SUGGESTIONS_AND_EMPTY_PRIMARY;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (accepted_suggestion_index.getAsInt()) {
|
|
||||||
case 0:
|
|
||||||
return ToFolderLabelState.TO_SUGGESTION0_WITH_VALID_PRIMARY;
|
|
||||||
case 1:
|
|
||||||
return hasValidPrimary ? ToFolderLabelState.TO_SUGGESTION1_WITH_VALID_PRIMARY
|
|
||||||
: ToFolderLabelState.TO_SUGGESTION1_WITH_EMPTY_PRIMARY;
|
|
||||||
case 2:
|
|
||||||
return hasValidPrimary ? ToFolderLabelState.TO_SUGGESTION2_WITH_VALID_PRIMARY
|
|
||||||
: ToFolderLabelState.TO_SUGGESTION2_WITH_EMPTY_PRIMARY;
|
|
||||||
case 3:
|
|
||||||
return hasValidPrimary ? ToFolderLabelState.TO_SUGGESTION3_WITH_VALID_PRIMARY
|
|
||||||
: ToFolderLabelState.TO_SUGGESTION3_WITH_EMPTY_PRIMARY;
|
|
||||||
default:
|
|
||||||
// fall through
|
|
||||||
}
|
|
||||||
return ToFolderLabelState.TO_FOLDER_LABEL_STATE_UNSPECIFIED;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private Optional<String[]> getSuggestedLabels() {
|
|
||||||
return ofNullable(mInfo)
|
|
||||||
.map(info -> info.suggestedFolderNames)
|
|
||||||
.map(
|
|
||||||
folderNames ->
|
|
||||||
(FolderNameInfo[])
|
|
||||||
folderNames.getParcelableArrayExtra(FolderInfo.EXTRA_FOLDER_SUGGESTIONS))
|
|
||||||
.map(
|
|
||||||
folderNameInfoArray ->
|
|
||||||
stream(folderNameInfoArray)
|
|
||||||
.filter(Objects::nonNull)
|
|
||||||
.map(FolderNameInfo::getLabel)
|
|
||||||
.filter(Objects::nonNull)
|
|
||||||
.map(CharSequence::toString)
|
|
||||||
.toArray(String[]::new));
|
|
||||||
}
|
|
||||||
|
|
||||||
private OptionalInt getAcceptedSuggestionIndex() {
|
|
||||||
String newLabel = checkNotNull(mFolderName.getText().toString(),
|
|
||||||
"Expected valid folder label, but found null");
|
|
||||||
return getSuggestedLabels()
|
|
||||||
.map(suggestionsArray ->
|
|
||||||
IntStream.range(0, suggestionsArray.length)
|
|
||||||
.filter(
|
|
||||||
index -> !isEmpty(suggestionsArray[index])
|
|
||||||
&& newLabel.equalsIgnoreCase(suggestionsArray[index]))
|
|
||||||
.sequential()
|
|
||||||
.findFirst()
|
|
||||||
).orElse(OptionalInt.empty());
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private Target.Builder newEditTextTargetBuilder() {
|
|
||||||
return Target.newBuilder().setType(Target.Type.ITEM).setItemType(ItemType.EDITTEXT);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Target.Builder newFolderTargetBuilder() {
|
|
||||||
return Target.newBuilder()
|
|
||||||
.setType(Target.Type.CONTAINER)
|
|
||||||
.setContainerType(ContainerType.FOLDER)
|
|
||||||
.setPageIndex(mInfo.screenId)
|
|
||||||
.setGridX(mInfo.cellX)
|
|
||||||
.setGridY(mInfo.cellY)
|
|
||||||
.setCardinality(mInfo.contents.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
private Target.Builder newParentContainerTarget() {
|
|
||||||
Target.Builder builder = Target.newBuilder().setType(Target.Type.CONTAINER);
|
|
||||||
switch (mInfo.container) {
|
|
||||||
case CONTAINER_HOTSEAT:
|
|
||||||
return builder.setContainerType(ContainerType.HOTSEAT);
|
|
||||||
case CONTAINER_DESKTOP:
|
|
||||||
return builder.setContainerType(ContainerType.WORKSPACE);
|
|
||||||
default:
|
|
||||||
throw new AssertionError(String
|
|
||||||
.format("Expected container to be either %s or %s but found %s.",
|
|
||||||
CONTAINER_HOTSEAT,
|
|
||||||
CONTAINER_DESKTOP,
|
|
||||||
mInfo.container));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +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.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW;
|
||||||
import static com.android.launcher3.folder.PreviewItemManager.INITIAL_ITEM_ANIMATION_DURATION;
|
import static com.android.launcher3.folder.PreviewItemManager.INITIAL_ITEM_ANIMATION_DURATION;
|
||||||
|
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_LABEL_CHANGED;
|
||||||
|
|
||||||
import android.animation.Animator;
|
import android.animation.Animator;
|
||||||
import android.animation.AnimatorListenerAdapter;
|
import android.animation.AnimatorListenerAdapter;
|
||||||
|
@ -62,6 +63,8 @@ import com.android.launcher3.dragndrop.DragLayer;
|
||||||
import com.android.launcher3.dragndrop.DragView;
|
import com.android.launcher3.dragndrop.DragView;
|
||||||
import com.android.launcher3.dragndrop.DraggableView;
|
import com.android.launcher3.dragndrop.DraggableView;
|
||||||
import com.android.launcher3.icons.DotRenderer;
|
import com.android.launcher3.icons.DotRenderer;
|
||||||
|
import com.android.launcher3.logging.InstanceId;
|
||||||
|
import com.android.launcher3.logging.StatsLogManager;
|
||||||
import com.android.launcher3.model.data.AppInfo;
|
import com.android.launcher3.model.data.AppInfo;
|
||||||
import com.android.launcher3.model.data.FolderInfo;
|
import com.android.launcher3.model.data.FolderInfo;
|
||||||
import com.android.launcher3.model.data.FolderInfo.FolderListener;
|
import com.android.launcher3.model.data.FolderInfo.FolderListener;
|
||||||
|
@ -410,10 +413,10 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel
|
||||||
Executors.UI_HELPER_EXECUTOR.post(() -> {
|
Executors.UI_HELPER_EXECUTOR.post(() -> {
|
||||||
d.folderNameProvider.getSuggestedFolderName(
|
d.folderNameProvider.getSuggestedFolderName(
|
||||||
getContext(), mInfo.contents, nameInfos);
|
getContext(), mInfo.contents, nameInfos);
|
||||||
showFinalView(finalIndex, item, nameInfos);
|
showFinalView(finalIndex, item, nameInfos, d.logInstanceId);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
showFinalView(finalIndex, item, nameInfos);
|
showFinalView(finalIndex, item, nameInfos, d.logInstanceId);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
addItem(item);
|
addItem(item);
|
||||||
|
@ -421,12 +424,11 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showFinalView(int finalIndex, final WorkspaceItemInfo item,
|
private void showFinalView(int finalIndex, final WorkspaceItemInfo item,
|
||||||
FolderNameInfo[] nameInfos) {
|
FolderNameInfo[] nameInfos, InstanceId instanceId) {
|
||||||
postDelayed(() -> {
|
postDelayed(() -> {
|
||||||
mPreviewItemManager.hidePreviewItem(finalIndex, false);
|
mPreviewItemManager.hidePreviewItem(finalIndex, false);
|
||||||
mFolder.showItem(item);
|
mFolder.showItem(item);
|
||||||
setLabelSuggestion(nameInfos);
|
setLabelSuggestion(nameInfos, instanceId);
|
||||||
mFolder.logCurrentFolderLabelState();
|
|
||||||
invalidate();
|
invalidate();
|
||||||
}, DROP_IN_ANIMATION_DURATION);
|
}, DROP_IN_ANIMATION_DURATION);
|
||||||
}
|
}
|
||||||
|
@ -434,7 +436,7 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel
|
||||||
/**
|
/**
|
||||||
* Set the suggested folder name.
|
* Set the suggested folder name.
|
||||||
*/
|
*/
|
||||||
public void setLabelSuggestion(FolderNameInfo[] nameInfos) {
|
public void setLabelSuggestion(FolderNameInfo[] nameInfos, InstanceId instanceId) {
|
||||||
if (!FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
|
if (!FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -445,7 +447,10 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel
|
||||||
if (nameInfos == null || nameInfos[0] == null || isEmpty(nameInfos[0].getLabel())) {
|
if (nameInfos == null || nameInfos[0] == null || isEmpty(nameInfos[0].getLabel())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
mInfo.previousTitle = mInfo.title;
|
||||||
mInfo.title = nameInfos[0].getLabel();
|
mInfo.title = nameInfos[0].getLabel();
|
||||||
|
StatsLogManager.newInstance(getContext())
|
||||||
|
.log(LAUNCHER_FOLDER_LABEL_CHANGED, instanceId, mInfo.getFolderIconAtom());
|
||||||
onTitleChanged(mInfo.title);
|
onTitleChanged(mInfo.title);
|
||||||
mFolder.mFolderName.setText(mInfo.title);
|
mFolder.mFolderName.setText(mInfo.title);
|
||||||
mFolder.mLauncher.getModelWriter().updateItemInDatabase(mInfo);
|
mFolder.mLauncher.getModelWriter().updateItemInDatabase(mInfo);
|
||||||
|
|
|
@ -61,6 +61,10 @@ public class StatsLogManager implements ResourceBasedOverride {
|
||||||
+ "resulting in a new folder creation")
|
+ "resulting in a new folder creation")
|
||||||
LAUNCHER_ITEM_DROP_FOLDER_CREATED(386),
|
LAUNCHER_ITEM_DROP_FOLDER_CREATED(386),
|
||||||
|
|
||||||
|
@LauncherUiEvent(doc = "A dragged launcher item is successfully dropped on another item "
|
||||||
|
+ "resulting in new folder creation")
|
||||||
|
LAUNCHER_FOLDER_LABEL_CHANGED(460),
|
||||||
|
|
||||||
@LauncherUiEvent(doc = "A dragged item is dropped on 'Remove' button in the target bar")
|
@LauncherUiEvent(doc = "A dragged item is dropped on 'Remove' button in the target bar")
|
||||||
LAUNCHER_ITEM_DROPPED_ON_REMOVE(465),
|
LAUNCHER_ITEM_DROPPED_ON_REMOVE(465),
|
||||||
|
|
||||||
|
|
|
@ -16,16 +16,31 @@
|
||||||
|
|
||||||
package com.android.launcher3.model.data;
|
package com.android.launcher3.model.data;
|
||||||
|
|
||||||
|
import static android.text.TextUtils.isEmpty;
|
||||||
|
|
||||||
|
import static androidx.core.util.Preconditions.checkNotNull;
|
||||||
|
|
||||||
|
import static java.util.Arrays.stream;
|
||||||
|
import static java.util.Optional.ofNullable;
|
||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Process;
|
import android.os.Process;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
import com.android.launcher3.LauncherSettings;
|
import com.android.launcher3.LauncherSettings;
|
||||||
import com.android.launcher3.Utilities;
|
import com.android.launcher3.Utilities;
|
||||||
|
import com.android.launcher3.config.FeatureFlags;
|
||||||
|
import com.android.launcher3.folder.FolderNameInfo;
|
||||||
import com.android.launcher3.logger.LauncherAtom;
|
import com.android.launcher3.logger.LauncherAtom;
|
||||||
import com.android.launcher3.model.ModelWriter;
|
import com.android.launcher3.model.ModelWriter;
|
||||||
import com.android.launcher3.util.ContentWriter;
|
import com.android.launcher3.util.ContentWriter;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.OptionalInt;
|
||||||
|
import java.util.stream.IntStream;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a folder containing shortcuts or apps.
|
* Represents a folder containing shortcuts or apps.
|
||||||
|
@ -57,6 +72,10 @@ public class FolderInfo extends ItemInfo {
|
||||||
|
|
||||||
public Intent suggestedFolderNames;
|
public Intent suggestedFolderNames;
|
||||||
|
|
||||||
|
// When title changes, previous title is stored.
|
||||||
|
// Primarily used for logging purpose.
|
||||||
|
public CharSequence previousTitle;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The apps and shortcuts
|
* The apps and shortcuts
|
||||||
*/
|
*/
|
||||||
|
@ -172,4 +191,125 @@ public class FolderInfo extends ItemInfo {
|
||||||
folderInfo.contents = this.contents;
|
folderInfo.contents = this.contents;
|
||||||
return folderInfo;
|
return folderInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@link LauncherAtom.FolderIcon} wrapped as {@link LauncherAtom.ItemInfo} for logging
|
||||||
|
* into Westworld.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public LauncherAtom.ItemInfo getFolderIconAtom() {
|
||||||
|
LauncherAtom.ToState toFolderLabelState = getToFolderLabelState();
|
||||||
|
LauncherAtom.FolderIcon.Builder folderIconBuilder = LauncherAtom.FolderIcon.newBuilder()
|
||||||
|
.setCardinality(contents.size())
|
||||||
|
.setFromState(getFromFolderLabelState())
|
||||||
|
.setToState(toFolderLabelState);
|
||||||
|
if (toFolderLabelState.toString().startsWith("TO_SUGGESTION")) {
|
||||||
|
folderIconBuilder.setLabel(title.toString());
|
||||||
|
}
|
||||||
|
return getDefaultItemInfoBuilder()
|
||||||
|
.setFolderIcon(folderIconBuilder)
|
||||||
|
.setContainerInfo(getContainerInfo())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns index of the accepted suggestion.
|
||||||
|
*/
|
||||||
|
public OptionalInt getAcceptedSuggestionIndex() {
|
||||||
|
String newLabel = checkNotNull(title,
|
||||||
|
"Expected valid folder label, but found null").toString();
|
||||||
|
return getSuggestedLabels()
|
||||||
|
.map(suggestionsArray ->
|
||||||
|
IntStream.range(0, suggestionsArray.length)
|
||||||
|
.filter(
|
||||||
|
index -> !isEmpty(suggestionsArray[index])
|
||||||
|
&& newLabel.equalsIgnoreCase(
|
||||||
|
suggestionsArray[index]))
|
||||||
|
.sequential()
|
||||||
|
.findFirst()
|
||||||
|
).orElse(OptionalInt.empty());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private LauncherAtom.ToState getToFolderLabelState() {
|
||||||
|
if (title == null) {
|
||||||
|
return LauncherAtom.ToState.TO_STATE_UNSPECIFIED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (title.equals(previousTitle)) {
|
||||||
|
return LauncherAtom.ToState.UNCHANGED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
|
||||||
|
return title.length() > 0
|
||||||
|
? LauncherAtom.ToState.TO_CUSTOM_WITH_SUGGESTIONS_DISABLED
|
||||||
|
: LauncherAtom.ToState.TO_EMPTY_WITH_SUGGESTIONS_DISABLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
Optional<String[]> suggestedLabels = getSuggestedLabels();
|
||||||
|
boolean isEmptySuggestions = suggestedLabels
|
||||||
|
.map(labels -> stream(labels).allMatch(TextUtils::isEmpty))
|
||||||
|
.orElse(true);
|
||||||
|
if (isEmptySuggestions) {
|
||||||
|
return title.length() > 0
|
||||||
|
? LauncherAtom.ToState.TO_CUSTOM_WITH_EMPTY_SUGGESTIONS
|
||||||
|
: LauncherAtom.ToState.TO_EMPTY_WITH_EMPTY_SUGGESTIONS;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean hasValidPrimary = suggestedLabels
|
||||||
|
.map(labels -> !isEmpty(labels[0]))
|
||||||
|
.orElse(false);
|
||||||
|
if (title.length() == 0) {
|
||||||
|
return hasValidPrimary ? LauncherAtom.ToState.TO_EMPTY_WITH_VALID_PRIMARY
|
||||||
|
: LauncherAtom.ToState.TO_EMPTY_WITH_VALID_SUGGESTIONS_AND_EMPTY_PRIMARY;
|
||||||
|
}
|
||||||
|
|
||||||
|
OptionalInt accepted_suggestion_index = getAcceptedSuggestionIndex();
|
||||||
|
if (!accepted_suggestion_index.isPresent()) {
|
||||||
|
return hasValidPrimary ? LauncherAtom.ToState.TO_CUSTOM_WITH_VALID_PRIMARY
|
||||||
|
: LauncherAtom.ToState.TO_CUSTOM_WITH_VALID_SUGGESTIONS_AND_EMPTY_PRIMARY;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (accepted_suggestion_index.getAsInt()) {
|
||||||
|
case 0:
|
||||||
|
return LauncherAtom.ToState.TO_SUGGESTION0;
|
||||||
|
case 1:
|
||||||
|
return hasValidPrimary ? LauncherAtom.ToState.TO_SUGGESTION1_WITH_VALID_PRIMARY
|
||||||
|
: LauncherAtom.ToState.TO_SUGGESTION1_WITH_EMPTY_PRIMARY;
|
||||||
|
case 2:
|
||||||
|
return hasValidPrimary ? LauncherAtom.ToState.TO_SUGGESTION2_WITH_VALID_PRIMARY
|
||||||
|
: LauncherAtom.ToState.TO_SUGGESTION2_WITH_EMPTY_PRIMARY;
|
||||||
|
case 3:
|
||||||
|
return hasValidPrimary ? LauncherAtom.ToState.TO_SUGGESTION3_WITH_VALID_PRIMARY
|
||||||
|
: LauncherAtom.ToState.TO_SUGGESTION3_WITH_EMPTY_PRIMARY;
|
||||||
|
default:
|
||||||
|
// fall through
|
||||||
|
}
|
||||||
|
return LauncherAtom.ToState.TO_STATE_UNSPECIFIED;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private LauncherAtom.FromState getFromFolderLabelState() {
|
||||||
|
return previousTitle == null
|
||||||
|
? LauncherAtom.FromState.FROM_STATE_UNSPECIFIED
|
||||||
|
: previousTitle.toString().isEmpty()
|
||||||
|
? LauncherAtom.FromState.FROM_EMPTY
|
||||||
|
: hasOption(FLAG_MANUAL_FOLDER_NAME)
|
||||||
|
? LauncherAtom.FromState.FROM_CUSTOM
|
||||||
|
: LauncherAtom.FromState.FROM_SUGGESTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<String[]> getSuggestedLabels() {
|
||||||
|
return ofNullable(suggestedFolderNames)
|
||||||
|
.map(folderNames ->
|
||||||
|
(FolderNameInfo[])
|
||||||
|
folderNames.getParcelableArrayExtra(EXTRA_FOLDER_SUGGESTIONS))
|
||||||
|
.map(folderNameInfoArray ->
|
||||||
|
stream(folderNameInfoArray)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.map(FolderNameInfo::getLabel)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.map(CharSequence::toString)
|
||||||
|
.toArray(String[]::new));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue