• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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