blob: 92fdbde85ba7459deedfd353bd022604099936d6 [file] [log] [blame]
Adam Cohen2e6da152015-05-06 11:42:25 -07001/*
2 * Copyright (C) 2015 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
17package com.android.launcher3;
18
Sunny Goyalc6205602015-05-21 20:46:33 -070019import android.annotation.TargetApi;
Adam Cohen2e6da152015-05-06 11:42:25 -070020import android.content.Context;
21import android.graphics.Point;
22import android.graphics.PointF;
Sunny Goyalc6205602015-05-21 20:46:33 -070023import android.os.Build;
Adam Cohen2e6da152015-05-06 11:42:25 -070024import android.util.DisplayMetrics;
Adam Cohen2e6da152015-05-06 11:42:25 -070025import android.view.Display;
26import android.view.WindowManager;
Adam Cohen2e6da152015-05-06 11:42:25 -070027import com.android.launcher3.util.Thunk;
Adam Cohen2e6da152015-05-06 11:42:25 -070028import java.util.ArrayList;
29import java.util.Collections;
30import java.util.Comparator;
31
32public class InvariantDeviceProfile {
Adam Cohen2e6da152015-05-06 11:42:25 -070033
34 // This is a static that we use for the default icon size on a 4/5-inch phone
Sunny Goyalc6205602015-05-21 20:46:33 -070035 private static float DEFAULT_ICON_SIZE_DP = 60;
Adam Cohen2e6da152015-05-06 11:42:25 -070036
Sunny Goyalc6205602015-05-21 20:46:33 -070037 private static final ArrayList<InvariantDeviceProfile> sDeviceProfiles = new ArrayList<>();
Adam Cohen2e6da152015-05-06 11:42:25 -070038 static {
39 sDeviceProfiles.add(new InvariantDeviceProfile("Super Short Stubby",
40 255, 300, 2, 3, 2, 3, 48, 13, 3, 48, R.xml.default_workspace_4x4));
41 sDeviceProfiles.add(new InvariantDeviceProfile("Shorter Stubby",
42 255, 400, 3, 3, 3, 3, 48, 13, 3, 48, R.xml.default_workspace_4x4));
43 sDeviceProfiles.add(new InvariantDeviceProfile("Short Stubby",
44 275, 420, 3, 4, 3, 4, 48, 13, 5, 48, R.xml.default_workspace_4x4));
45 sDeviceProfiles.add(new InvariantDeviceProfile("Stubby",
46 255, 450, 3, 4, 3, 4, 48, 13, 5, 48, R.xml.default_workspace_4x4));
47 sDeviceProfiles.add(new InvariantDeviceProfile("Nexus S",
48 296, 491.33f, 4, 4, 4, 4, 48, 13, 5, 48, R.xml.default_workspace_4x4));
49 sDeviceProfiles.add(new InvariantDeviceProfile("Nexus 4",
50 335, 567, 4, 4, 4, 4, DEFAULT_ICON_SIZE_DP, 13, 5, 56, R.xml.default_workspace_4x4));
51 sDeviceProfiles.add(new InvariantDeviceProfile("Nexus 5",
52 359, 567, 4, 4, 4, 4, DEFAULT_ICON_SIZE_DP, 13, 5, 56, R.xml.default_workspace_4x4));
53 sDeviceProfiles.add(new InvariantDeviceProfile("Large Phone",
54 406, 694, 5, 5, 4, 4, 64, 14.4f, 5, 56, R.xml.default_workspace_5x5));
55 // The tablet profile is odd in that the landscape orientation
56 // also includes the nav bar on the side
57 sDeviceProfiles.add(new InvariantDeviceProfile("Nexus 7",
58 575, 904, 5, 6, 4, 5, 72, 14.4f, 7, 60, R.xml.default_workspace_5x6));
59 // Larger tablet profiles always have system bars on the top & bottom
60 sDeviceProfiles.add(new InvariantDeviceProfile("Nexus 10",
61 727, 1207, 5, 6, 4, 5, 76, 14.4f, 7, 64, R.xml.default_workspace_5x6));
62 sDeviceProfiles.add(new InvariantDeviceProfile("20-inch Tablet",
63 1527, 2527, 7, 7, 6, 6, 100, 20, 7, 72, R.xml.default_workspace_4x4));
64 }
65
Sunny Goyalc6205602015-05-21 20:46:33 -070066 private class DeviceProfileQuery {
Adam Cohen2e6da152015-05-06 11:42:25 -070067 InvariantDeviceProfile profile;
68 float widthDps;
69 float heightDps;
70 float value;
71 PointF dimens;
72
73 DeviceProfileQuery(InvariantDeviceProfile p, float v) {
74 widthDps = p.minWidthDps;
75 heightDps = p.minHeightDps;
76 value = v;
77 dimens = new PointF(widthDps, heightDps);
78 profile = p;
79 }
80 }
81
82 // Profile-defining invariant properties
83 String name;
84 float minWidthDps;
85 float minHeightDps;
86 public int numRows;
87 public int numColumns;
88 public int numFolderRows;
89 public int numFolderColumns;
90 float iconSize;
91 float iconTextSize;
92 float numHotseatIcons;
93 float hotseatIconSize;
94 int defaultLayoutId;
95
96 // Derived invariant properties
97 int hotseatAllAppsRank;
98
Sunny Goyalc6205602015-05-21 20:46:33 -070099 DeviceProfile landscapeProfile;
100 DeviceProfile portraitProfile;
101
Adam Cohen2e6da152015-05-06 11:42:25 -0700102 InvariantDeviceProfile() {
103 }
104
105 InvariantDeviceProfile(String n, float w, float h, int r, int c, int fr, int fc,
106 float is, float its, float hs, float his, int dlId) {
107 // Ensure that we have an odd number of hotseat items (since we need to place all apps)
108 if (hs % 2 == 0) {
109 throw new RuntimeException("All Device Profiles must have an odd number of hotseat spaces");
110 }
111
112 name = n;
113 minWidthDps = w;
114 minHeightDps = h;
115 numRows = r;
116 numColumns = c;
117 numFolderRows = fr;
118 numFolderColumns = fc;
119 iconSize = is;
120 iconTextSize = its;
121 numHotseatIcons = hs;
122 hotseatIconSize = his;
123 defaultLayoutId = dlId;
124 }
125
Sunny Goyalc6205602015-05-21 20:46:33 -0700126 @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
Adam Cohen2e6da152015-05-06 11:42:25 -0700127 InvariantDeviceProfile(Context context) {
128 WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
129 Display display = wm.getDefaultDisplay();
130 DisplayMetrics dm = new DisplayMetrics();
131 display.getMetrics(dm);
132
133 Point smallestSize = new Point();
134 Point largestSize = new Point();
135 display.getCurrentSizeRange(smallestSize, largestSize);
136
137 minWidthDps = Utilities.dpiFromPx(Math.min(smallestSize.x, smallestSize.y), dm);
138 minHeightDps = Utilities.dpiFromPx(Math.min(largestSize.x, largestSize.y), dm);
139
140 ArrayList<DeviceProfileQuery> points =
141 new ArrayList<DeviceProfileQuery>();
142
143 // Find the closes profile given the width/height
144 for (InvariantDeviceProfile p : sDeviceProfiles) {
145 points.add(new DeviceProfileQuery(p, 0f));
146 }
147
148 InvariantDeviceProfile closestProfile =
149 findClosestDeviceProfile(minWidthDps, minHeightDps, points);
150
151 // The following properties are inherited directly from the nearest archetypal profile
152 numRows = closestProfile.numRows;
153 numColumns = closestProfile.numColumns;
154 numHotseatIcons = closestProfile.numHotseatIcons;
155 hotseatAllAppsRank = (int) (numHotseatIcons / 2);
156 defaultLayoutId = closestProfile.defaultLayoutId;
157 numFolderRows = closestProfile.numFolderRows;
158 numFolderColumns = closestProfile.numFolderColumns;
159
160
161 // The following properties are interpolated based on proximity to nearby archetypal
162 // profiles
163 points.clear();
164 for (InvariantDeviceProfile p : sDeviceProfiles) {
165 points.add(new DeviceProfileQuery(p, p.iconSize));
166 }
167 iconSize = invDistWeightedInterpolate(minWidthDps, minHeightDps, points);
168 points.clear();
169 for (InvariantDeviceProfile p : sDeviceProfiles) {
170 points.add(new DeviceProfileQuery(p, p.iconTextSize));
171 }
172 iconTextSize = invDistWeightedInterpolate(minWidthDps, minHeightDps, points);
173 points.clear();
174 for (InvariantDeviceProfile p : sDeviceProfiles) {
175 points.add(new DeviceProfileQuery(p, p.hotseatIconSize));
176 }
177 hotseatIconSize = invDistWeightedInterpolate(minWidthDps, minHeightDps, points);
178
179 // If the partner customization apk contains any grid overrides, apply them
180 // Supported overrides: numRows, numColumns, iconSize
181 applyPartnerDeviceProfileOverrides(context, dm);
Sunny Goyalc6205602015-05-21 20:46:33 -0700182
183 Point realSize = new Point();
184 display.getRealSize(realSize);
185 // The real size never changes. smallSide and largeSize will remain the
186 // same in any orientation.
187 int smallSide = Math.min(realSize.x, realSize.y);
188 int largeSide = Math.max(realSize.x, realSize.y);
189
190 landscapeProfile = new DeviceProfile(context, this, smallestSize, largestSize,
191 largeSide, smallSide, true /* isLandscape */);
192 portraitProfile = new DeviceProfile(context, this, smallestSize, largestSize,
193 smallSide, largeSide, false /* isLandscape */);
Adam Cohen2e6da152015-05-06 11:42:25 -0700194 }
195
196 /**
197 * Apply any Partner customization grid overrides.
198 *
199 * Currently we support: all apps row / column count.
200 */
201 private void applyPartnerDeviceProfileOverrides(Context ctx, DisplayMetrics dm) {
202 Partner p = Partner.get(ctx.getPackageManager());
203 if (p != null) {
204 p.applyInvariantDeviceProfileOverrides(this, dm);
205 }
206 }
207
208 @Thunk float dist(PointF p0, PointF p1) {
209 return (float) Math.sqrt((p1.x - p0.x)*(p1.x-p0.x) +
210 (p1.y-p0.y)*(p1.y-p0.y));
211 }
212
213 private float weight(PointF a, PointF b,
214 float pow) {
215 float d = dist(a, b);
216 if (d == 0f) {
217 return Float.POSITIVE_INFINITY;
218 }
219 return (float) (1f / Math.pow(d, pow));
220 }
221
222 /** Returns the closest device profile given the width and height and a list of profiles */
223 private InvariantDeviceProfile findClosestDeviceProfile(float width, float height,
224 ArrayList<DeviceProfileQuery> points) {
225 return findClosestDeviceProfiles(width, height, points).get(0).profile;
226 }
227
228 /** Returns the closest device profiles ordered by closeness to the specified width and height */
229 private ArrayList<DeviceProfileQuery> findClosestDeviceProfiles(float width, float height,
230 ArrayList<DeviceProfileQuery> points) {
231 final PointF xy = new PointF(width, height);
232
233 // Sort the profiles by their closeness to the dimensions
234 ArrayList<DeviceProfileQuery> pointsByNearness = points;
235 Collections.sort(pointsByNearness, new Comparator<DeviceProfileQuery>() {
236 public int compare(DeviceProfileQuery a, DeviceProfileQuery b) {
237 return (int) (dist(xy, a.dimens) - dist(xy, b.dimens));
238 }
239 });
240
241 return pointsByNearness;
242 }
243
244 private float invDistWeightedInterpolate(float width, float height,
245 ArrayList<DeviceProfileQuery> points) {
246 float sum = 0;
247 float weights = 0;
248 float pow = 5;
249 float kNearestNeighbors = 3;
250 final PointF xy = new PointF(width, height);
251
252 ArrayList<DeviceProfileQuery> pointsByNearness = findClosestDeviceProfiles(width, height,
253 points);
254
255 for (int i = 0; i < pointsByNearness.size(); ++i) {
256 DeviceProfileQuery p = pointsByNearness.get(i);
257 if (i < kNearestNeighbors) {
258 float w = weight(xy, p.dimens, pow);
259 if (w == Float.POSITIVE_INFINITY) {
260 return p.value;
261 }
262 weights += w;
263 }
264 }
265
266 for (int i = 0; i < pointsByNearness.size(); ++i) {
267 DeviceProfileQuery p = pointsByNearness.get(i);
268 if (i < kNearestNeighbors) {
269 float w = weight(xy, p.dimens, pow);
270 sum += w * p.value / weights;
271 }
272 }
273
274 return sum;
275 }
276}