1 /*
2  * Copyright (C) 2017 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.wallpaper.util;
17 
18 import android.app.Activity;
19 import android.content.Context;
20 import android.content.res.Resources;
21 import android.graphics.Point;
22 import android.graphics.drawable.GradientDrawable;
23 import android.util.DisplayMetrics;
24 import android.view.Display;
25 import android.view.View;
26 import android.view.WindowManager;
27 
28 import androidx.annotation.NonNull;
29 
30 import com.android.systemui.shared.system.QuickStepContract;
31 import com.android.wallpaper.R;
32 import com.android.wallpaper.module.FormFactorChecker;
33 import com.android.wallpaper.module.FormFactorChecker.FormFactor;
34 import com.android.wallpaper.module.InjectorProvider;
35 
36 
37 /**
38  * Simple utility class that calculates various sizes relative to the display or current
39  * configuration.
40  */
41 public class SizeCalculator {
42     private static final int COLUMN_COUNT_THRESHOLD_DP = 732;
43 
44     /**
45      * The number of columns for a "fewer columns" configuration of the category tiles grid.
46      */
47     private static final int CATEGORY_FEWER_COLUMNS = 3;
48 
49     /**
50      * The number of columns for a "more columns" configuration of the category tiles grid.
51      */
52     private static final int CATEGORY_MORE_COLUMNS = 3;
53 
54     /**
55      * The number of columns for a "fewer columns" configuration of the featured category tiles
56      * grid.
57      */
58     private static final int FEATURED_CATEGORY_FEWER_COLUMNS = 2;
59 
60     /**
61      * The number of columns for a "more columns" configuration of the featured category tiles grid.
62      */
63     private static final int FEATURED_CATEGORY_MORE_COLUMNS = 2;
64 
65     /**
66      * The number of columns for a "fewer columns" configuration of the individual wallpaper tiles
67      * grid.
68      */
69     private static final int INDIVIDUAL_FEWER_COLUMNS = 3;
70 
71     /**
72      * The number of columns for a "more columns" configuration of the individual wallpaper tiles
73      * grid.
74      */
75     private static final int INDIVIDUAL_MORE_COLUMNS = 4;
76 
77     /**
78      * The number of columns for a "fewer columns" configuration of the featured individual
79      * wallpaper tiles grid.
80      */
81     private static final int FEATURED_INDIVIDUAL_FEWER_COLUMNS = 2;
82 
83     /**
84      * The number of columns for a "more columns" configuration of the featured individual wallpaper
85      * tiles grid.
86      */
87     private static final int FEATURED_INDIVIDUAL_MORE_COLUMNS = 2;
88 
89     // Suppress default constructor for noninstantiability.
SizeCalculator()90     private SizeCalculator() {
91         throw new AssertionError("Can't initialize a SizeCalculator.");
92     }
93 
94     /**
95      * Returns the number of columns for a grid of category tiles. Selects from fewer and more
96      * columns based on the width of the activity.
97      */
getNumCategoryColumns(@onNull Activity activity)98     public static int getNumCategoryColumns(@NonNull Activity activity) {
99         int windowWidthPx = getActivityWindowWidthPx(activity);
100         return getNumCategoryColumns(activity, windowWidthPx);
101     }
102 
103     /**
104      * Returns the number of columns for a grid of individual tiles. Selects from fewer and more
105      * columns based on the width of the activity.
106      */
getNumIndividualColumns(@onNull Activity activity)107     public static int getNumIndividualColumns(@NonNull Activity activity) {
108         int windowWidthPx = getActivityWindowWidthPx(activity);
109         return getNumIndividualColumns(activity, windowWidthPx);
110     }
111 
112     /**
113      * Returns the number of columns for a grid of featured individual tiles. Selects from fewer and
114      * more columns based on the width of the activity.
115      */
getNumFeaturedIndividualColumns(@onNull Activity activity)116     public static int getNumFeaturedIndividualColumns(@NonNull Activity activity) {
117         int windowWidthPx = getActivityWindowWidthPx(activity);
118         return getNumFeaturedIndividualColumns(activity, windowWidthPx);
119     }
120 
getNumCategoryColumns(Activity activity, int windowWidthPx)121     private static int getNumCategoryColumns(Activity activity, int windowWidthPx) {
122         return getNumColumns(activity, windowWidthPx, CATEGORY_FEWER_COLUMNS,
123                 CATEGORY_MORE_COLUMNS);
124     }
125 
getNumFeaturedCategoryColumns(Activity activity, int windowWidthPx)126     private static int getNumFeaturedCategoryColumns(Activity activity, int windowWidthPx) {
127         return getNumColumns(activity, windowWidthPx, FEATURED_CATEGORY_FEWER_COLUMNS,
128                 FEATURED_CATEGORY_MORE_COLUMNS);
129     }
130 
getNumIndividualColumns(Activity activity, int windowWidthPx)131     private static int getNumIndividualColumns(Activity activity, int windowWidthPx) {
132         return getNumColumns(
133                 activity, windowWidthPx, INDIVIDUAL_FEWER_COLUMNS, INDIVIDUAL_MORE_COLUMNS);
134     }
135 
getNumFeaturedIndividualColumns(Activity activity, int windowWidthPx)136     private static int getNumFeaturedIndividualColumns(Activity activity, int windowWidthPx) {
137         return getNumColumns(activity, windowWidthPx, FEATURED_INDIVIDUAL_FEWER_COLUMNS,
138                 FEATURED_INDIVIDUAL_MORE_COLUMNS);
139     }
140 
getNumColumns( Context context, int windowWidthPx, int fewerCount, int moreCount)141     private static int getNumColumns(
142             Context context, int windowWidthPx, int fewerCount, int moreCount) {
143         WindowManager windowManager = (WindowManager)
144                 context.getSystemService(Context.WINDOW_SERVICE);
145         Display display = windowManager.getDefaultDisplay();
146 
147         DisplayMetrics metrics = DisplayMetricsRetriever.getInstance()
148                 .getDisplayMetrics(context.getResources(), display);
149 
150         // Columns should be based on the size of the window, not the size of the display.
151         int windowWidthDp = (int) (windowWidthPx / metrics.density);
152 
153         if (windowWidthDp < COLUMN_COUNT_THRESHOLD_DP) {
154             return fewerCount;
155         } else {
156             return moreCount;
157         }
158     }
159 
160     /**
161      * Returns the size of a category grid tile in px.
162      */
getCategoryTileSize(@onNull Activity activity)163     public static Point getCategoryTileSize(@NonNull Activity activity) {
164         Resources res = activity.getResources();
165         int windowWidthPx = getActivityWindowWidthPx(activity);
166 
167         int columnCount = getNumCategoryColumns(activity, windowWidthPx);
168         return getSquareTileSize(columnCount, windowWidthPx,
169                 res.getDimensionPixelSize(R.dimen.grid_item_category_padding_horizontal),
170                 res.getDimensionPixelSize(R.dimen.category_grid_edge_space));
171     }
172 
173     /**
174      * Returns the size of a featured category grid tile in px.
175      */
getFeaturedCategoryTileSize(@onNull Activity activity)176     public static Point getFeaturedCategoryTileSize(@NonNull Activity activity) {
177         Resources res = activity.getResources();
178         int windowWidthPx = getActivityWindowWidthPx(activity);
179 
180         int columnCount = getNumFeaturedCategoryColumns(activity, windowWidthPx);
181         return getSquareTileSize(columnCount, windowWidthPx,
182                 res.getDimensionPixelSize(R.dimen.grid_item_category_padding_horizontal),
183                 res.getDimensionPixelSize(R.dimen.category_grid_edge_space));
184     }
185 
186     /**
187      * Returns the size of an individual grid tile for the given activity in px.
188      */
getIndividualTileSize(@onNull Activity activity)189     public static Point getIndividualTileSize(@NonNull Activity activity) {
190         Resources res = activity.getResources();
191         int windowWidthPx = getActivityWindowWidthPx(activity);
192 
193         int columnCount = getNumIndividualColumns(activity, windowWidthPx);
194         return getSquareTileSize(columnCount, windowWidthPx,
195                 res.getDimensionPixelSize(R.dimen.grid_item_individual_padding_horizontal),
196                 res.getDimensionPixelSize(R.dimen.wallpaper_grid_edge_space));
197     }
198 
199     /**
200      * Returns the size of a featured individual grid tile for the given activity in px.
201      */
getFeaturedIndividualTileSize(@onNull Activity activity)202     public static Point getFeaturedIndividualTileSize(@NonNull Activity activity) {
203         Resources res = activity.getResources();
204         int windowWidthPx = getActivityWindowWidthPx(activity);
205 
206         int columnCount = getNumFeaturedIndividualColumns(activity, windowWidthPx);
207         return getSquareTileSize(columnCount, windowWidthPx,
208                 res.getDimensionPixelSize(R.dimen.grid_item_featured_individual_padding_horizontal),
209                 res.getDimensionPixelSize(R.dimen.featured_wallpaper_grid_edge_space));
210     }
211 
212     /**
213      * Returns a suggested thumbnail tile size for images that may be presented either as a
214      * category or individual tile on any-sized activity on the device. This size matches the
215      * individual tile size when an activity takes up the entire screen's width.
216      */
getSuggestedThumbnailSize(@onNull Context appContext)217     public static Point getSuggestedThumbnailSize(@NonNull Context appContext) {
218         // Category tiles are larger than individual tiles, so get the number of columns for
219         // categories and then calculate a tile size for when the app window takes up the entire
220         // display.
221         int windowWidthPx = getDeviceDisplayWidthPx(appContext);
222         int columnCount = getNumColumns(
223                 appContext, windowWidthPx, INDIVIDUAL_FEWER_COLUMNS, INDIVIDUAL_MORE_COLUMNS);
224         return getTileSize(appContext, columnCount, windowWidthPx);
225     }
226 
227     /**
228      * Returns the corner radius to use in a wallpaper preview view so that it's proportional
229      * to the screen's corner radius
230      */
getPreviewCornerRadius(@onNull Activity activity, int previewWidth)231     public static float getPreviewCornerRadius(@NonNull Activity activity, int previewWidth) {
232         Point screenSize = ScreenSizeCalculator.getInstance().getScreenSize(
233                 activity.getWindowManager().getDefaultDisplay());
234 
235         return QuickStepContract.getWindowCornerRadius(activity)
236                 / ((float) screenSize.x / previewWidth);
237     }
238 
239     /**
240      * Returns the size of a grid tile with the given "fewer" count and "more" count, on the given
241      * display. The size is determined by these counts and by the aspect ratio of the display and is
242      * in units of px.
243      */
getTileSize(Context context, int columnCount, int windowWidthPx)244     private static Point getTileSize(Context context, int columnCount, int windowWidthPx) {
245         WindowManager windowManager = (WindowManager)
246                 context.getSystemService(Context.WINDOW_SERVICE);
247         Display display = windowManager.getDefaultDisplay();
248         Point screenSizePx = ScreenSizeCalculator.getInstance().getScreenSize(display);
249 
250         FormFactorChecker formFactorChecker =
251                 InjectorProvider.getInjector().getFormFactorChecker(context);
252         @FormFactor int formFactor = formFactorChecker.getFormFactor();
253 
254         int gridPaddingPx;
255         Resources res = context.getResources();
256         if (formFactor == FormFactorChecker.FORM_FACTOR_MOBILE) {
257             gridPaddingPx = res.getDimensionPixelSize(R.dimen.grid_padding);
258         } else { // DESKTOP
259             gridPaddingPx = res.getDimensionPixelSize(R.dimen.grid_padding_desktop);
260         }
261 
262         // Note: don't need to multiply by density because getting the dimension from resources
263         // already does that.
264         int guttersWidthPx = (columnCount + 1) * gridPaddingPx;
265         int availableWidthPx = windowWidthPx - guttersWidthPx;
266 
267         int widthPx = Math.round((float) availableWidthPx / columnCount);
268         int heightPx = Math.round(((float) availableWidthPx / columnCount)
269                 //* screenSizePx.y / screenSizePx.x);
270                 * res.getDimensionPixelSize(R.dimen.grid_tile_aspect_height)
271                 / res.getDimensionPixelSize(R.dimen.grid_tile_aspect_width));
272         return new Point(widthPx, heightPx);
273     }
274 
275     /**
276      * Returns the size of a grid tile with the given "fewer" count and "more" count, on the given
277      * display. The size is determined by these counts with the aspect ratio of 1:1 and is in units
278      * of px.
279      */
getSquareTileSize(int columnCount, int windowWidthPx, int gridPaddingPx, int gridEdgeSpacePx)280     private static Point getSquareTileSize(int columnCount, int windowWidthPx, int gridPaddingPx,
281             int gridEdgeSpacePx) {
282         int availableWidthPx = windowWidthPx
283                 - gridPaddingPx * 2 * columnCount // Item's left and right padding * column count
284                 - gridEdgeSpacePx * 2; // Grid view's left and right edge's space
285         int widthPx = Math.round((float) availableWidthPx / columnCount);
286 
287         return new Point(widthPx, widthPx);
288     }
289 
290     /**
291      * Returns the available width of the activity window in pixels.
292      */
getActivityWindowWidthPx(Activity activity)293     private static int getActivityWindowWidthPx(Activity activity) {
294         Display display = activity.getWindowManager().getDefaultDisplay();
295 
296         Point outPoint = new Point();
297         display.getSize(outPoint);
298 
299         return outPoint.x;
300     }
301 
302     /**
303      * Returns the available width of the device's default display in pixels.
304      */
getDeviceDisplayWidthPx(Context appContext)305     private static int getDeviceDisplayWidthPx(Context appContext) {
306         WindowManager windowManager =
307                 (WindowManager) appContext.getSystemService(Context.WINDOW_SERVICE);
308         Display defaultDisplay = windowManager.getDefaultDisplay();
309 
310         Point outPoint = new Point();
311         defaultDisplay.getSize(outPoint);
312 
313         return outPoint.x;
314     }
315 
316     /**
317      * Adjusts the corner radius of the given view by doubling their current values
318      *
319      * @param view whose background is set to a GradientDrawable
320      */
adjustBackgroundCornerRadius(View view)321     public static void adjustBackgroundCornerRadius(View view) {
322         GradientDrawable background = (GradientDrawable) view.getBackground();
323         // Using try/catch because currently GradientDrawable has a bug where when the radii array
324         // is null, instead of getCornerRadii returning null, it throws NPE.
325         try {
326             float[] radii = background.getCornerRadii();
327             if (radii == null) {
328                 return;
329             }
330             for (int i = 0; i < radii.length; i++) {
331                 radii[i] *= 2f;
332             }
333             background = ((GradientDrawable) background.mutate());
334             background.setCornerRadii(radii);
335             view.setBackground(background);
336         } catch (NullPointerException e) {
337             //Ignore in this case, since it means the radius was 0.
338         }
339     }
340 }
341