1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.launcher3.hybridhotseat; 17 18 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOTSEAT_EDU_ONLY_TIP; 19 20 import android.content.Intent; 21 import android.graphics.Rect; 22 import android.util.Log; 23 import android.view.Gravity; 24 import android.view.View; 25 26 import com.android.launcher3.BubbleTextView; 27 import com.android.launcher3.CellLayout; 28 import com.android.launcher3.Hotseat; 29 import com.android.launcher3.Launcher; 30 import com.android.launcher3.LauncherSettings; 31 import com.android.launcher3.R; 32 import com.android.launcher3.Utilities; 33 import com.android.launcher3.Workspace; 34 import com.android.launcher3.model.data.ItemInfo; 35 import com.android.launcher3.model.data.WorkspaceItemInfo; 36 import com.android.launcher3.util.IntArray; 37 import com.android.launcher3.views.ArrowTipView; 38 import com.android.launcher3.views.Snackbar; 39 40 import java.util.ArrayList; 41 import java.util.List; 42 import java.util.stream.IntStream; 43 44 /** 45 * Controller class for managing user onboaridng flow for hybrid hotseat 46 */ 47 public class HotseatEduController { 48 49 private static final String TAG = "HotseatEduController"; 50 51 public static final String SETTINGS_ACTION = 52 "android.settings.ACTION_CONTENT_SUGGESTIONS_SETTINGS"; 53 54 private final Launcher mLauncher; 55 private final Hotseat mHotseat; 56 private List<WorkspaceItemInfo> mPredictedApps; 57 private HotseatEduDialog mActiveDialog; 58 59 private ArrayList<ItemInfo> mNewItems = new ArrayList<>(); 60 private IntArray mNewScreens = null; 61 HotseatEduController(Launcher launcher)62 HotseatEduController(Launcher launcher) { 63 mLauncher = launcher; 64 mHotseat = launcher.getHotseat(); 65 } 66 67 /** 68 * Checks what type of migration should be used and migrates hotseat 69 */ migrate()70 void migrate() { 71 HotseatRestoreHelper.createBackup(mLauncher); 72 migrateHotseatWhole(); 73 Snackbar.show(mLauncher, R.string.hotsaet_tip_prediction_enabled, 74 R.string.hotseat_prediction_settings, null, 75 () -> mLauncher.startActivity(getSettingsIntent())); 76 } 77 78 /** 79 * This migration option attempts to move the entire hotseat up to the first workspace that 80 * has space to host items. If no such page is found, it moves items to a new page. 81 * 82 * @return pageId where items are migrated 83 */ migrateHotseatWhole()84 private int migrateHotseatWhole() { 85 Workspace<?> workspace = mLauncher.getWorkspace(); 86 87 int pageId = -1; 88 int toRow = 0; 89 for (int i = 0; i < workspace.getPageCount(); i++) { 90 CellLayout target = workspace.getScreenWithId(workspace.getScreenIdForPageIndex(i)); 91 if (target.makeSpaceForHotseatMigration(true)) { 92 toRow = mLauncher.getDeviceProfile().inv.numRows - 1; 93 pageId = i; 94 break; 95 } 96 } 97 if (pageId == -1) { 98 pageId = mLauncher.getModel().getModelDbController().getNewScreenId(); 99 mNewScreens = IntArray.wrap(pageId); 100 } 101 boolean isPortrait = !mLauncher.getDeviceProfile().isVerticalBarLayout(); 102 int hotseatItemsNum = mLauncher.getDeviceProfile().numShownHotseatIcons; 103 for (int i = 0; i < hotseatItemsNum; i++) { 104 int x = isPortrait ? i : 0; 105 int y = isPortrait ? 0 : hotseatItemsNum - i - 1; 106 View child = mHotseat.getChildAt(x, y); 107 if (child == null || child.getTag() == null) continue; 108 ItemInfo tag = (ItemInfo) child.getTag(); 109 if (tag.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) continue; 110 mLauncher.getModelWriter().moveItemInDatabase(tag, 111 LauncherSettings.Favorites.CONTAINER_DESKTOP, pageId, i, toRow); 112 mNewItems.add(tag); 113 } 114 return pageId; 115 } 116 moveHotseatItems()117 void moveHotseatItems() { 118 mHotseat.removeAllViewsInLayout(); 119 if (!mNewItems.isEmpty()) { 120 int lastPage = mNewItems.get(mNewItems.size() - 1).screenId; 121 ArrayList<ItemInfo> animated = new ArrayList<>(); 122 ArrayList<ItemInfo> nonAnimated = new ArrayList<>(); 123 124 for (ItemInfo info : mNewItems) { 125 if (info.screenId == lastPage) { 126 animated.add(info); 127 } else { 128 nonAnimated.add(info); 129 } 130 } 131 mLauncher.bindAppsAdded(mNewScreens, nonAnimated, animated); 132 } 133 } 134 finishOnboarding()135 void finishOnboarding() { 136 mLauncher.getModel().onWorkspaceUiChanged(); 137 } 138 showDimissTip()139 void showDimissTip() { 140 if (mHotseat.getShortcutsAndWidgets().getChildCount() 141 < mLauncher.getDeviceProfile().numShownHotseatIcons) { 142 Snackbar.show(mLauncher, R.string.hotseat_tip_gaps_filled, 143 R.string.hotseat_prediction_settings, null, 144 () -> mLauncher.startActivity(getSettingsIntent())); 145 } else { 146 showHotseatArrowTip(true, mLauncher.getString(R.string.hotseat_tip_no_empty_slots)); 147 } 148 } 149 setPredictedApps(List<WorkspaceItemInfo> predictedApps)150 void setPredictedApps(List<WorkspaceItemInfo> predictedApps) { 151 mPredictedApps = predictedApps; 152 } 153 showEdu()154 void showEdu() { 155 int childCount = mHotseat.getShortcutsAndWidgets().getChildCount(); 156 CellLayout cellLayout = mLauncher.getWorkspace().getScreenWithId(Workspace.FIRST_SCREEN_ID); 157 // hotseat is already empty and does not require migration. show edu tip 158 boolean requiresMigration = IntStream.range(0, childCount).anyMatch(i -> { 159 View v = mHotseat.getShortcutsAndWidgets().getChildAt(i); 160 return v != null && v.getTag() != null && ((ItemInfo) v.getTag()).container 161 != LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION; 162 }); 163 boolean canMigrateToFirstPage = cellLayout.makeSpaceForHotseatMigration(false); 164 if (requiresMigration && canMigrateToFirstPage) { 165 showDialog(); 166 } else { 167 if (showHotseatArrowTip(requiresMigration, mLauncher.getString( 168 requiresMigration ? R.string.hotseat_tip_no_empty_slots 169 : R.string.hotseat_auto_enrolled))) { 170 mLauncher.getStatsLogManager().logger().log(LAUNCHER_HOTSEAT_EDU_ONLY_TIP); 171 } 172 finishOnboarding(); 173 } 174 } 175 176 /** 177 * Finds a child suitable child in hotseat and shows arrow tip pointing at it. 178 * 179 * @param usePinned used to determine target view. If true, will use the first matching pinned 180 * item. Otherwise, will use the first predicted child 181 * @param message String to be shown inside the arrowView 182 * @return whether suitable child was found and tip was shown 183 */ showHotseatArrowTip(boolean usePinned, String message)184 private boolean showHotseatArrowTip(boolean usePinned, String message) { 185 int childCount = mHotseat.getShortcutsAndWidgets().getChildCount(); 186 boolean isPortrait = !mLauncher.getDeviceProfile().isVerticalBarLayout(); 187 188 BubbleTextView tipTargetView = null; 189 for (int i = childCount - 1; i > -1; i--) { 190 int x = isPortrait ? i : 0; 191 int y = isPortrait ? 0 : i; 192 View v = mHotseat.getShortcutsAndWidgets().getChildAt(x, y); 193 if (v instanceof BubbleTextView && v.getTag() instanceof WorkspaceItemInfo) { 194 ItemInfo info = (ItemInfo) v.getTag(); 195 boolean isPinned = info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT; 196 if (isPinned == usePinned) { 197 tipTargetView = (BubbleTextView) v; 198 break; 199 } 200 } 201 } 202 if (tipTargetView == null) { 203 Log.e(TAG, "Unable to find suitable view for ArrowTip"); 204 return false; 205 } 206 Rect bounds = Utilities.getViewBounds(tipTargetView); 207 new ArrowTipView(mLauncher).show(message, Gravity.END, bounds.centerX(), bounds.top); 208 return true; 209 } 210 showDialog()211 void showDialog() { 212 if (mPredictedApps == null || mPredictedApps.isEmpty()) { 213 return; 214 } 215 if (mActiveDialog != null) { 216 mActiveDialog.handleClose(false); 217 } 218 mActiveDialog = HotseatEduDialog.getDialog(mLauncher); 219 mActiveDialog.setHotseatEduController(this); 220 mActiveDialog.show(mPredictedApps); 221 } 222 getSettingsIntent()223 static Intent getSettingsIntent() { 224 return new Intent(SETTINGS_ACTION).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 225 } 226 } 227