Home | History | Annotate | Download | only in launcher3
      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.PointF;
     27 import android.graphics.Rect;
     28 import android.util.DisplayMetrics;
     29 import android.util.TypedValue;
     30 import android.view.Gravity;
     31 import android.view.View;
     32 import android.view.ViewGroup.LayoutParams;
     33 import android.widget.FrameLayout;
     34 
     35 import java.util.ArrayList;
     36 import java.util.Collections;
     37 import java.util.Comparator;
     38 
     39 
     40 class DeviceProfileQuery {
     41     float widthDps;
     42     float heightDps;
     43     float value;
     44     PointF dimens;
     45 
     46     DeviceProfileQuery(float w, float h, float v) {
     47         widthDps = w;
     48         heightDps = h;
     49         value = v;
     50         dimens = new PointF(w, h);
     51     }
     52 }
     53 
     54 class DeviceProfile {
     55     String name;
     56     float minWidthDps;
     57     float minHeightDps;
     58     float numRows;
     59     float numColumns;
     60     float iconSize;
     61     float iconTextSize;
     62     float numHotseatIcons;
     63     float hotseatIconSize;
     64 
     65     boolean isLandscape;
     66     boolean isTablet;
     67     boolean isLargeTablet;
     68     boolean transposeLayoutWithOrientation;
     69 
     70     int desiredWorkspaceLeftRightMarginPx;
     71     int edgeMarginPx;
     72     Rect defaultWidgetPadding;
     73 
     74     int widthPx;
     75     int heightPx;
     76     int availableWidthPx;
     77     int availableHeightPx;
     78     int iconSizePx;
     79     int iconTextSizePx;
     80     int cellWidthPx;
     81     int cellHeightPx;
     82     int folderBackgroundOffset;
     83     int folderIconSizePx;
     84     int folderCellWidthPx;
     85     int folderCellHeightPx;
     86     int hotseatCellWidthPx;
     87     int hotseatCellHeightPx;
     88     int hotseatIconSizePx;
     89     int hotseatBarHeightPx;
     90     int hotseatAllAppsRank;
     91     int allAppsNumRows;
     92     int allAppsNumCols;
     93     int searchBarSpaceWidthPx;
     94     int searchBarSpaceMaxWidthPx;
     95     int searchBarSpaceHeightPx;
     96     int searchBarHeightPx;
     97     int pageIndicatorHeightPx;
     98 
     99     DeviceProfile(String n, float w, float h, float r, float c,
    100                   float is, float its, float hs, float his) {
    101         // Ensure that we have an odd number of hotseat items (since we need to place all apps)
    102         if (!AppsCustomizePagedView.DISABLE_ALL_APPS && hs % 2 == 0) {
    103             throw new RuntimeException("All Device Profiles must have an odd number of hotseat spaces");
    104         }
    105 
    106         name = n;
    107         minWidthDps = w;
    108         minHeightDps = h;
    109         numRows = r;
    110         numColumns = c;
    111         iconSize = is;
    112         iconTextSize = its;
    113         numHotseatIcons = hs;
    114         hotseatIconSize = his;
    115     }
    116 
    117     DeviceProfile(Context context,
    118                   ArrayList<DeviceProfile> profiles,
    119                   float minWidth, float minHeight,
    120                   int wPx, int hPx,
    121                   int awPx, int ahPx,
    122                   Resources resources) {
    123         DisplayMetrics dm = resources.getDisplayMetrics();
    124         ArrayList<DeviceProfileQuery> points =
    125                 new ArrayList<DeviceProfileQuery>();
    126         transposeLayoutWithOrientation =
    127                 resources.getBoolean(R.bool.hotseat_transpose_layout_with_orientation);
    128         minWidthDps = minWidth;
    129         minHeightDps = minHeight;
    130 
    131         ComponentName cn = new ComponentName(context.getPackageName(),
    132                 this.getClass().getName());
    133         defaultWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(context, cn, null);
    134         edgeMarginPx = resources.getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);
    135         desiredWorkspaceLeftRightMarginPx = 2 * edgeMarginPx;
    136         pageIndicatorHeightPx = resources.getDimensionPixelSize(R.dimen.dynamic_grid_page_indicator_height);
    137 
    138         // Interpolate the rows
    139         for (DeviceProfile p : profiles) {
    140             points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.numRows));
    141         }
    142         numRows = Math.round(invDistWeightedInterpolate(minWidth, minHeight, points));
    143         // Interpolate the columns
    144         points.clear();
    145         for (DeviceProfile p : profiles) {
    146             points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.numColumns));
    147         }
    148         numColumns = Math.round(invDistWeightedInterpolate(minWidth, minHeight, points));
    149         // Interpolate the icon size
    150         points.clear();
    151         for (DeviceProfile p : profiles) {
    152             points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.iconSize));
    153         }
    154         iconSize = invDistWeightedInterpolate(minWidth, minHeight, points);
    155         iconSizePx = DynamicGrid.pxFromDp(iconSize, dm);
    156 
    157         // Interpolate the icon text size
    158         points.clear();
    159         for (DeviceProfile p : profiles) {
    160             points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.iconTextSize));
    161         }
    162         iconTextSize = invDistWeightedInterpolate(minWidth, minHeight, points);
    163         iconTextSizePx = DynamicGrid.pxFromSp(iconTextSize, dm);
    164 
    165         // Interpolate the hotseat size
    166         points.clear();
    167         for (DeviceProfile p : profiles) {
    168             points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.numHotseatIcons));
    169         }
    170         numHotseatIcons = Math.round(invDistWeightedInterpolate(minWidth, minHeight, points));
    171         // Interpolate the hotseat icon size
    172         points.clear();
    173         for (DeviceProfile p : profiles) {
    174             points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.hotseatIconSize));
    175         }
    176         // Hotseat
    177         hotseatIconSize = invDistWeightedInterpolate(minWidth, minHeight, points);
    178         hotseatIconSizePx = DynamicGrid.pxFromDp(hotseatIconSize, dm);
    179         hotseatAllAppsRank = (int) (numColumns / 2);
    180 
    181         // Calculate other vars based on Configuration
    182         updateFromConfiguration(resources, wPx, hPx, awPx, ahPx);
    183 
    184         // Search Bar
    185         searchBarSpaceMaxWidthPx = resources.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_max_width);
    186         searchBarHeightPx = resources.getDimensionPixelSize(R.dimen.dynamic_grid_search_bar_height);
    187         searchBarSpaceWidthPx = Math.min(searchBarSpaceMaxWidthPx, widthPx);
    188         searchBarSpaceHeightPx = searchBarHeightPx + 2 * edgeMarginPx;
    189 
    190         // Calculate the actual text height
    191         Paint textPaint = new Paint();
    192         textPaint.setTextSize(iconTextSizePx);
    193         FontMetrics fm = textPaint.getFontMetrics();
    194         cellWidthPx = iconSizePx;
    195         cellHeightPx = iconSizePx + (int) Math.ceil(fm.bottom - fm.top);
    196 
    197         // At this point, if the cells do not fit into the available height, then we need
    198         // to shrink the icon size
    199         /*
    200         Rect padding = getWorkspacePadding(isLandscape ?
    201                 CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
    202         int h = (int) (numRows * cellHeightPx) + padding.top + padding.bottom;
    203         if (h > availableHeightPx) {
    204             float delta = h - availableHeightPx;
    205             int deltaPx = (int) Math.ceil(delta / numRows);
    206             iconSizePx -= deltaPx;
    207             iconSize = DynamicGrid.dpiFromPx(iconSizePx, dm);
    208             cellWidthPx = iconSizePx;
    209             cellHeightPx = iconSizePx + (int) Math.ceil(fm.bottom - fm.top);
    210         }
    211         */
    212 
    213         // Hotseat
    214         hotseatBarHeightPx = iconSizePx + 4 * edgeMarginPx;
    215         hotseatCellWidthPx = iconSizePx;
    216         hotseatCellHeightPx = iconSizePx;
    217 
    218         // Folder
    219         folderCellWidthPx = cellWidthPx + 3 * edgeMarginPx;
    220         folderCellHeightPx = cellHeightPx + (int) ((3f/2f) * edgeMarginPx);
    221         folderBackgroundOffset = -edgeMarginPx;
    222         folderIconSizePx = iconSizePx + 2 * -folderBackgroundOffset;
    223     }
    224 
    225     void updateFromConfiguration(Resources resources, int wPx, int hPx,
    226                                  int awPx, int ahPx) {
    227         isLandscape = (resources.getConfiguration().orientation ==
    228                 Configuration.ORIENTATION_LANDSCAPE);
    229         isTablet = resources.getBoolean(R.bool.is_tablet);
    230         isLargeTablet = resources.getBoolean(R.bool.is_large_tablet);
    231         widthPx = wPx;
    232         heightPx = hPx;
    233         availableWidthPx = awPx;
    234         availableHeightPx = ahPx;
    235 
    236         Rect padding = getWorkspacePadding(isLandscape ?
    237                 CellLayout.LANDSCAPE : CellLayout.PORTRAIT);
    238         int pageIndicatorOffset =
    239             resources.getDimensionPixelSize(R.dimen.apps_customize_page_indicator_offset);
    240         if (isLandscape) {
    241             allAppsNumRows = (availableHeightPx - pageIndicatorOffset - 4 * edgeMarginPx) /
    242                     (iconSizePx + iconTextSizePx + 2 * edgeMarginPx);
    243         } else {
    244             allAppsNumRows = (int) numRows + 1;
    245         }
    246         allAppsNumCols = (availableWidthPx - padding.left - padding.right - 2 * edgeMarginPx) /
    247                 (iconSizePx + 2 * edgeMarginPx);
    248     }
    249 
    250     private float dist(PointF p0, PointF p1) {
    251         return (float) Math.sqrt((p1.x - p0.x)*(p1.x-p0.x) +
    252                 (p1.y-p0.y)*(p1.y-p0.y));
    253     }
    254 
    255     private float weight(PointF a, PointF b,
    256                         float pow) {
    257         float d = dist(a, b);
    258         if (d == 0f) {
    259             return Float.POSITIVE_INFINITY;
    260         }
    261         return (float) (1f / Math.pow(d, pow));
    262     }
    263 
    264     private float invDistWeightedInterpolate(float width, float height,
    265                 ArrayList<DeviceProfileQuery> points) {
    266         float sum = 0;
    267         float weights = 0;
    268         float pow = 5;
    269         float kNearestNeighbors = 3;
    270         final PointF xy = new PointF(width, height);
    271 
    272         ArrayList<DeviceProfileQuery> pointsByNearness = points;
    273         Collections.sort(pointsByNearness, new Comparator<DeviceProfileQuery>() {
    274             public int compare(DeviceProfileQuery a, DeviceProfileQuery b) {
    275                 return (int) (dist(xy, a.dimens) - dist(xy, b.dimens));
    276             }
    277         });
    278 
    279         for (int i = 0; i < pointsByNearness.size(); ++i) {
    280             DeviceProfileQuery p = pointsByNearness.get(i);
    281             if (i < kNearestNeighbors) {
    282                 float w = weight(xy, p.dimens, pow);
    283                 if (w == Float.POSITIVE_INFINITY) {
    284                     return p.value;
    285                 }
    286                 weights += w;
    287             }
    288         }
    289 
    290         for (int i = 0; i < pointsByNearness.size(); ++i) {
    291             DeviceProfileQuery p = pointsByNearness.get(i);
    292             if (i < kNearestNeighbors) {
    293                 float w = weight(xy, p.dimens, pow);
    294                 sum += w * p.value / weights;
    295             }
    296         }
    297 
    298         return sum;
    299     }
    300 
    301     Rect getWorkspacePadding(int orientation) {
    302         Rect padding = new Rect();
    303         if (orientation == CellLayout.LANDSCAPE &&
    304                 transposeLayoutWithOrientation) {
    305             // Pad the left and right of the workspace with search/hotseat bar sizes
    306             padding.set(searchBarSpaceHeightPx, edgeMarginPx,
    307                     hotseatBarHeightPx, edgeMarginPx);
    308         } else {
    309             if (isTablet()) {
    310                 // Pad the left and right of the workspace to ensure consistent spacing
    311                 // between all icons
    312                 int width = (orientation == CellLayout.LANDSCAPE)
    313                         ? Math.max(widthPx, heightPx)
    314                         : Math.min(widthPx, heightPx);
    315                 // XXX: If the icon size changes across orientations, we will have to take
    316                 //      that into account here too.
    317                 int gap = (int) ((width - 2 * edgeMarginPx -
    318                         (numColumns * cellWidthPx)) / (2 * (numColumns + 1)));
    319                 padding.set(edgeMarginPx + gap,
    320                         searchBarSpaceHeightPx,
    321                         edgeMarginPx + gap,
    322                         hotseatBarHeightPx + pageIndicatorHeightPx);
    323             } else {
    324                 // Pad the top and bottom of the workspace with search/hotseat bar sizes
    325                 padding.set(desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.left,
    326                         searchBarSpaceHeightPx,
    327                         desiredWorkspaceLeftRightMarginPx - defaultWidgetPadding.right,
    328                         hotseatBarHeightPx + pageIndicatorHeightPx);
    329             }
    330         }
    331         return padding;
    332     }
    333 
    334     // The rect returned will be extended to below the system ui that covers the workspace
    335     Rect getHotseatRect() {
    336         if (isVerticalBarLayout()) {
    337             return new Rect(availableWidthPx - hotseatBarHeightPx, 0,
    338                     Integer.MAX_VALUE, availableHeightPx);
    339         } else {
    340             return new Rect(0, availableHeightPx - hotseatBarHeightPx,
    341                     availableWidthPx, Integer.MAX_VALUE);
    342         }
    343     }
    344 
    345     int calculateCellWidth(int width, int countX) {
    346         return width / countX;
    347     }
    348     int calculateCellHeight(int height, int countY) {
    349         return height / countY;
    350     }
    351 
    352     boolean isPhone() {
    353         return !isTablet && !isLargeTablet;
    354     }
    355     boolean isTablet() {
    356         return isTablet;
    357     }
    358     boolean isLargeTablet() {
    359         return isLargeTablet;
    360     }
    361 
    362     boolean isVerticalBarLayout() {
    363         return isLandscape && transposeLayoutWithOrientation;
    364     }
    365 
    366     public void layout(Launcher launcher) {
    367         FrameLayout.LayoutParams lp;
    368         Resources res = launcher.getResources();
    369         boolean hasVerticalBarLayout = isVerticalBarLayout();
    370 
    371         // Layout the search bar space
    372         View searchBar = launcher.getSearchBar();
    373         lp = (FrameLayout.LayoutParams) searchBar.getLayoutParams();
    374         if (hasVerticalBarLayout) {
    375             // Vertical search bar
    376             lp.gravity = Gravity.TOP | Gravity.LEFT;
    377             lp.width = searchBarSpaceHeightPx;
    378             lp.height = LayoutParams.MATCH_PARENT;
    379             searchBar.setPadding(
    380                     0, 2 * edgeMarginPx, 0,
    381                     2 * edgeMarginPx);
    382         } else {
    383             // Horizontal search bar
    384             lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
    385             lp.width = searchBarSpaceWidthPx;
    386             lp.height = searchBarSpaceHeightPx;
    387             searchBar.setPadding(
    388                     2 * edgeMarginPx,
    389                     2 * edgeMarginPx,
    390                     2 * edgeMarginPx, 0);
    391         }
    392         searchBar.setLayoutParams(lp);
    393 
    394         // Layout the search bar
    395         View qsbBar = launcher.getQsbBar();
    396         LayoutParams vglp = qsbBar.getLayoutParams();
    397         vglp.width = LayoutParams.MATCH_PARENT;
    398         vglp.height = LayoutParams.MATCH_PARENT;
    399         qsbBar.setLayoutParams(vglp);
    400 
    401         // Layout the voice proxy
    402         View voiceButtonProxy = launcher.findViewById(R.id.voice_button_proxy);
    403         if (voiceButtonProxy != null) {
    404             if (hasVerticalBarLayout) {
    405                 // TODO: MOVE THIS INTO SEARCH BAR MEASURE
    406             } else {
    407                 lp = (FrameLayout.LayoutParams) voiceButtonProxy.getLayoutParams();
    408                 lp.gravity = Gravity.TOP | Gravity.END;
    409                 lp.width = (widthPx - searchBarSpaceWidthPx) / 2 +
    410                         2 * iconSizePx;
    411                 lp.height = searchBarSpaceHeightPx;
    412             }
    413         }
    414 
    415         // Layout the workspace
    416         View workspace = launcher.findViewById(R.id.workspace);
    417         lp = (FrameLayout.LayoutParams) workspace.getLayoutParams();
    418         lp.gravity = Gravity.CENTER;
    419         Rect padding = getWorkspacePadding(isLandscape
    420                 ? CellLayout.LANDSCAPE
    421                 : CellLayout.PORTRAIT);
    422         workspace.setPadding(padding.left, padding.top,
    423                 padding.right, padding.bottom);
    424         workspace.setLayoutParams(lp);
    425 
    426         // Layout the hotseat
    427         View hotseat = launcher.findViewById(R.id.hotseat);
    428         lp = (FrameLayout.LayoutParams) hotseat.getLayoutParams();
    429         if (hasVerticalBarLayout) {
    430             // Vertical hotseat
    431             lp.gravity = Gravity.RIGHT;
    432             lp.width = hotseatBarHeightPx;
    433             lp.height = LayoutParams.MATCH_PARENT;
    434             hotseat.setPadding(0, 2 * edgeMarginPx,
    435                     2 * edgeMarginPx, 2 * edgeMarginPx);
    436         } else if (isTablet()) {
    437             // Pad the hotseat with the grid gap calculated above
    438             int gridGap = (int) ((widthPx - 2 * edgeMarginPx -
    439                     (numColumns * cellWidthPx)) / (2 * (numColumns + 1)));
    440             int gridWidth = (int) ((numColumns * cellWidthPx) +
    441                     ((numColumns - 1) * gridGap));
    442             int hotseatGap = (int) Math.max(0,
    443                     (gridWidth - (numHotseatIcons * hotseatCellWidthPx))
    444                             / (numHotseatIcons - 1));
    445             lp.gravity = Gravity.BOTTOM;
    446             lp.width = LayoutParams.MATCH_PARENT;
    447             lp.height = hotseatBarHeightPx;
    448             hotseat.setPadding(2 * edgeMarginPx + gridGap + hotseatGap, 0,
    449                     2 * edgeMarginPx + gridGap + hotseatGap,
    450                     2 * edgeMarginPx);
    451         } else {
    452             // For phones, layout the hotseat without any bottom margin
    453             // to ensure that we have space for the folders
    454             lp.gravity = Gravity.BOTTOM;
    455             lp.width = LayoutParams.MATCH_PARENT;
    456             lp.height = hotseatBarHeightPx;
    457             hotseat.findViewById(R.id.layout).setPadding(2 * edgeMarginPx, 0,
    458                     2 * edgeMarginPx, 0);
    459         }
    460         hotseat.setLayoutParams(lp);
    461 
    462         // Layout the page indicators
    463         View pageIndicator = launcher.findViewById(R.id.page_indicator);
    464         if (pageIndicator != null) {
    465             if (hasVerticalBarLayout) {
    466                 // Hide the page indicators when we have vertical search/hotseat
    467                 pageIndicator.setVisibility(View.GONE);
    468             } else {
    469                 // Put the page indicators above the hotseat
    470                 lp = (FrameLayout.LayoutParams) pageIndicator.getLayoutParams();
    471                 lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
    472                 lp.width = LayoutParams.WRAP_CONTENT;
    473                 lp.height = LayoutParams.WRAP_CONTENT;
    474                 lp.bottomMargin = hotseatBarHeightPx;
    475                 pageIndicator.setLayoutParams(lp);
    476             }
    477         }
    478     }
    479 }
    480 
    481 public class DynamicGrid {
    482     @SuppressWarnings("unused")
    483     private static final String TAG = "DynamicGrid";
    484 
    485     private DeviceProfile mProfile;
    486     private float mMinWidth;
    487     private float mMinHeight;
    488 
    489     public static float dpiFromPx(int size, DisplayMetrics metrics){
    490         float densityRatio = (float) metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT;
    491         return (size / densityRatio);
    492     }
    493     public static int pxFromDp(float size, DisplayMetrics metrics) {
    494         return (int) Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
    495                 size, metrics));
    496     }
    497     public static int pxFromSp(float size, DisplayMetrics metrics) {
    498         return (int) Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
    499                 size, metrics));
    500     }
    501 
    502     public DynamicGrid(Context context, Resources resources,
    503                        int minWidthPx, int minHeightPx,
    504                        int widthPx, int heightPx,
    505                        int awPx, int ahPx) {
    506         DisplayMetrics dm = resources.getDisplayMetrics();
    507         ArrayList<DeviceProfile> deviceProfiles =
    508                 new ArrayList<DeviceProfile>();
    509         boolean hasAA = !AppsCustomizePagedView.DISABLE_ALL_APPS;
    510         // Our phone profiles include the bar sizes in each orientation
    511         deviceProfiles.add(new DeviceProfile("Super Short Stubby",
    512                 255, 300,  2, 3,  48, 13, (hasAA ? 5 : 4), 48));
    513         deviceProfiles.add(new DeviceProfile("Shorter Stubby",
    514                 255, 400,  3, 3,  48, 13, (hasAA ? 5 : 4), 48));
    515         deviceProfiles.add(new DeviceProfile("Short Stubby",
    516                 275, 420,  3, 4,  48, 13, (hasAA ? 5 : 4), 48));
    517         deviceProfiles.add(new DeviceProfile("Stubby",
    518                 255, 450,  3, 4,  48, 13, (hasAA ? 5 : 4), 48));
    519         deviceProfiles.add(new DeviceProfile("Nexus S",
    520                 296, 491.33f,  4, 4,  48, 13, (hasAA ? 5 : 4), 48));
    521         deviceProfiles.add(new DeviceProfile("Nexus 4",
    522                 359, 518,  4, 4,  60, 13, (hasAA ? 5 : 4), 56));
    523         // The tablet profile is odd in that the landscape orientation
    524         // also includes the nav bar on the side
    525         deviceProfiles.add(new DeviceProfile("Nexus 7",
    526                 575, 904,  6, 6,  72, 14.4f,  7, 60));
    527         // Larger tablet profiles always have system bars on the top & bottom
    528         deviceProfiles.add(new DeviceProfile("Nexus 10",
    529                 727, 1207,  5, 8,  80, 14.4f,  9, 64));
    530         /*
    531         deviceProfiles.add(new DeviceProfile("Nexus 7",
    532                 600, 960,  5, 5,  72, 14.4f,  5, 60));
    533         deviceProfiles.add(new DeviceProfile("Nexus 10",
    534                 800, 1280,  5, 5,  80, 14.4f, (hasAA ? 7 : 6), 64));
    535          */
    536         deviceProfiles.add(new DeviceProfile("20-inch Tablet",
    537                 1527, 2527,  7, 7,  100, 20,  7, 72));
    538         mMinWidth = dpiFromPx(minWidthPx, dm);
    539         mMinHeight = dpiFromPx(minHeightPx, dm);
    540         mProfile = new DeviceProfile(context, deviceProfiles,
    541                 mMinWidth, mMinHeight,
    542                 widthPx, heightPx,
    543                 awPx, ahPx,
    544                 resources);
    545     }
    546 
    547     DeviceProfile getDeviceProfile() {
    548         return mProfile;
    549     }
    550 
    551     public String toString() {
    552         return "-------- DYNAMIC GRID ------- \n" +
    553                 "Wd: " + mProfile.minWidthDps + ", Hd: " + mProfile.minHeightDps +
    554                 ", W: " + mProfile.widthPx + ", H: " + mProfile.heightPx +
    555                 " [r: " + mProfile.numRows + ", c: " + mProfile.numColumns +
    556                 ", is: " + mProfile.iconSizePx + ", its: " + mProfile.iconTextSize +
    557                 ", cw: " + mProfile.cellWidthPx + ", ch: " + mProfile.cellHeightPx +
    558                 ", hc: " + mProfile.numHotseatIcons + ", his: " + mProfile.hotseatIconSizePx + "]";
    559     }
    560 }
    561