Migrate hotseat items into a folder
If feature flag HOTSEAT_MIGRATE_TO_FOLDER is enabled, this moves hotseat
items into a folder in the workspace. If not, it moves the whole hotseat
to the first workspace page that can host the hotseat.
Bug: 151099421
Test: Manual
Change-Id: I49f6a22a0ada2c4cf237ca91a323a46346a11a59
diff --git a/quickstep/recents_ui_overrides/res/layout/arrow_toast.xml b/quickstep/recents_ui_overrides/res/layout/arrow_toast.xml
index b0f2b4b..980bb5a 100644
--- a/quickstep/recents_ui_overrides/res/layout/arrow_toast.xml
+++ b/quickstep/recents_ui_overrides/res/layout/arrow_toast.xml
@@ -34,6 +34,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
+ android:gravity="center"
android:layout_gravity="center_vertical"
android:textColor="@android:color/white"
android:textSize="16sp"/>
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/ArrowTipView.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/ArrowTipView.java
new file mode 100644
index 0000000..a5ea523
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/ArrowTipView.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2008 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;
+
+import android.content.Context;
+import android.graphics.CornerPathEffect;
+import android.graphics.Paint;
+import android.graphics.drawable.ShapeDrawable;
+import android.os.Handler;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.core.content.ContextCompat;
+
+import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.graphics.TriangleShape;
+
+/**
+ * A base class for arrow tip view in launcher
+ */
+public class ArrowTipView extends AbstractFloatingView {
+
+ private static final long AUTO_CLOSE_TIMEOUT_MILLIS = 10 * 1000;
+ private static final long SHOW_DELAY_MS = 200;
+ private static final long SHOW_DURATION_MS = 300;
+ private static final long HIDE_DURATION_MS = 100;
+
+ protected final Launcher mLauncher;
+ private final Handler mHandler = new Handler();
+ private Runnable mOnClosed;
+
+ public ArrowTipView(Context context) {
+ super(context, null, 0);
+ mLauncher = Launcher.getLauncher(context);
+ init(context);
+ }
+
+ @Override
+ public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+ if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+ close(true);
+ }
+ return false;
+ }
+
+ @Override
+ protected void handleClose(boolean animate) {
+ if (mIsOpen) {
+ if (animate) {
+ animate().alpha(0f)
+ .withLayer()
+ .setStartDelay(0)
+ .setDuration(HIDE_DURATION_MS)
+ .setInterpolator(Interpolators.ACCEL)
+ .withEndAction(() -> mLauncher.getDragLayer().removeView(this))
+ .start();
+ } else {
+ animate().cancel();
+ mLauncher.getDragLayer().removeView(this);
+ }
+ if (mOnClosed != null) mOnClosed.run();
+ mIsOpen = false;
+ }
+ }
+
+ @Override
+ public void logActionCommand(int command) {
+ }
+
+ @Override
+ protected boolean isOfType(int type) {
+ return (type & TYPE_ON_BOARD_POPUP) != 0;
+ }
+
+ private void init(Context context) {
+ inflate(context, R.layout.arrow_toast, this);
+ setOrientation(LinearLayout.VERTICAL);
+ View dismissButton = findViewById(R.id.dismiss);
+ dismissButton.setOnClickListener(view -> {
+ handleClose(true);
+ });
+
+ View arrowView = findViewById(R.id.arrow);
+ ViewGroup.LayoutParams arrowLp = arrowView.getLayoutParams();
+ ShapeDrawable arrowDrawable = new ShapeDrawable(TriangleShape.create(
+ arrowLp.width, arrowLp.height, false));
+ Paint arrowPaint = arrowDrawable.getPaint();
+ TypedValue typedValue = new TypedValue();
+ context.getTheme().resolveAttribute(android.R.attr.colorAccent, typedValue, true);
+ arrowPaint.setColor(ContextCompat.getColor(getContext(), typedValue.resourceId));
+ // The corner path effect won't be reflected in the shadow, but shouldn't be noticeable.
+ arrowPaint.setPathEffect(new CornerPathEffect(
+ context.getResources().getDimension(R.dimen.arrow_toast_corner_radius)));
+ arrowView.setBackground(arrowDrawable);
+
+ mIsOpen = true;
+
+ mHandler.postDelayed(() -> handleClose(true), AUTO_CLOSE_TIMEOUT_MILLIS);
+ }
+
+ /**
+ * Show Tip with specified string and Y location
+ */
+ public ArrowTipView show(String text, int top) {
+ ((TextView) findViewById(R.id.text)).setText(text);
+ mLauncher.getDragLayer().addView(this);
+
+ DragLayer.LayoutParams params = (DragLayer.LayoutParams) getLayoutParams();
+ params.gravity = Gravity.CENTER_HORIZONTAL;
+ params.leftMargin = mLauncher.getDeviceProfile().workspacePadding.left;
+ params.rightMargin = mLauncher.getDeviceProfile().workspacePadding.right;
+ post(() -> setY(top - getHeight()));
+ setAlpha(0);
+ animate()
+ .alpha(1f)
+ .withLayer()
+ .setStartDelay(SHOW_DELAY_MS)
+ .setDuration(SHOW_DURATION_MS)
+ .setInterpolator(Interpolators.DEACCEL)
+ .start();
+ return this;
+ }
+
+ /**
+ * Register a callback fired when toast is hidden
+ */
+ public ArrowTipView setOnClosedCallback(Runnable runnable) {
+ mOnClosed = runnable;
+ return this;
+ }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AllAppsTipView.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AllAppsTipView.java
index 0ae7435..b3bb850 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AllAppsTipView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AllAppsTipView.java
@@ -16,133 +16,30 @@
package com.android.launcher3.appprediction;
+import static com.android.launcher3.AbstractFloatingView.TYPE_DISCOVERY_BOUNCE;
+import static com.android.launcher3.AbstractFloatingView.TYPE_ON_BOARD_POPUP;
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.quickstep.logging.UserEventDispatcherExtension.ALL_APPS_PREDICTION_TIPS;
-import android.content.Context;
-import android.graphics.CornerPathEffect;
-import android.graphics.Paint;
-import android.graphics.drawable.ShapeDrawable;
-import android.os.Handler;
import android.os.UserManager;
-import android.util.AttributeSet;
-import android.util.TypedValue;
-import android.view.Gravity;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import androidx.core.content.ContextCompat;
import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.ArrowTipView;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.LauncherStateManager;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.FloatingHeaderView;
-import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.dragndrop.DragLayer;
-import com.android.launcher3.graphics.TriangleShape;
import com.android.systemui.shared.system.LauncherEventUtil;
/**
- * All apps tip view aligned just above prediction apps, shown to users that enter all apps for the
+ * ArrowTip helper aligned just above prediction apps, shown to users that enter all apps for the
* first time.
*/
-public class AllAppsTipView extends AbstractFloatingView {
+public class AllAppsTipView {
private static final String ALL_APPS_TIP_SEEN = "launcher.all_apps_tip_seen";
- private static final long AUTO_CLOSE_TIMEOUT_MILLIS = 10 * 1000;
- private static final long SHOW_DELAY_MS = 200;
- private static final long SHOW_DURATION_MS = 300;
- private static final long HIDE_DURATION_MS = 100;
-
- private final Launcher mLauncher;
- private final Handler mHandler = new Handler();
-
- private AllAppsTipView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- private AllAppsTipView(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- setOrientation(LinearLayout.VERTICAL);
-
- mLauncher = Launcher.getLauncher(context);
-
- init(context);
- }
-
- @Override
- public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
- if (ev.getAction() == MotionEvent.ACTION_DOWN) {
- close(true);
- }
- return false;
- }
-
- @Override
- protected void handleClose(boolean animate) {
- if (mIsOpen) {
- if (animate) {
- animate().alpha(0f)
- .withLayer()
- .setStartDelay(0)
- .setDuration(HIDE_DURATION_MS)
- .setInterpolator(Interpolators.ACCEL)
- .withEndAction(() -> mLauncher.getDragLayer().removeView(this))
- .start();
- } else {
- animate().cancel();
- mLauncher.getDragLayer().removeView(this);
- }
- mLauncher.getSharedPrefs().edit().putBoolean(ALL_APPS_TIP_SEEN, true).apply();
- mIsOpen = false;
- }
- }
-
- @Override
- public void logActionCommand(int command) {
- }
-
- @Override
- protected boolean isOfType(int type) {
- return (type & TYPE_ON_BOARD_POPUP) != 0;
- }
-
- private void init(Context context) {
- inflate(context, R.layout.arrow_toast, this);
-
- TextView textView = findViewById(R.id.text);
- textView.setText(R.string.all_apps_prediction_tip);
-
- View dismissButton = findViewById(R.id.dismiss);
- dismissButton.setOnClickListener(view -> {
- mLauncher.getUserEventDispatcher().logActionTip(
- LauncherEventUtil.DISMISS, ALL_APPS_PREDICTION_TIPS);
- handleClose(true);
- });
-
- View arrowView = findViewById(R.id.arrow);
- ViewGroup.LayoutParams arrowLp = arrowView.getLayoutParams();
- ShapeDrawable arrowDrawable = new ShapeDrawable(TriangleShape.create(
- arrowLp.width, arrowLp.height, false));
- Paint arrowPaint = arrowDrawable.getPaint();
- TypedValue typedValue = new TypedValue();
- context.getTheme().resolveAttribute(android.R.attr.colorAccent, typedValue, true);
- arrowPaint.setColor(ContextCompat.getColor(getContext(), typedValue.resourceId));
- // The corner path effect won't be reflected in the shadow, but shouldn't be noticeable.
- arrowPaint.setPathEffect(new CornerPathEffect(
- context.getResources().getDimension(R.dimen.arrow_toast_corner_radius)));
- arrowView.setBackground(arrowDrawable);
-
- mIsOpen = true;
-
- mHandler.postDelayed(() -> handleClose(true), AUTO_CLOSE_TIMEOUT_MILLIS);
- }
private static boolean showAllAppsTipIfNecessary(Launcher launcher) {
FloatingHeaderView floatingHeaderView = launcher.getAppsView().getFloatingHeaderView();
@@ -156,28 +53,15 @@
return false;
}
- AllAppsTipView allAppsTipView = new AllAppsTipView(launcher.getAppsView().getContext(),
- null);
- launcher.getDragLayer().addView(allAppsTipView);
+ int[] coords = new int[2];
+ floatingHeaderView.findFixedRowByType(PredictionRowView.class).getLocationOnScreen(coords);
+ ArrowTipView arrowTipView = new ArrowTipView(launcher).setOnClosedCallback(() -> {
+ launcher.getSharedPrefs().edit().putBoolean(ALL_APPS_TIP_SEEN, true).apply();
+ launcher.getUserEventDispatcher().logActionTip(LauncherEventUtil.DISMISS,
+ ALL_APPS_PREDICTION_TIPS);
+ });
+ arrowTipView.show(launcher.getString(R.string.all_apps_prediction_tip), coords[1]);
- DragLayer.LayoutParams params = (DragLayer.LayoutParams) allAppsTipView.getLayoutParams();
- params.gravity = Gravity.CENTER_HORIZONTAL;
-
- int top = floatingHeaderView.findFixedRowByType(PredictionRowView.class).getTop();
- allAppsTipView.setY(top - launcher.getResources().getDimensionPixelSize(
- R.dimen.all_apps_tip_bottom_margin));
-
- allAppsTipView.setAlpha(0);
- allAppsTipView.animate()
- .alpha(1f)
- .withLayer()
- .setStartDelay(SHOW_DELAY_MS)
- .setDuration(SHOW_DURATION_MS)
- .setInterpolator(Interpolators.DEACCEL)
- .start();
-
- launcher.getUserEventDispatcher().logActionTip(
- LauncherEventUtil.VISIBLE, ALL_APPS_PREDICTION_TIPS);
return true;
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java
index a07cd1d..da58817 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java
@@ -23,23 +23,28 @@
import android.content.res.Configuration;
import android.os.Build;
import android.view.View;
-import android.view.ViewGroup;
import androidx.core.app.NotificationCompat;
import com.android.launcher3.CellLayout;
+import com.android.launcher3.FolderInfo;
+import com.android.launcher3.Hotseat;
+import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.R;
import com.android.launcher3.Workspace;
import com.android.launcher3.WorkspaceItemInfo;
-import com.android.launcher3.WorkspaceLayoutManager;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.uioverrides.QuickstepLauncher;
import com.android.launcher3.util.ActivityTracker;
+import com.android.launcher3.util.GridOccupancy;
+import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.Themes;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
import java.util.List;
/**
@@ -52,48 +57,179 @@
private static final int ONBOARDING_NOTIFICATION_ID = 7641;
private final Launcher mLauncher;
+ private final NotificationManager mNotificationManager;
+ private final Notification mNotification;
private List<WorkspaceItemInfo> mPredictedApps;
private HotseatEduDialog mActiveDialog;
- private final NotificationManager mNotificationManager;
- private final Notification mNotification;
+ private ArrayList<ItemInfo> mNewItems = new ArrayList<>();
+ private IntArray mNewScreens = null;
+ private Runnable mOnOnboardingComplete;
- HotseatEduController(Launcher launcher) {
+ HotseatEduController(Launcher launcher, Runnable runnable) {
mLauncher = launcher;
+ mOnOnboardingComplete = runnable;
mNotificationManager = mLauncher.getSystemService(NotificationManager.class);
createNotificationChannel();
mNotification = createNotification();
}
- boolean migrate() {
- Workspace workspace = mLauncher.getWorkspace();
- CellLayout firstScreen = workspace.getScreenWithId(WorkspaceLayoutManager.FIRST_SCREEN_ID);
- int toPage = Workspace.FIRST_SCREEN_ID;
- int toRow = mLauncher.getDeviceProfile().inv.numRows - 1;
- if (FeatureFlags.HOTSEAT_MIGRATE_NEW_PAGE.get()) {
- toPage = workspace.getScreenIdForPageIndex(workspace.getPageCount());
- toRow = 0;
- } else if (!firstScreen.makeSpaceForHotseatMigration(true)) {
- return false;
+ /**
+ * Checks what type of migration should be used and migrates hotseat
+ */
+ void migrate() {
+ if (FeatureFlags.HOTSEAT_MIGRATE_TO_FOLDER.get()) {
+ migrateToFolder();
+ } else {
+ migrateHotseatWhole();
}
- ViewGroup hotseatVG = mLauncher.getHotseat().getShortcutsAndWidgets();
- for (int i = 0; i < hotseatVG.getChildCount(); i++) {
- View child = hotseatVG.getChildAt(i);
+ }
+
+ /**
+ * This migration places all non folder items in the hotseat into a folder and then moves
+ * all folders in the hotseat to a workspace page that has enough empty spots.
+ *
+ * @return pageId that has accepted the items.
+ */
+ private int migrateToFolder() {
+ ArrayDeque<FolderInfo> folders = new ArrayDeque<>();
+
+ ArrayList<WorkspaceItemInfo> putIntoFolder = new ArrayList<>();
+
+ //separate folders and items that can get in folders
+ for (int i = 0; i < mLauncher.getDeviceProfile().inv.numHotseatIcons; i++) {
+ View view = mLauncher.getHotseat().getChildAt(i, 0);
+ if (view == null) continue;
+ ItemInfo info = (ItemInfo) view.getTag();
+ if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
+ folders.add((FolderInfo) info);
+ } else if (info instanceof WorkspaceItemInfo) {
+ putIntoFolder.add((WorkspaceItemInfo) info);
+ }
+ }
+
+ // create a temp folder and add non folder items to it
+ if (!putIntoFolder.isEmpty()) {
+ ItemInfo firstItem = putIntoFolder.get(0);
+ FolderInfo folderInfo = new FolderInfo();
+ folderInfo.title = "";
+ mLauncher.getModelWriter().addItemToDatabase(folderInfo, firstItem.container,
+ firstItem.screenId, firstItem.cellX, firstItem.cellY);
+ folderInfo.contents.addAll(putIntoFolder);
+ for (int i = 0; i < folderInfo.contents.size(); i++) {
+ ItemInfo item = folderInfo.contents.get(i);
+ item.rank = i;
+ mLauncher.getModelWriter().moveItemInDatabase(item, folderInfo.id, 0,
+ item.cellX, item.cellY);
+ }
+ folders.add(folderInfo);
+ }
+ mNewItems.addAll(folders);
+
+ return placeFoldersInWorkspace(folders);
+ }
+
+ private int placeFoldersInWorkspace(ArrayDeque<FolderInfo> folders) {
+ if (folders.isEmpty()) return 0;
+
+ Workspace workspace = mLauncher.getWorkspace();
+ InvariantDeviceProfile idp = mLauncher.getDeviceProfile().inv;
+
+ GridOccupancy[] occupancyList = new GridOccupancy[workspace.getChildCount()];
+ for (int i = 0; i < occupancyList.length; i++) {
+ occupancyList[i] = ((CellLayout) workspace.getChildAt(i)).cloneGridOccupancy();
+ }
+ //scan every screen to find available spots to place folders
+ int occupancyIndex = 0;
+ int[] itemXY = new int[2];
+ while (occupancyIndex < occupancyList.length && !folders.isEmpty()) {
+ GridOccupancy occupancy = occupancyList[occupancyIndex];
+ if (occupancy.findVacantCell(itemXY, 1, 1)) {
+ FolderInfo info = folders.poll();
+ mLauncher.getModelWriter().moveItemInDatabase(info,
+ LauncherSettings.Favorites.CONTAINER_DESKTOP,
+ workspace.getScreenIdForPageIndex(occupancyIndex), itemXY[0], itemXY[1]);
+ occupancy.markCells(info, true);
+ } else {
+ occupancyIndex++;
+ }
+ }
+ if (folders.isEmpty()) return workspace.getScreenIdForPageIndex(occupancyIndex);
+ int screenId = LauncherSettings.Settings.call(mLauncher.getContentResolver(),
+ LauncherSettings.Settings.METHOD_NEW_SCREEN_ID)
+ .getInt(LauncherSettings.Settings.EXTRA_VALUE);
+ // if all screens are full and we still have folders left, put those on a new page
+ FolderInfo folderInfo;
+ int col = 0;
+ while ((folderInfo = folders.poll()) != null) {
+ mLauncher.getModelWriter().moveItemInDatabase(folderInfo,
+ LauncherSettings.Favorites.CONTAINER_DESKTOP, screenId, col++,
+ idp.numRows - 1);
+ }
+ mNewScreens = IntArray.wrap(screenId);
+ return workspace.getPageCount();
+ }
+
+ /**
+ * This migration option attempts to move the entire hotseat up to the first workspace that
+ * has space to host items. If no such page is found, it moves items to a new page.
+ *
+ * @return pageId where items are migrated
+ */
+ private int migrateHotseatWhole() {
+ Workspace workspace = mLauncher.getWorkspace();
+ Hotseat hotseatVG = mLauncher.getHotseat();
+
+ int pageId = -1;
+ int toRow = 0;
+ for (int i = 0; i < workspace.getPageCount(); i++) {
+ CellLayout target = workspace.getScreenWithId(workspace.getScreenIdForPageIndex(i));
+ if (target.makeSpaceForHotseatMigration(true)) {
+ toRow = mLauncher.getDeviceProfile().inv.numRows - 1;
+ pageId = i;
+ break;
+ }
+ }
+ if (pageId == -1) {
+ pageId = LauncherSettings.Settings.call(mLauncher.getContentResolver(),
+ LauncherSettings.Settings.METHOD_NEW_SCREEN_ID)
+ .getInt(LauncherSettings.Settings.EXTRA_VALUE);
+ }
+ for (int i = 0; i < mLauncher.getDeviceProfile().inv.numHotseatIcons; i++) {
+ View child = hotseatVG.getChildAt(i, 0);
+ if (child == null || child.getTag() == null) continue;
ItemInfo tag = (ItemInfo) child.getTag();
mLauncher.getModelWriter().moveItemInDatabase(tag,
- LauncherSettings.Favorites.CONTAINER_DESKTOP, toPage, tag.screenId, toRow);
+ LauncherSettings.Favorites.CONTAINER_DESKTOP, pageId, i, toRow);
+ mNewItems.add(tag);
}
- return true;
+ return pageId;
}
+
void removeNotification() {
mNotificationManager.cancel(ONBOARDING_NOTIFICATION_ID);
}
void finishOnboarding() {
- mLauncher.getModel().rebindCallbacks();
+ mLauncher.getHotseat().removeAllViewsInLayout();
+ if (!mNewItems.isEmpty()) {
+ int lastPage = mNewItems.get(mNewItems.size() - 1).screenId;
+ ArrayList<ItemInfo> animated = new ArrayList<>();
+ ArrayList<ItemInfo> nonAnimated = new ArrayList<>();
+
+ for (ItemInfo info : mNewItems) {
+ if (info.screenId == lastPage) {
+ animated.add(info);
+ } else {
+ nonAnimated.add(info);
+ }
+ }
+ mLauncher.bindAppsAdded(mNewScreens, nonAnimated, animated);
+ }
+ mOnOnboardingComplete.run();
+ destroy();
mLauncher.getSharedPrefs().edit().putBoolean(KEY_HOTSEAT_EDU_SEEN, true).apply();
- removeNotification();
}
void setPredictedApps(List<WorkspaceItemInfo> predictedApps) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
index 7986c26..bcce168 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
@@ -29,6 +29,7 @@
import android.widget.TextView;
import android.widget.Toast;
+import com.android.launcher3.ArrowTipView;
import com.android.launcher3.CellLayout;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Insettable;
@@ -53,20 +54,15 @@
private static final int DEFAULT_CLOSE_DURATION = 200;
protected static final int FINAL_SCRIM_BG_COLOR = 0x88000000;
- private static final int MIGRATE_SAME_PAGE = 0;
- private static final int MIGRATE_NEW_PAGE = 1;
- private static final int MIGRATE_NO_MIGRATE = 2;
+ // we use this value to keep track of migration logs as we experiment with different migrations
+ private static final int MIGRATION_EXPERIMENT_IDENTIFIER = 1;
private final Rect mInsets = new Rect();
private View mHotseatWrapper;
private CellLayout mSampleHotseat;
- private TextView mEduHeading;
- private TextView mEduContent;
private Button mDismissBtn;
- private int mMigrationMode = MIGRATE_SAME_PAGE;
-
public void setHotseatEduController(HotseatEduController hotseatEduController) {
mHotseatEduController = hotseatEduController;
}
@@ -89,8 +85,6 @@
super.onFinishInflate();
mHotseatWrapper = findViewById(R.id.hotseat_wrapper);
mSampleHotseat = findViewById(R.id.sample_prediction);
- mEduHeading = findViewById(R.id.hotseat_edu_heading);
- mEduContent = findViewById(R.id.hotseat_edu_content);
DeviceProfile grid = mLauncher.getDeviceProfile();
Rect padding = grid.getHotseatLayoutPadding();
@@ -105,25 +99,30 @@
mDismissBtn = findViewById(R.id.no_thanks);
mDismissBtn.setOnClickListener(this::onDismiss);
+ // update ui to reflect which migration method is going to be used
+ if (FeatureFlags.HOTSEAT_MIGRATE_TO_FOLDER.get()) {
+ ((TextView) findViewById(R.id.hotseat_edu_content)).setText(
+ R.string.hotseat_edu_message_migrate_alt);
+ }
}
private void onAccept(View v) {
- if (mMigrationMode == MIGRATE_NO_MIGRATE || !mHotseatEduController.migrate()) {
- onDismiss(v);
- return;
- }
+ mHotseatEduController.migrate();
handleClose(true);
mHotseatEduController.finishOnboarding();
- logUserAction(true);
- int toastStringRes = mMigrationMode == MIGRATE_SAME_PAGE
+ //TODO: pass actual page index here.
+ // Temporarily we're passing 1 for folder migration and 2 for page migration
+ logUserAction(true, FeatureFlags.HOTSEAT_MIGRATE_TO_FOLDER.get() ? 1 : 2);
+ int toastStringRes = !FeatureFlags.HOTSEAT_MIGRATE_TO_FOLDER.get()
? R.string.hotseat_items_migrated : R.string.hotseat_items_migrated_alt;
Toast.makeText(mLauncher, toastStringRes, Toast.LENGTH_LONG).show();
}
private void onDismiss(View v) {
- Toast.makeText(getContext(), R.string.hotseat_no_migration, Toast.LENGTH_LONG).show();
+ int top = mLauncher.getHotseat().getTop();
+ new ArrowTipView(mLauncher).show(mLauncher.getString(R.string.hotseat_no_migration), top);
mHotseatEduController.finishOnboarding();
- logUserAction(false);
+ logUserAction(false, -1);
handleClose(true);
}
@@ -155,7 +154,7 @@
mLauncher.getDeviceProfile().hotseatBarSizePx + insets.bottom;
}
- private void logUserAction(boolean migrated) {
+ private void logUserAction(boolean migrated, int pageIndex) {
LauncherLogProto.Action action = new LauncherLogProto.Action();
LauncherLogProto.Target target = new LauncherLogProto.Target();
action.type = LauncherLogProto.Action.Type.TOUCH;
@@ -164,8 +163,9 @@
target.tipType = LauncherLogProto.TipType.HYBRID_HOTSEAT;
target.controlType = migrated ? LauncherLogProto.ControlType.HYBRID_HOTSEAT_ACCEPTED
: HYBRID_HOTSEAT_CANCELED;
+ target.rank = MIGRATION_EXPERIMENT_IDENTIFIER;
// encoding migration type on pageIndex
- target.pageIndex = mMigrationMode;
+ target.pageIndex = pageIndex;
LauncherLogProto.LauncherEvent event = newLauncherEvent(action, target);
UserEventDispatcher.newInstance(getContext()).dispatchUserEvent(event, null);
}
@@ -218,15 +218,6 @@
}
}
- @Override
- protected void attachToContainer() {
- super.attachToContainer();
- if (FeatureFlags.HOTSEAT_MIGRATE_NEW_PAGE.get()) {
- mEduContent.setText(R.string.hotseat_edu_message_migrate_alt);
- mMigrationMode = MIGRATE_NEW_PAGE;
- }
- }
-
/**
* Opens User education dialog with a list of suggested apps
*/
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
index 2cdcd20..d82e9f0 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
@@ -94,7 +94,6 @@
private static final String BUNDLE_KEY_WORKSPACE = "workspace_apps";
private static final String PREDICTION_CLIENT = "hotseat";
-
private DropTarget.DragObject mDragObject;
private int mHotSeatItemsCount;
private int mPredictedSpotsCount = 0;
@@ -115,6 +114,7 @@
private List<PredictedAppIcon.PredictedIconOutlineDrawing> mOutlineDrawings = new ArrayList<>();
+
private final View.OnLongClickListener mPredictionLongClickListener = v -> {
if (!ItemLongClickListener.canStartDrag(mLauncher)) return false;
if (mLauncher.getWorkspace().isSwitchingState()) return false;
@@ -276,12 +276,10 @@
.build());
mAppPredictor.registerPredictionUpdates(mLauncher.getMainExecutor(),
this::setPredictedApps);
+ setPauseUIUpdate(false);
if (!isReady()) {
- if (mHotseatEduController != null) {
- mHotseatEduController.destroy();
- }
- mHotseatEduController = new HotseatEduController(mLauncher);
+ mHotseatEduController = new HotseatEduController(mLauncher, this::createPredictor);
}
mAppPredictor.requestPredictionUpdate();
}
@@ -327,7 +325,7 @@
mComponentKeyMappers.add(new ComponentKeyMapper(key, mDynamicItemCache));
}
predictionLog.append("]");
- FileLog.d(TAG, predictionLog.toString());
+ if (false) FileLog.d(TAG, predictionLog.toString());
updateDependencies();
if (isReady()) {
fillGapsWithPrediction();
@@ -488,7 +486,6 @@
}
}
-
@Override
public void onDragEnd() {
if (mDragObject == null) {
@@ -564,7 +561,8 @@
}
@Override
- public void reapplyItemInfo(ItemInfoWithIcon info) {}
+ public void reapplyItemInfo(ItemInfoWithIcon info) {
+ }
@Override
public void onDropCompleted(View target, DropTarget.DragObject d, boolean success) {
diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java
index 12b5fc1..bed8278 100644
--- a/src/com/android/launcher3/AbstractFloatingView.java
+++ b/src/com/android/launcher3/AbstractFloatingView.java
@@ -180,7 +180,10 @@
return null;
}
- protected static <T extends AbstractFloatingView> T getOpenView(
+ /**
+ * Returns a view matching FloatingViewType
+ */
+ public static <T extends AbstractFloatingView> T getOpenView(
ActivityContext activity, @FloatingViewType int type) {
BaseDragLayer dragLayer = activity.getDragLayer();
if (dragLayer == null) return null;
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 4742bbc..f2d07f2 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -2766,7 +2766,6 @@
* Finds solution to accept hotseat migration to cell layout. commits solution if commitConfig
*/
public boolean makeSpaceForHotseatMigration(boolean commitConfig) {
- if (FeatureFlags.HOTSEAT_MIGRATE_NEW_PAGE.get()) return false;
int[] cellPoint = new int[2];
int[] directionVector = new int[]{0, -1};
cellToPoint(0, mCountY, cellPoint);
@@ -2776,12 +2775,23 @@
if (commitConfig) {
copySolutionToTempState(configuration, null);
commitTempPlacement();
+ // undo marking cells occupied since there is actually nothing being placed yet.
+ mOccupied.markCells(0, mCountY - 1, mCountX, 1, false);
}
return true;
}
return false;
}
+ /**
+ * returns a copy of cell layout's grid occupancy
+ */
+ public GridOccupancy cloneGridOccupancy() {
+ GridOccupancy occupancy = new GridOccupancy(mCountX, mCountY);
+ mOccupied.copyTo(occupancy);
+ return occupancy;
+ }
+
public boolean isRegionVacant(int x, int y, int spanX, int spanY) {
return mOccupied.isRegionVacant(x, y, spanX, spanY);
}
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 6292167..4df3b0a 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -120,12 +120,11 @@
"ASSISTANT_GIVES_LAUNCHER_FOCUS", false,
"Allow Launcher to handle nav bar gestures while Assistant is running over it");
- public static final BooleanFlag ENABLE_HYBRID_HOTSEAT = getDebugFlag(
+ public static final BooleanFlag ENABLE_HYBRID_HOTSEAT = new DeviceFlag(
"ENABLE_HYBRID_HOTSEAT", false, "Fill gaps in hotseat with predicted apps");
- public static final BooleanFlag HOTSEAT_MIGRATE_NEW_PAGE = getDebugFlag(
- "HOTSEAT_MIGRATE_NEW_PAGE", false,
- "Migrates hotseat to a new workspace page instead of same page");
+ public static final BooleanFlag HOTSEAT_MIGRATE_TO_FOLDER = new DeviceFlag(
+ "HOTSEAT_MIGRATE_TO_FOLDER", false, "Should move hotseat items into a folder");
public static final BooleanFlag ENABLE_DEEP_SHORTCUT_ICON_CACHE = getDebugFlag(
"ENABLE_DEEP_SHORTCUT_ICON_CACHE", true, "R/W deep shortcut in IconCache");