• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 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 
17 package com.android.launcher3;
18 
19 import android.appwidget.AppWidgetHostView;
20 import android.content.ComponentName;
21 import android.content.Context;
22 import android.content.res.Configuration;
23 import android.content.res.Resources;
24 import android.graphics.Paint;
25 import android.graphics.Paint.FontMetrics;
26 import android.graphics.Point;
27 import android.graphics.PointF;
28 import android.graphics.Rect;
29 import android.util.DisplayMetrics;
30 import android.view.Display;
31 import android.view.Gravity;
32 import android.view.Surface;
33 import android.view.View;
34 import android.view.ViewGroup;
35 import android.view.ViewGroup.LayoutParams;
36 import android.view.WindowManager;
37 import android.widget.FrameLayout;
38 import android.widget.LinearLayout;
39 
40 import java.util.ArrayList;
41 import java.util.Collections;
42 import java.util.Comparator;
43 
44 
45 class DeviceProfileQuery {
46     DeviceProfile profile;
47     float widthDps;
48     float heightDps;
49     float value;
50     PointF dimens;
51 
DeviceProfileQuery(DeviceProfile p, float v)52     DeviceProfileQuery(DeviceProfile p, float v) {
53         widthDps = p.minWidthDps;
54         heightDps = p.minHeightDps;
55         value = v;
56         dimens = new PointF(widthDps, heightDps);
57         profile = p;
58     }
59 }
60 
61 public class DeviceProfile {
62     public static interface DeviceProfileCallbacks {
onAvailableSizeChanged(DeviceProfile grid)63         public void onAvailableSizeChanged(DeviceProfile grid);
64     }
65 
66     String name;
67     float minWidthDps;
68     float minHeightDps;
69     float numRows;
70     float numColumns;
71     float numHotseatIcons;
72     float iconSize;
73     private float iconTextSize;
74     private int iconDrawablePaddingOriginalPx;
75     private float hotseatIconSize;
76 
77     int defaultLayoutId;
78     int defaultNoAllAppsLayoutId;
79 
80     boolean isLandscape;
81     boolean isTablet;
82     boolean isLargeTablet;
83     boolean isLayoutRtl;
84     boolean transposeLayoutWithOrientation;
85 
86     int desiredWorkspaceLeftRightMarginPx;
87     int edgeMarginPx;
88     Rect defaultWidgetPadding;
89 
90     int widthPx;
91     int heightPx;
92     int availableWidthPx;
93     int availableHeightPx;
94     int defaultPageSpacingPx;
95 
96     int overviewModeMinIconZoneHeightPx;
97     int overviewModeMaxIconZoneHeightPx;
98     int overviewModeBarItemWidthPx;
99     int overviewModeBarSpacerWidthPx;
100     float overviewModeIconZoneRatio;
101     float overviewModeScaleFactor;
102 
103     int iconSizePx;
104     int iconTextSizePx;
105     int iconDrawablePaddingPx;
106     int cellWidthPx;
107     int cellHeightPx;
108     int allAppsIconSizePx;
109     int allAppsIconTextSizePx;
110     int allAppsCellWidthPx;
111     int allAppsCellHeightPx;
112     int allAppsCellPaddingPx;
113     int folderBackgroundOffset;
114     int folderIconSizePx;
115     int folderCellWidthPx;
116     int folderCellHeightPx;
117     int hotseatCellWidthPx;
118     int hotseatCellHeightPx;
119     int hotseatIconSizePx;
120     int hotseatBarHeightPx;
121     int hotseatAllAppsRank;
122     int allAppsNumRows;
123     int allAppsNumCols;
124     int searchBarSpaceWidthPx;
125     int searchBarSpaceMaxWidthPx;
126     int searchBarSpaceHeightPx;
127     int searchBarHeightPx;
128     int pageIndicatorHeightPx;
129     int allAppsButtonVisualSize;
130 
131     float dragViewScale;
132 
133     int allAppsShortEdgeCount = -1;
134     int allAppsLongEdgeCount = -1;
135 
136     private ArrayList<DeviceProfileCallbacks> mCallbacks = new ArrayList<DeviceProfileCallbacks>();
137 
DeviceProfile(String n, float w, float h, float r, float c, float is, float its, float hs, float his, int dlId, int dnalId)138     DeviceProfile(String n, float w, float h, float r, float c,
139                   float is, float its, float hs, float his, int dlId, int dnalId) {
140         // Ensure that we have an odd number of hotseat items (since we need to place all apps)
141         if (!LauncherAppState.isDisableAllApps() && hs % 2 == 0) {
142             throw new RuntimeException("All Device Profiles must have an odd number of hotseat spaces");
143         }
144 
145         name = n;
146         minWidthDps = w;
147         minHeightDps = h;
148         numRows = r;
149         numColumns = c;
150         iconSize = is;
151         iconTextSize = its;
152         numHotseatIcons = hs;
153         hotseatIconSize = his;
154         defaultLayoutId = dlId;
155         defaultNoAllAppsLayoutId = dnalId;
156     }
157 
DeviceProfile()158     DeviceProfile() {
159     }
160 
DeviceProfile(Context context, ArrayList<DeviceProfile> profiles, float minWidth, float minHeight, int wPx, int hPx, int awPx, int ahPx, Resources res)161     DeviceProfile(Context context,
162                   ArrayList<DeviceProfile> profiles,
163                   float minWidth, float minHeight,
164                   int wPx, int hPx,
165                   int awPx, int ahPx,
166                   Resources res) {
167         DisplayMetrics dm = res.getDisplayMetrics();
168         ArrayList<DeviceProfileQuery> points =
169                 new ArrayList<DeviceProfileQuery>();
170         transposeLayoutWithOrientation =
171                 res.getBoolean(R.bool.hotseat_transpose_layout_with_orientation);
172         minWidthDps = minWidth;
173         minHeightDps = minHeight;
174 
175         ComponentName cn = new ComponentName(context.getPackageName(),
176                 this.getClass().getName());
177         defaultWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(context, cn, null);
178         edgeMarginPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);
179         desiredWorkspaceLeftRightMarginPx = 2 * edgeMarginPx;
180         pageIndicatorHeightPx =
181                 res.getDimensionPixelSize(R.dimen.dynamic_grid_page_indicator_height);
182         defaultPageSpacingPx =
183                 res.getDimensionPixelSize(R.dimen.dynamic_grid_workspace_page_spacing);
184         allAppsCellPaddingPx =
185                 res.getDimensionPixelSize(R.dimen.dynamic_grid_all_apps_cell_padding);
186         overviewModeMinIconZoneHeightPx =
187                 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_min_icon_zone_height);
188         overviewModeMaxIconZoneHeightPx =
189                 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_max_icon_zone_height);
190         overviewModeBarItemWidthPx =
191                 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_bar_item_width);
192         overviewModeBarSpacerWidthPx =
193                 res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_bar_spacer_width);
194         overviewModeIconZoneRatio =
195                 res.getInteger(R.integer.config_dynamic_grid_overview_icon_zone_percentage) / 100f;
196         overviewModeScaleFactor =
197                 res.getInteger(R.integer.config_dynamic_grid_overview_scale_percentage) / 100f;
198 
199         // Find the closes profile given the width/height
200         for (DeviceProfile p : profiles) {
201             points.add(new DeviceProfileQuery(p, 0f));
202         }
203         DeviceProfile closestProfile = findClosestDeviceProfile(minWidth, minHeight, points);
204 
205         // Snap to the closest row count
206         numRows = closestProfile.numRows;
207 
208         // Snap to the closest column count
209         numColumns = closestProfile.numColumns;
210 
211         // Snap to the closest hotseat size
212         numHotseatIcons = closestProfile.numHotseatIcons;
213         hotseatAllAppsRank = (int) (numHotseatIcons / 2);
214 
215         // Snap to the closest default layout id
216         defaultLayoutId = closestProfile.defaultLayoutId;
217 
218         // Snap to the closest default no all-apps layout id
219         defaultNoAllAppsLayoutId = closestProfile.defaultNoAllAppsLayoutId;
220 
221         // Interpolate the icon size
222         points.clear();
223         for (DeviceProfile p : profiles) {
224             points.add(new DeviceProfileQuery(p, p.iconSize));
225         }
226         iconSize = invDistWeightedInterpolate(minWidth, minHeight, points);
227 
228         // AllApps uses the original non-scaled icon size
229         allAppsIconSizePx = DynamicGrid.pxFromDp(iconSize, dm);
230 
231         // Interpolate the icon text size
232         points.clear();
233         for (DeviceProfile p : profiles) {
234             points.add(new DeviceProfileQuery(p, p.iconTextSize));
235         }
236         iconTextSize = invDistWeightedInterpolate(minWidth, minHeight, points);
237         iconDrawablePaddingOriginalPx =
238                 res.getDimensionPixelSize(R.dimen.dynamic_grid_icon_drawable_padding);
239         // AllApps uses the original non-scaled icon text size
240         allAppsIconTextSizePx = DynamicGrid.pxFromDp(iconTextSize, dm);
241 
242         // Interpolate the hotseat icon size
243         points.clear();
244         for (DeviceProfile p : profiles) {
245             points.add(new DeviceProfileQuery(p, p.hotseatIconSize));
246         }
247         // Hotseat
248         hotseatIconSize = invDistWeightedInterpolate(minWidth, minHeight, points);
249 
250         // If the partner customization apk contains any grid overrides, apply them
251         applyPartnerDeviceProfileOverrides(context, dm);
252 
253         // Calculate the remaining vars
254         updateFromConfiguration(context, res, wPx, hPx, awPx, ahPx);
255         updateAvailableDimensions(context);
256         computeAllAppsButtonSize(context);
257     }
258 
259     /**
260      * Apply any Partner customization grid overrides.
261      *
262      * Currently we support: all apps row / column count.
263      */
applyPartnerDeviceProfileOverrides(Context ctx, DisplayMetrics dm)264     private void applyPartnerDeviceProfileOverrides(Context ctx, DisplayMetrics dm) {
265         Partner p = Partner.get(ctx.getPackageManager());
266         if (p != null) {
267             DeviceProfile partnerDp = p.getDeviceProfileOverride(dm);
268             if (partnerDp != null) {
269                 if (partnerDp.numRows > 0 && partnerDp.numColumns > 0) {
270                     numRows = partnerDp.numRows;
271                     numColumns = partnerDp.numColumns;
272                 }
273                 if (partnerDp.allAppsShortEdgeCount > 0 && partnerDp.allAppsLongEdgeCount > 0) {
274                     allAppsShortEdgeCount = partnerDp.allAppsShortEdgeCount;
275                     allAppsLongEdgeCount = partnerDp.allAppsLongEdgeCount;
276                 }
277                 if (partnerDp.iconSize > 0) {
278                     iconSize = partnerDp.iconSize;
279                     // AllApps uses the original non-scaled icon size
280                     allAppsIconSizePx = DynamicGrid.pxFromDp(iconSize, dm);
281                 }
282             }
283         }
284     }
285 
286     /**
287      * Determine the exact visual footprint of the all apps button, taking into account scaling
288      * and internal padding of the drawable.
289      */
computeAllAppsButtonSize(Context context)290     private void computeAllAppsButtonSize(Context context) {
291         Resources res = context.getResources();
292         float padding = res.getInteger(R.integer.config_allAppsButtonPaddingPercent) / 100f;
293         LauncherAppState app = LauncherAppState.getInstance();
294         allAppsButtonVisualSize = (int) (hotseatIconSizePx * (1 - padding));
295     }
296 
addCallback(DeviceProfileCallbacks cb)297     void addCallback(DeviceProfileCallbacks cb) {
298         mCallbacks.add(cb);
299         cb.onAvailableSizeChanged(this);
300     }
removeCallback(DeviceProfileCallbacks cb)301     void removeCallback(DeviceProfileCallbacks cb) {
302         mCallbacks.remove(cb);
303     }
304 
getDeviceOrientation(Context context)305     private int getDeviceOrientation(Context context) {
306         WindowManager windowManager =  (WindowManager)
307                 context.getSystemService(Context.WINDOW_SERVICE);
308         Resources resources = context.getResources();
309         DisplayMetrics dm = resources.getDisplayMetrics();
310         Configuration config = resources.getConfiguration();
311         int rotation = windowManager.getDefaultDisplay().getRotation();
312 
313         boolean isLandscape = (config.orientation == Configuration.ORIENTATION_LANDSCAPE) &&
314                 (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180);
315         boolean isRotatedPortrait = (config.orientation == Configuration.ORIENTATION_PORTRAIT) &&
316                 (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270);
317         if (isLandscape || isRotatedPortrait) {
318             return CellLayout.LANDSCAPE;
319         } else {
320             return CellLayout.PORTRAIT;
321         }
322     }
323 
updateAvailableDimensions(Context context)324     private void updateAvailableDimensions(Context context) {
325         WindowManager windowManager =  (WindowManager)
326                 context.getSystemService(Context.WINDOW_SERVICE);
327         Display display = windowManager.getDefaultDisplay();
328         Resources resources = context.getResources();
329         DisplayMetrics dm = resources.getDisplayMetrics();
330         Configuration config = resources.getConfiguration();
331 
332         // There are three possible configurations that the dynamic grid accounts for, portrait,
333         // landscape with the nav bar at the bottom, and landscape with the nav bar at the side.
334         // To prevent waiting for fitSystemWindows(), we make the observation that in landscape,
335         // the height is the smallest height (either with the nav bar at the bottom or to the
336         // side) and otherwise, the height is simply the largest possible height for a portrait
337         // device.
338         Point size = new Point();
339         Point smallestSize = new Point();
340         Point largestSize = new Point();
341         display.getSize(size);
342         display.getCurrentSizeRange(smallestSize, largestSize);
343         availableWidthPx = size.x;
344         if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) {
345             availableHeightPx = smallestSize.y;
346         } else {
347             availableHeightPx = largestSize.y;
348         }
349 
350         // Check to see if the icons fit in the new available height.  If not, then we need to
351         // shrink the icon size.
352         float scale = 1f;
353         int drawablePadding = iconDrawablePaddingOriginalPx;
354         updateIconSize(1f, drawablePadding, resources, dm);
355         float usedHeight = (cellHeightPx * numRows);
356 
357         Rect workspacePadding = getWorkspacePadding();
358         int maxHeight = (availableHeightPx - workspacePadding.top - workspacePadding.bottom);
359         if (usedHeight > maxHeight) {
360             scale = maxHeight / usedHeight;
361             drawablePadding = 0;
362         }
363         updateIconSize(scale, drawablePadding, resources, dm);
364 
365         // Make the callbacks
366         for (DeviceProfileCallbacks cb : mCallbacks) {
367             cb.onAvailableSizeChanged(this);
368         }
369     }
370 
updateIconSize(float scale, int drawablePadding, Resources resources, DisplayMetrics dm)371     private void updateIconSize(float scale, int drawablePadding, Resources resources,
372                                 DisplayMetrics dm) {
373         iconSizePx = (int) (DynamicGrid.pxFromDp(iconSize, dm) * scale);
374         iconTextSizePx = (int) (DynamicGrid.pxFromSp(iconTextSize, dm) * scale);
375         iconDrawablePaddingPx = drawablePadding;
376         hotseatIconSizePx = (int) (DynamicGrid.pxFromDp(hotseatIconSize, dm) * scale);
377 
378         // Search Bar
379         searchBarSpaceMaxWidthPx = resources.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_max_width);
380         searchBarHeightPx = resources.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_height);
381         searchBarSpaceWidthPx = Math.min(searchBarSpaceMaxWidthPx, widthPx);
382         searchBarSpaceHeightPx = searchBarHeightPx + getSearchBarTopOffset();
383 
384         // Calculate the actual text height
385         Paint textPaint = new Paint();
386         textPaint.setTextSize(iconTextSizePx);
387         FontMetrics fm = textPaint.getFontMetrics();
388         cellWidthPx = iconSizePx;
389         cellHeightPx = iconSizePx + iconDrawablePaddingPx + (int) Math.ceil(fm.bottom - fm.top);
390         final float scaleDps = resources.getDimensionPixelSize(R.dimen.dragViewScale);
391         dragViewScale = (iconSizePx + scaleDps) / iconSizePx;
392 
393         // Hotseat
394         hotseatBarHeightPx = iconSizePx + 4 * edgeMarginPx;
395         hotseatCellWidthPx = iconSizePx;
396         hotseatCellHeightPx = iconSizePx;
397 
398         // Folder
399         folderCellWidthPx = cellWidthPx + 3 * edgeMarginPx;
400         folderCellHeightPx = cellHeightPx + edgeMarginPx;
401         folderBackgroundOffset = -edgeMarginPx;
402         folderIconSizePx = iconSizePx + 2 * -folderBackgroundOffset;
403 
404         // All Apps
405         Rect padding = getWorkspacePadding(isLandscape ?
406                 CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
407         int pageIndicatorOffset =
408                 resources.getDimensionPixelSize(R.dimen.apps_customize_page_indicator_offset);
409         allAppsCellWidthPx = allAppsIconSizePx;
410         allAppsCellHeightPx = allAppsIconSizePx + drawablePadding + iconTextSizePx;
411         int maxLongEdgeCellCount =
412                 resources.getInteger(R.integer.config_dynamic_grid_max_long_edge_cell_count);
413         int maxShortEdgeCellCount =
414                 resources.getInteger(R.integer.config_dynamic_grid_max_short_edge_cell_count);
415         int minEdgeCellCount =
416                 resources.getInteger(R.integer.config_dynamic_grid_min_edge_cell_count);
417         int maxRows = (isLandscape ? maxShortEdgeCellCount : maxLongEdgeCellCount);
418         int maxCols = (isLandscape ? maxLongEdgeCellCount : maxShortEdgeCellCount);
419 
420         if (allAppsShortEdgeCount > 0 && allAppsLongEdgeCount > 0) {
421             allAppsNumRows = isLandscape ? allAppsShortEdgeCount : allAppsLongEdgeCount;
422             allAppsNumCols = isLandscape ? allAppsLongEdgeCount : allAppsShortEdgeCount;
423         } else {
424             allAppsNumRows = (availableHeightPx - pageIndicatorHeightPx) /
425                     (allAppsCellHeightPx + allAppsCellPaddingPx);
426             allAppsNumRows = Math.max(minEdgeCellCount, Math.min(maxRows, allAppsNumRows));
427             allAppsNumCols = (availableWidthPx) /
428                     (allAppsCellWidthPx + allAppsCellPaddingPx);
429             allAppsNumCols = Math.max(minEdgeCellCount, Math.min(maxCols, allAppsNumCols));
430         }
431     }
432 
updateFromConfiguration(Context context, Resources resources, int wPx, int hPx, int awPx, int ahPx)433     void updateFromConfiguration(Context context, Resources resources, int wPx, int hPx,
434                                  int awPx, int ahPx) {
435         Configuration configuration = resources.getConfiguration();
436         isLandscape = (configuration.orientation == Configuration.ORIENTATION_LANDSCAPE);
437         isTablet = resources.getBoolean(R.bool.is_tablet);
438         isLargeTablet = resources.getBoolean(R.bool.is_large_tablet);
439         if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) {
440             isLayoutRtl = (configuration.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL);
441         } else {
442             isLayoutRtl = false;
443         }
444         widthPx = wPx;
445         heightPx = hPx;
446         availableWidthPx = awPx;
447         availableHeightPx = ahPx;
448 
449         updateAvailableDimensions(context);
450     }
451 
dist(PointF p0, PointF p1)452     private float dist(PointF p0, PointF p1) {
453         return (float) Math.sqrt((p1.x - p0.x)*(p1.x-p0.x) +
454                 (p1.y-p0.y)*(p1.y-p0.y));
455     }
456 
weight(PointF a, PointF b, float pow)457     private float weight(PointF a, PointF b,
458                         float pow) {
459         float d = dist(a, b);
460         if (d == 0f) {
461             return Float.POSITIVE_INFINITY;
462         }
463         return (float) (1f / Math.pow(d, pow));
464     }
465 
466     /** Returns the closest device profile given the width and height and a list of profiles */
findClosestDeviceProfile(float width, float height, ArrayList<DeviceProfileQuery> points)467     private DeviceProfile findClosestDeviceProfile(float width, float height,
468                                                    ArrayList<DeviceProfileQuery> points) {
469         return findClosestDeviceProfiles(width, height, points).get(0).profile;
470     }
471 
472     /** Returns the closest device profiles ordered by closeness to the specified width and height */
findClosestDeviceProfiles(float width, float height, ArrayList<DeviceProfileQuery> points)473     private ArrayList<DeviceProfileQuery> findClosestDeviceProfiles(float width, float height,
474                                                    ArrayList<DeviceProfileQuery> points) {
475         final PointF xy = new PointF(width, height);
476 
477         // Sort the profiles by their closeness to the dimensions
478         ArrayList<DeviceProfileQuery> pointsByNearness = points;
479         Collections.sort(pointsByNearness, new Comparator<DeviceProfileQuery>() {
480             public int compare(DeviceProfileQuery a, DeviceProfileQuery b) {
481                 return (int) (dist(xy, a.dimens) - dist(xy, b.dimens));
482             }
483         });
484 
485         return pointsByNearness;
486     }
487 
invDistWeightedInterpolate(float width, float height, ArrayList<DeviceProfileQuery> points)488     private float invDistWeightedInterpolate(float width, float height,
489                 ArrayList<DeviceProfileQuery> points) {
490         float sum = 0;
491         float weights = 0;
492         float pow = 5;
493         float kNearestNeighbors = 3;
494         final PointF xy = new PointF(width, height);
495 
496         ArrayList<DeviceProfileQuery> pointsByNearness = findClosestDeviceProfiles(width, height,
497                 points);
498 
499         for (int i = 0; i < pointsByNearness.size(); ++i) {
500             DeviceProfileQuery p = pointsByNearness.get(i);
501             if (i < kNearestNeighbors) {
502                 float w = weight(xy, p.dimens, pow);
503                 if (w == Float.POSITIVE_INFINITY) {
504                     return p.value;
505                 }
506                 weights += w;
507             }
508         }
509 
510         for (int i = 0; i < pointsByNearness.size(); ++i) {
511             DeviceProfileQuery p = pointsByNearness.get(i);
512             if (i < kNearestNeighbors) {
513                 float w = weight(xy, p.dimens, pow);
514                 sum += w * p.value / weights;
515             }
516         }
517 
518         return sum;
519     }
520 
521     /** Returns the search bar top offset */
getSearchBarTopOffset()522     int getSearchBarTopOffset() {
523         if (isTablet() && !isVerticalBarLayout()) {
524             return 4 * edgeMarginPx;
525         } else {
526             return 2 * edgeMarginPx;
527         }
528     }
529 
530     /** Returns the search bar bounds in the current orientation */
getSearchBarBounds()531     Rect getSearchBarBounds() {
532         return getSearchBarBounds(isLandscape ? CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
533     }
534     /** Returns the search bar bounds in the specified orientation */
getSearchBarBounds(int orientation)535     Rect getSearchBarBounds(int orientation) {
536         Rect bounds = new Rect();
537         if (orientation == CellLayout.LANDSCAPE &&
538                 transposeLayoutWithOrientation) {
539             if (isLayoutRtl) {
540                 bounds.set(availableWidthPx - searchBarSpaceHeightPx, edgeMarginPx,
541                         availableWidthPx, availableHeightPx - edgeMarginPx);
542             } else {
543                 bounds.set(0, edgeMarginPx, searchBarSpaceHeightPx,
544                         availableHeightPx - edgeMarginPx);
545             }
546         } else {
547             if (isTablet()) {
548                 // Pad the left and right of the workspace to ensure consistent spacing
549                 // between all icons
550                 int width = (orientation == CellLayout.LANDSCAPE)
551                         ? Math.max(widthPx, heightPx)
552                         : Math.min(widthPx, heightPx);
553                 // XXX: If the icon size changes across orientations, we will have to take
554                 //      that into account here too.
555                 int gap = (int) ((width - 2 * edgeMarginPx -
556                         (numColumns * cellWidthPx)) / (2 * (numColumns + 1)));
557                 bounds.set(edgeMarginPx + gap, getSearchBarTopOffset(),
558                         availableWidthPx - (edgeMarginPx + gap),
559                         searchBarSpaceHeightPx);
560             } else {
561                 bounds.set(desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.left,
562                         getSearchBarTopOffset(),
563                         availableWidthPx - (desiredWorkspaceLeftRightMarginPx -
564                         defaultWidgetPadding.right), searchBarSpaceHeightPx);
565             }
566         }
567         return bounds;
568     }
569 
570     /** Returns the bounds of the workspace page indicators. */
getWorkspacePageIndicatorBounds(Rect insets)571     Rect getWorkspacePageIndicatorBounds(Rect insets) {
572         Rect workspacePadding = getWorkspacePadding();
573         if (isLandscape && transposeLayoutWithOrientation) {
574             if (isLayoutRtl) {
575                 return new Rect(workspacePadding.left, workspacePadding.top,
576                         workspacePadding.left + pageIndicatorHeightPx,
577                         heightPx - workspacePadding.bottom - insets.bottom);
578             } else {
579                 int pageIndicatorLeft = widthPx - workspacePadding.right;
580                 return new Rect(pageIndicatorLeft, workspacePadding.top,
581                         pageIndicatorLeft + pageIndicatorHeightPx,
582                         heightPx - workspacePadding.bottom - insets.bottom);
583             }
584         } else {
585             int pageIndicatorTop = heightPx - insets.bottom - workspacePadding.bottom;
586             return new Rect(workspacePadding.left, pageIndicatorTop,
587                     widthPx - workspacePadding.right, pageIndicatorTop + pageIndicatorHeightPx);
588         }
589     }
590 
591     /** Returns the workspace padding in the specified orientation */
getWorkspacePadding()592     Rect getWorkspacePadding() {
593         return getWorkspacePadding(isLandscape ? CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
594     }
getWorkspacePadding(int orientation)595     Rect getWorkspacePadding(int orientation) {
596         Rect searchBarBounds = getSearchBarBounds(orientation);
597         Rect padding = new Rect();
598         if (orientation == CellLayout.LANDSCAPE &&
599                 transposeLayoutWithOrientation) {
600             // Pad the left and right of the workspace with search/hotseat bar sizes
601             if (isLayoutRtl) {
602                 padding.set(hotseatBarHeightPx, edgeMarginPx,
603                         searchBarBounds.width(), edgeMarginPx);
604             } else {
605                 padding.set(searchBarBounds.width(), edgeMarginPx,
606                         hotseatBarHeightPx, edgeMarginPx);
607             }
608         } else {
609             if (isTablet()) {
610                 // Pad the left and right of the workspace to ensure consistent spacing
611                 // between all icons
612                 float gapScale = 1f + (dragViewScale - 1f) / 2f;
613                 int width = (orientation == CellLayout.LANDSCAPE)
614                         ? Math.max(widthPx, heightPx)
615                         : Math.min(widthPx, heightPx);
616                 int height = (orientation != CellLayout.LANDSCAPE)
617                         ? Math.max(widthPx, heightPx)
618                         : Math.min(widthPx, heightPx);
619                 int paddingTop = searchBarBounds.bottom;
620                 int paddingBottom = hotseatBarHeightPx + pageIndicatorHeightPx;
621                 int availableWidth = Math.max(0, width - (int) ((numColumns * cellWidthPx) +
622                         (numColumns * gapScale * cellWidthPx)));
623                 int availableHeight = Math.max(0, height - paddingTop - paddingBottom
624                         - (int) (2 * numRows * cellHeightPx));
625                 padding.set(availableWidth / 2, paddingTop + availableHeight / 2,
626                         availableWidth / 2, paddingBottom + availableHeight / 2);
627             } else {
628                 // Pad the top and bottom of the workspace with search/hotseat bar sizes
629                 padding.set(desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.left,
630                         searchBarBounds.bottom,
631                         desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.right,
632                         hotseatBarHeightPx + pageIndicatorHeightPx);
633             }
634         }
635         return padding;
636     }
637 
getWorkspacePageSpacing(int orientation)638     int getWorkspacePageSpacing(int orientation) {
639         if ((orientation == CellLayout.LANDSCAPE &&
640                 transposeLayoutWithOrientation) || isLargeTablet()) {
641             // In landscape mode the page spacing is set to the default.
642             return defaultPageSpacingPx;
643         } else {
644             // In portrait, we want the pages spaced such that there is no
645             // overhang of the previous / next page into the current page viewport.
646             // We assume symmetrical padding in portrait mode.
647             return Math.max(defaultPageSpacingPx, 2 * getWorkspacePadding().left);
648         }
649     }
650 
getOverviewModeButtonBarRect()651     Rect getOverviewModeButtonBarRect() {
652         int zoneHeight = (int) (overviewModeIconZoneRatio * availableHeightPx);
653         zoneHeight = Math.min(overviewModeMaxIconZoneHeightPx,
654                 Math.max(overviewModeMinIconZoneHeightPx, zoneHeight));
655         return new Rect(0, availableHeightPx - zoneHeight, 0, availableHeightPx);
656     }
657 
getOverviewModeScale()658     float getOverviewModeScale() {
659         Rect workspacePadding = getWorkspacePadding();
660         Rect overviewBar = getOverviewModeButtonBarRect();
661         int pageSpace = availableHeightPx - workspacePadding.top - workspacePadding.bottom;
662         return (overviewModeScaleFactor * (pageSpace - overviewBar.height())) / pageSpace;
663     }
664 
665     // The rect returned will be extended to below the system ui that covers the workspace
getHotseatRect()666     Rect getHotseatRect() {
667         if (isVerticalBarLayout()) {
668             return new Rect(availableWidthPx - hotseatBarHeightPx, 0,
669                     Integer.MAX_VALUE, availableHeightPx);
670         } else {
671             return new Rect(0, availableHeightPx - hotseatBarHeightPx,
672                     availableWidthPx, Integer.MAX_VALUE);
673         }
674     }
675 
calculateCellWidth(int width, int countX)676     int calculateCellWidth(int width, int countX) {
677         return width / countX;
678     }
calculateCellHeight(int height, int countY)679     int calculateCellHeight(int height, int countY) {
680         return height / countY;
681     }
682 
isPhone()683     boolean isPhone() {
684         return !isTablet && !isLargeTablet;
685     }
isTablet()686     boolean isTablet() {
687         return isTablet;
688     }
isLargeTablet()689     boolean isLargeTablet() {
690         return isLargeTablet;
691     }
692 
isVerticalBarLayout()693     boolean isVerticalBarLayout() {
694         return isLandscape && transposeLayoutWithOrientation;
695     }
696 
shouldFadeAdjacentWorkspaceScreens()697     boolean shouldFadeAdjacentWorkspaceScreens() {
698         return isVerticalBarLayout() || isLargeTablet();
699     }
700 
getVisibleChildCount(ViewGroup parent)701     int getVisibleChildCount(ViewGroup parent) {
702         int visibleChildren = 0;
703         for (int i = 0; i < parent.getChildCount(); i++) {
704             if (parent.getChildAt(i).getVisibility() != View.GONE) {
705                 visibleChildren++;
706             }
707         }
708         return visibleChildren;
709     }
710 
calculateOverviewModeWidth(int visibleChildCount)711     int calculateOverviewModeWidth(int visibleChildCount) {
712         return visibleChildCount * overviewModeBarItemWidthPx +
713                 (visibleChildCount-1) * overviewModeBarSpacerWidthPx;
714     }
715 
layout(Launcher launcher)716     public void layout(Launcher launcher) {
717         FrameLayout.LayoutParams lp;
718         Resources res = launcher.getResources();
719         boolean hasVerticalBarLayout = isVerticalBarLayout();
720 
721         // Layout the search bar space
722         View searchBar = launcher.getSearchBar();
723         lp = (FrameLayout.LayoutParams) searchBar.getLayoutParams();
724         if (hasVerticalBarLayout) {
725             // Vertical search bar space
726             lp.gravity = Gravity.TOP | Gravity.LEFT;
727             lp.width = searchBarSpaceHeightPx;
728             lp.height = LayoutParams.WRAP_CONTENT;
729             searchBar.setPadding(
730                     0, 2 * edgeMarginPx, 0,
731                     2 * edgeMarginPx);
732 
733             LinearLayout targets = (LinearLayout) searchBar.findViewById(R.id.drag_target_bar);
734             targets.setOrientation(LinearLayout.VERTICAL);
735         } else {
736             // Horizontal search bar space
737             lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
738             lp.width = searchBarSpaceWidthPx;
739             lp.height = searchBarSpaceHeightPx;
740             searchBar.setPadding(
741                     2 * edgeMarginPx,
742                     getSearchBarTopOffset(),
743                     2 * edgeMarginPx, 0);
744         }
745         searchBar.setLayoutParams(lp);
746 
747         // Layout the voice proxy
748         View voiceButtonProxy = launcher.findViewById(R.id.voice_button_proxy);
749         if (voiceButtonProxy != null) {
750             if (hasVerticalBarLayout) {
751                 // TODO: MOVE THIS INTO SEARCH BAR MEASURE
752             } else {
753                 lp = (FrameLayout.LayoutParams) voiceButtonProxy.getLayoutParams();
754                 lp.gravity = Gravity.TOP | Gravity.END;
755                 lp.width = (widthPx - searchBarSpaceWidthPx) / 2 +
756                         2 * iconSizePx;
757                 lp.height = searchBarSpaceHeightPx;
758             }
759         }
760 
761         // Layout the workspace
762         PagedView workspace = (PagedView) launcher.findViewById(R.id.workspace);
763         lp = (FrameLayout.LayoutParams) workspace.getLayoutParams();
764         lp.gravity = Gravity.CENTER;
765         int orientation = isLandscape ? CellLayout.LANDSCAPE : CellLayout.PORTRAIT;
766         Rect padding = getWorkspacePadding(orientation);
767         workspace.setLayoutParams(lp);
768         workspace.setPadding(padding.left, padding.top, padding.right, padding.bottom);
769         workspace.setPageSpacing(getWorkspacePageSpacing(orientation));
770 
771         // Layout the hotseat
772         View hotseat = launcher.findViewById(R.id.hotseat);
773         lp = (FrameLayout.LayoutParams) hotseat.getLayoutParams();
774         if (hasVerticalBarLayout) {
775             // Vertical hotseat
776             lp.gravity = Gravity.END;
777             lp.width = hotseatBarHeightPx;
778             lp.height = LayoutParams.MATCH_PARENT;
779             hotseat.findViewById(R.id.layout).setPadding(0, 2 * edgeMarginPx, 0, 2 * edgeMarginPx);
780         } else if (isTablet()) {
781             // Pad the hotseat with the workspace padding calculated above
782             lp.gravity = Gravity.BOTTOM;
783             lp.width = LayoutParams.MATCH_PARENT;
784             lp.height = hotseatBarHeightPx;
785             hotseat.setPadding(edgeMarginPx + padding.left, 0,
786                     edgeMarginPx + padding.right,
787                     2 * edgeMarginPx);
788         } else {
789             // For phones, layout the hotseat without any bottom margin
790             // to ensure that we have space for the folders
791             lp.gravity = Gravity.BOTTOM;
792             lp.width = LayoutParams.MATCH_PARENT;
793             lp.height = hotseatBarHeightPx;
794             hotseat.findViewById(R.id.layout).setPadding(2 * edgeMarginPx, 0,
795                     2 * edgeMarginPx, 0);
796         }
797         hotseat.setLayoutParams(lp);
798 
799         // Layout the page indicators
800         View pageIndicator = launcher.findViewById(R.id.page_indicator);
801         if (pageIndicator != null) {
802             if (hasVerticalBarLayout) {
803                 // Hide the page indicators when we have vertical search/hotseat
804                 pageIndicator.setVisibility(View.GONE);
805             } else {
806                 // Put the page indicators above the hotseat
807                 lp = (FrameLayout.LayoutParams) pageIndicator.getLayoutParams();
808                 lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
809                 lp.width = LayoutParams.WRAP_CONTENT;
810                 lp.height = LayoutParams.WRAP_CONTENT;
811                 lp.bottomMargin = hotseatBarHeightPx;
812                 pageIndicator.setLayoutParams(lp);
813             }
814         }
815 
816         // Layout AllApps
817         AppsCustomizeTabHost host = (AppsCustomizeTabHost)
818                 launcher.findViewById(R.id.apps_customize_pane);
819         if (host != null) {
820             // Center the all apps page indicator
821             int pageIndicatorHeight = (int) (pageIndicatorHeightPx * Math.min(1f,
822                     (allAppsIconSizePx / DynamicGrid.DEFAULT_ICON_SIZE_PX)));
823             pageIndicator = host.findViewById(R.id.apps_customize_page_indicator);
824             if (pageIndicator != null) {
825                 LinearLayout.LayoutParams lllp = (LinearLayout.LayoutParams) pageIndicator.getLayoutParams();
826                 lllp.width = LayoutParams.WRAP_CONTENT;
827                 lllp.height = pageIndicatorHeight;
828                 pageIndicator.setLayoutParams(lllp);
829             }
830 
831             AppsCustomizePagedView pagedView = (AppsCustomizePagedView)
832                     host.findViewById(R.id.apps_customize_pane_content);
833 
834             FrameLayout fakePageContainer = (FrameLayout)
835                     host.findViewById(R.id.fake_page_container);
836             FrameLayout fakePage = (FrameLayout) host.findViewById(R.id.fake_page);
837 
838             padding = new Rect();
839             if (pagedView != null) {
840                 // Constrain the dimensions of all apps so that it does not span the full width
841                 int paddingLR = (availableWidthPx - (allAppsCellWidthPx * allAppsNumCols)) /
842                         (2 * (allAppsNumCols + 1));
843                 int paddingTB = (availableHeightPx - (allAppsCellHeightPx * allAppsNumRows)) /
844                         (2 * (allAppsNumRows + 1));
845                 paddingLR = Math.min(paddingLR, (int)((paddingLR + paddingTB) * 0.75f));
846                 paddingTB = Math.min(paddingTB, (int)((paddingLR + paddingTB) * 0.75f));
847                 int maxAllAppsWidth = (allAppsNumCols * (allAppsCellWidthPx + 2 * paddingLR));
848                 int gridPaddingLR = (availableWidthPx - maxAllAppsWidth) / 2;
849                 // Only adjust the side paddings on landscape phones, or tablets
850                 if ((isTablet() || isLandscape) && gridPaddingLR > (allAppsCellWidthPx / 4)) {
851                     padding.left = padding.right = gridPaddingLR;
852                 }
853 
854                 // The icons are centered, so we can't just offset by the page indicator height
855                 // because the empty space will actually be pageIndicatorHeight + paddingTB
856                 padding.bottom = Math.max(0, pageIndicatorHeight - paddingTB);
857 
858                 pagedView.setWidgetsPageIndicatorPadding(pageIndicatorHeight);
859                 fakePage.setBackground(res.getDrawable(R.drawable.quantum_panel));
860 
861                 // Horizontal padding for the whole paged view
862                 int pagedFixedViewPadding =
863                         res.getDimensionPixelSize(R.dimen.apps_customize_horizontal_padding);
864 
865                 padding.left += pagedFixedViewPadding;
866                 padding.right += pagedFixedViewPadding;
867 
868                 pagedView.setPadding(padding.left, padding.top, padding.right, padding.bottom);
869                 fakePageContainer.setPadding(padding.left, padding.top, padding.right, padding.bottom);
870 
871             }
872         }
873 
874         // Layout the Overview Mode
875         ViewGroup overviewMode = launcher.getOverviewPanel();
876         if (overviewMode != null) {
877             Rect r = getOverviewModeButtonBarRect();
878             lp = (FrameLayout.LayoutParams) overviewMode.getLayoutParams();
879             lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
880             lp.width = Math.min(availableWidthPx,
881                     calculateOverviewModeWidth(getVisibleChildCount(overviewMode)));
882             lp.height = r.height();
883             overviewMode.setLayoutParams(lp);
884         }
885     }
886 }
887