From 82bbdaceae1d8ee0432567a96e4f5422a91a95cd Mon Sep 17 00:00:00 2001 From: Samuel Fufa Date: Mon, 9 Mar 2020 18:24:47 -0700 Subject: [PATCH] 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 --- .../res/layout/arrow_toast.xml | 1 + .../com/android/launcher3/ArrowTipView.java | 151 +++++++++++++++ .../appprediction/AllAppsTipView.java | 142 ++------------ .../hybridhotseat/HotseatEduController.java | 180 +++++++++++++++--- .../hybridhotseat/HotseatEduDialog.java | 47 ++--- .../HotseatPredictionController.java | 14 +- .../launcher3/AbstractFloatingView.java | 5 +- src/com/android/launcher3/CellLayout.java | 12 +- .../launcher3/config/FeatureFlags.java | 7 +- 9 files changed, 366 insertions(+), 193 deletions(-) create mode 100644 quickstep/recents_ui_overrides/src/com/android/launcher3/ArrowTipView.java diff --git a/quickstep/recents_ui_overrides/res/layout/arrow_toast.xml b/quickstep/recents_ui_overrides/res/layout/arrow_toast.xml index b0f2b4bf8c..980bb5a805 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 0000000000..a5ea523a27 --- /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 0ae7435907..b3bb850806 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 @@ public class AllAppsTipView extends AbstractFloatingView { 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 a07cd1d82c..da588173af 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.Intent; 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 @@ public class HotseatEduController { private static final int ONBOARDING_NOTIFICATION_ID = 7641; private final Launcher mLauncher; + private final NotificationManager mNotificationManager; + private final Notification mNotification; private List mPredictedApps; private HotseatEduDialog mActiveDialog; - private final NotificationManager mNotificationManager; - private final Notification mNotification; + private ArrayList 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 folders = new ArrayDeque<>(); + + ArrayList 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 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 animated = new ArrayList<>(); + ArrayList 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 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 7986c269bc..bcce168b95 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.Button; 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 @@ public class HotseatEduDialog extends AbstractSlideInView implements Insettable 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 @@ public class HotseatEduDialog extends AbstractSlideInView implements Insettable 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 @@ public class HotseatEduDialog extends AbstractSlideInView implements Insettable 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 @@ public class HotseatEduDialog extends AbstractSlideInView implements Insettable 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 @@ public class HotseatEduDialog extends AbstractSlideInView implements Insettable 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 @@ public class HotseatEduDialog extends AbstractSlideInView implements Insettable } } - @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 2cdcd20732..d82e9f0115 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 @@ public class HotseatPredictionController implements DragController.DragListener, 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 @@ public class HotseatPredictionController implements DragController.DragListener, private List 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 @@ public class HotseatPredictionController implements DragController.DragListener, .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 @@ public class HotseatPredictionController implements DragController.DragListener, 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 @@ public class HotseatPredictionController implements DragController.DragListener, } } - @Override public void onDragEnd() { if (mDragObject == null) { @@ -564,7 +561,8 @@ public class HotseatPredictionController implements DragController.DragListener, } @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 12b5fc1afa..bed8278bc7 100644 --- a/src/com/android/launcher3/AbstractFloatingView.java +++ b/src/com/android/launcher3/AbstractFloatingView.java @@ -180,7 +180,10 @@ public abstract class AbstractFloatingView extends LinearLayout implements Touch return null; } - protected static T getOpenView( + /** + * Returns a view matching FloatingViewType + */ + public static 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 4742bbc766..f2d07f2b11 100644 --- a/src/com/android/launcher3/CellLayout.java +++ b/src/com/android/launcher3/CellLayout.java @@ -2766,7 +2766,6 @@ public class CellLayout extends ViewGroup implements Transposable { * 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 @@ public class CellLayout extends ViewGroup implements Transposable { 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 6292167237..4df3b0a7ce 100644 --- a/src/com/android/launcher3/config/FeatureFlags.java +++ b/src/com/android/launcher3/config/FeatureFlags.java @@ -120,12 +120,11 @@ public final class FeatureFlags { "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");