1 /*
2  * Copyright (C) 2018 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;
17 
18 import android.util.Log;
19 import android.view.View;
20 import android.view.ViewGroup;
21 
22 import com.android.launcher3.celllayout.CellLayoutLayoutParams;
23 import com.android.launcher3.celllayout.CellPosMapper;
24 import com.android.launcher3.celllayout.CellPosMapper.CellPos;
25 import com.android.launcher3.folder.Folder;
26 import com.android.launcher3.folder.FolderIcon;
27 import com.android.launcher3.model.data.ItemInfo;
28 import com.android.launcher3.touch.ItemLongClickListener;
29 import com.android.launcher3.util.IntSet;
30 
31 public interface WorkspaceLayoutManager {
32 
33     String TAG = "Launcher.Workspace";
34 
35     // The screen id used for the empty screen always present at the end.
36     int EXTRA_EMPTY_SCREEN_ID = -201;
37     // The screen id used for the second empty screen always present at the end for two panel home.
38     int EXTRA_EMPTY_SCREEN_SECOND_ID = -200;
39     // The screen ids used for the empty screens at the end of the workspaces.
40     IntSet EXTRA_EMPTY_SCREEN_IDS =
41             IntSet.wrap(EXTRA_EMPTY_SCREEN_ID, EXTRA_EMPTY_SCREEN_SECOND_ID);
42 
43     // The is the first screen. It is always present, even if its empty.
44     int FIRST_SCREEN_ID = 0;
45     // This is the second page. On two panel home it is always present, even if its empty.
46     int SECOND_SCREEN_ID = 1;
47 
48     /**
49      * At bind time, we use the rank (screenId) to compute x and y for hotseat items.
50      * See {@link #addInScreen}.
51      */
addInScreenFromBind(View child, ItemInfo info)52     default void addInScreenFromBind(View child, ItemInfo info) {
53         CellPos presenterPos = getCellPosMapper().mapModelToPresenter(info);
54         int x = presenterPos.cellX;
55         int y = presenterPos.cellY;
56         if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT
57                 || info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) {
58             int screenId = presenterPos.screenId;
59             x = getHotseat().getCellXFromOrder(screenId);
60             y = getHotseat().getCellYFromOrder(screenId);
61             // TODO(b/335141365): Remove this log after the bug is fixed.
62             Log.d(TAG, "addInScreenFromBind: hotseat inflation with x = " + x
63                     + " and y = " + y);
64         }
65         addInScreen(child, info.container, presenterPos.screenId, x, y, info.spanX, info.spanY);
66     }
67 
68     /**
69      * Adds the specified child in the specified screen based on the {@param info}
70      * See {@link #addInScreen(View, int, int, int, int, int, int)}.
71      */
addInScreen(View child, ItemInfo info)72     default void addInScreen(View child, ItemInfo info) {
73         CellPos presenterPos = getCellPosMapper().mapModelToPresenter(info);
74         addInScreen(child, info.container,
75                 presenterPos.screenId, presenterPos.cellX, presenterPos.cellY,
76                 info.spanX, info.spanY);
77     }
78 
79     /**
80      * Adds the specified child in the specified screen. The position and dimension of
81      * the child are defined by x, y, spanX and spanY.
82      *
83      * @param child The child to add in one of the workspace's screens.
84      * @param screenId The screen in which to add the child.
85      * @param x The X position of the child in the screen's grid.
86      * @param y The Y position of the child in the screen's grid.
87      * @param spanX The number of cells spanned horizontally by the child.
88      * @param spanY The number of cells spanned vertically by the child.
89      */
addInScreen(View child, int container, int screenId, int x, int y, int spanX, int spanY)90     default void addInScreen(View child, int container, int screenId, int x, int y,
91             int spanX, int spanY) {
92         if (container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
93             if (getScreenWithId(screenId) == null) {
94                 Log.e(TAG, "Skipping child, screenId " + screenId + " not found");
95                 // DEBUGGING - Print out the stack trace to see where we are adding from
96                 new Throwable().printStackTrace();
97                 return;
98             }
99         }
100         if (EXTRA_EMPTY_SCREEN_IDS.contains(screenId)) {
101             // This should never happen
102             throw new RuntimeException("Screen id should not be extra empty screen: " + screenId);
103         }
104 
105         final CellLayout layout;
106         if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT
107                 || container == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) {
108             layout = getHotseat();
109 
110             // Hide folder title in the hotseat
111             if (child instanceof FolderIcon) {
112                 ((FolderIcon) child).setTextVisible(false);
113             }
114         } else {
115             // Show folder title if not in the hotseat
116             if (child instanceof FolderIcon) {
117                 ((FolderIcon) child).setTextVisible(true);
118             }
119             layout = getScreenWithId(screenId);
120         }
121 
122         ViewGroup.LayoutParams genericLp = child.getLayoutParams();
123         CellLayoutLayoutParams lp;
124         if (genericLp == null || !(genericLp instanceof CellLayoutLayoutParams)) {
125             lp = new CellLayoutLayoutParams(x, y, spanX, spanY);
126         } else {
127             lp = (CellLayoutLayoutParams) genericLp;
128             lp.setCellX(x);
129             lp.setCellY(y);
130             lp.cellHSpan = spanX;
131             lp.cellVSpan = spanY;
132         }
133 
134         if (spanX < 0 && spanY < 0) {
135             lp.isLockedToGrid = false;
136         }
137 
138         // Get the canonical child id to uniquely represent this view in this screen
139         ItemInfo info = (ItemInfo) child.getTag();
140         int childId = info.getViewId();
141 
142         boolean markCellsAsOccupied = !(child instanceof Folder);
143         if (!layout.addViewToCellLayout(child, -1, childId, lp, markCellsAsOccupied)) {
144             // TODO: This branch occurs when the workspace is adding views
145             // outside of the defined grid
146             // maybe we should be deleting these items from the LauncherModel?
147             Log.e(TAG, "Failed to add to item at (" + lp.getCellX() + "," + lp.getCellY()
148                     + ") to CellLayout");
149         }
150 
151         child.setHapticFeedbackEnabled(false);
152         child.setOnLongClickListener(getWorkspaceChildOnLongClickListener());
153         if (child instanceof DropTarget) {
154             onAddDropTarget((DropTarget) child);
155         }
156     }
157 
getWorkspaceChildOnLongClickListener()158     default View.OnLongClickListener getWorkspaceChildOnLongClickListener() {
159         return ItemLongClickListener.INSTANCE_WORKSPACE;
160     }
161 
162     /**
163      * Returns the mapper for converting between model and presenter
164      */
getCellPosMapper()165     CellPosMapper getCellPosMapper();
166 
getHotseat()167     Hotseat getHotseat();
168 
getScreenWithId(int screenId)169     CellLayout getScreenWithId(int screenId);
170 
onAddDropTarget(DropTarget target)171     default void onAddDropTarget(DropTarget target) { }
172 }
173