Loading quickstep/recents_ui_overrides/res/layout/arrow_toast.xml +1 −0 Original line number Diff line number Diff line Loading @@ -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"/> Loading quickstep/recents_ui_overrides/src/com/android/launcher3/ArrowTipView.java 0 → 100644 +151 −0 Original line number Diff line number Diff line /* * 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; } } quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AllAppsTipView.java +13 −129 Original line number Diff line number Diff line Loading @@ -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(); Loading @@ -156,28 +53,15 @@ public class AllAppsTipView extends AbstractFloatingView { return false; } AllAppsTipView allAppsTipView = new AllAppsTipView(launcher.getAppsView().getContext(), null); launcher.getDragLayer().addView(allAppsTipView); 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(); 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]); launcher.getUserEventDispatcher().logActionTip( LauncherEventUtil.VISIBLE, ALL_APPS_PREDICTION_TIPS); return true; } Loading quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java +157 −21 Original line number Diff line number Diff line Loading @@ -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; /** Loading @@ -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<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() { /** * Checks what type of migration should be used and migrates hotseat */ void migrate() { if (FeatureFlags.HOTSEAT_MIGRATE_TO_FOLDER.get()) { migrateToFolder(); } else { migrateHotseatWhole(); } } /** * 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(); 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; } ViewGroup hotseatVG = mLauncher.getHotseat().getShortcutsAndWidgets(); for (int i = 0; i < hotseatVG.getChildCount(); i++) { View child = hotseatVG.getChildAt(i); 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) { Loading quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java +19 −28 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
quickstep/recents_ui_overrides/res/layout/arrow_toast.xml +1 −0 Original line number Diff line number Diff line Loading @@ -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"/> Loading
quickstep/recents_ui_overrides/src/com/android/launcher3/ArrowTipView.java 0 → 100644 +151 −0 Original line number Diff line number Diff line /* * 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; } }
quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AllAppsTipView.java +13 −129 Original line number Diff line number Diff line Loading @@ -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(); Loading @@ -156,28 +53,15 @@ public class AllAppsTipView extends AbstractFloatingView { return false; } AllAppsTipView allAppsTipView = new AllAppsTipView(launcher.getAppsView().getContext(), null); launcher.getDragLayer().addView(allAppsTipView); 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(); 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]); launcher.getUserEventDispatcher().logActionTip( LauncherEventUtil.VISIBLE, ALL_APPS_PREDICTION_TIPS); return true; } Loading
quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java +157 −21 Original line number Diff line number Diff line Loading @@ -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; /** Loading @@ -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<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() { /** * Checks what type of migration should be used and migrates hotseat */ void migrate() { if (FeatureFlags.HOTSEAT_MIGRATE_TO_FOLDER.get()) { migrateToFolder(); } else { migrateHotseatWhole(); } } /** * 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(); 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; } ViewGroup hotseatVG = mLauncher.getHotseat().getShortcutsAndWidgets(); for (int i = 0; i < hotseatVG.getChildCount(); i++) { View child = hotseatVG.getChildAt(i); 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) { Loading
quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java +19 −28 File changed.Preview size limit exceeded, changes collapsed. Show changes