blob: 0b2f4d921ea34365c492cf2bcef196fc610b3449 [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 Goyal87dc48b2018-10-12 11:42:33 -070019import static com.android.launcher3.config.FeatureFlags.APPLY_CONFIG_AT_RUNTIME;
20
Sunny Goyalc6205602015-05-21 20:46:33 -070021import android.annotation.TargetApi;
Adam Cohen2e6da152015-05-06 11:42:25 -070022import android.content.Context;
Sunny Goyal27835952017-01-13 12:15:53 -080023import android.content.res.Configuration;
Sunny Goyal819e1932016-07-07 16:43:58 -070024import android.content.res.TypedArray;
25import android.content.res.XmlResourceParser;
Adam Cohen2e6da152015-05-06 11:42:25 -070026import android.graphics.Point;
Adam Cohen2e6da152015-05-06 11:42:25 -070027import android.util.DisplayMetrics;
Sunny Goyal87dc48b2018-10-12 11:42:33 -070028import android.util.Log;
Sunny Goyal819e1932016-07-07 16:43:58 -070029import android.util.Xml;
Adam Cohen2e6da152015-05-06 11:42:25 -070030import android.view.Display;
31import android.view.WindowManager;
Hyunyoung Song35c3c7f2015-05-28 15:33:40 -070032
Sunny Goyald0e360a2018-06-29 14:40:18 -070033import com.android.launcher3.util.ConfigMonitor;
34import com.android.launcher3.util.MainThreadInitializedObject;
Adam Cohen2e6da152015-05-06 11:42:25 -070035import com.android.launcher3.util.Thunk;
Hyunyoung Song35c3c7f2015-05-28 15:33:40 -070036
Sunny Goyal819e1932016-07-07 16:43:58 -070037import org.xmlpull.v1.XmlPullParser;
38import org.xmlpull.v1.XmlPullParserException;
39
40import java.io.IOException;
Adam Cohen2e6da152015-05-06 11:42:25 -070041import java.util.ArrayList;
42import java.util.Collections;
43import java.util.Comparator;
44
Sunny Goyald2303072018-08-14 15:21:45 -070045import androidx.annotation.VisibleForTesting;
46
Adam Cohen2e6da152015-05-06 11:42:25 -070047public class InvariantDeviceProfile {
Adam Cohen2e6da152015-05-06 11:42:25 -070048
Sunny Goyald0e360a2018-06-29 14:40:18 -070049 // We do not need any synchronization for this variable as its only written on UI thread.
50 public static final MainThreadInitializedObject<InvariantDeviceProfile> INSTANCE =
Sunny Goyal87dc48b2018-10-12 11:42:33 -070051 new MainThreadInitializedObject<>(InvariantDeviceProfile::new);
Adam Cohen2e6da152015-05-06 11:42:25 -070052
Sunny Goyal53d7ee42015-05-22 12:25:45 -070053 private static final float ICON_SIZE_DEFINED_IN_APP_DP = 48;
54
Sunny Goyal87dc48b2018-10-12 11:42:33 -070055 public static final int CHANGE_FLAG_GRID = 1 << 0;
56 public static final int CHANGE_FLAG_ICON_SIZE = 1 << 1;
57
Hyunyoung Song35c3c7f2015-05-28 15:33:40 -070058 // Constants that affects the interpolation curve between statically defined device profile
59 // buckets.
60 private static float KNEARESTNEIGHBOR = 3;
61 private static float WEIGHT_POWER = 5;
Adam Cohen2e6da152015-05-06 11:42:25 -070062
Hyunyoung Song35c3c7f2015-05-28 15:33:40 -070063 // used to offset float not being able to express extremely small weights in extreme cases.
64 private static float WEIGHT_EFFICIENT = 100000f;
Adam Cohen2e6da152015-05-06 11:42:25 -070065
66 // Profile-defining invariant properties
Sunny Goyal87dc48b2018-10-12 11:42:33 -070067 private String name;
68 private float minWidthDps;
69 private float minHeightDps;
Hyunyoung Song35c3c7f2015-05-28 15:33:40 -070070
71 /**
72 * Number of icons per row and column in the workspace.
73 */
Adam Cohen2e6da152015-05-06 11:42:25 -070074 public int numRows;
75 public int numColumns;
Hyunyoung Song35c3c7f2015-05-28 15:33:40 -070076
77 /**
78 * Number of icons per row and column in the folder.
79 */
Adam Cohen2e6da152015-05-06 11:42:25 -070080 public int numFolderRows;
81 public int numFolderColumns;
Sunny Goyalfc218302015-09-17 14:59:10 -070082 public float iconSize;
Jon Mirandab28c4fc2017-06-20 10:58:36 -070083 public float landscapeIconSize;
Sunny Goyalfc218302015-09-17 14:59:10 -070084 public int iconBitmapSize;
85 public int fillResIconDpi;
86 public float iconTextSize;
Hyunyoung Song35c3c7f2015-05-28 15:33:40 -070087
88 /**
89 * Number of icons inside the hotseat area.
90 */
Sunny Goyalf862a262015-12-14 14:27:38 -080091 public int numHotseatIcons;
Adam Cohen27824492017-09-22 17:10:55 -070092
Adam Cohen2e6da152015-05-06 11:42:25 -070093 int defaultLayoutId;
Adam Cohen27824492017-09-22 17:10:55 -070094 int demoModeLayoutId;
Adam Cohen2e6da152015-05-06 11:42:25 -070095
cuijiaxingabda8d72017-03-20 09:51:36 -070096 public DeviceProfile landscapeProfile;
97 public DeviceProfile portraitProfile;
Sunny Goyalc6205602015-05-21 20:46:33 -070098
Sunny Goyal6f866092016-03-17 17:04:15 -070099 public Point defaultWallpaperSize;
100
Sunny Goyal87dc48b2018-10-12 11:42:33 -0700101 private final ArrayList<OnIDPChangeListener> mChangeListeners = new ArrayList<>();
102 private ConfigMonitor mConfigMonitor;
103
Sunny Goyalf633ef52018-03-13 09:57:05 -0700104 @VisibleForTesting
Sunny Goyal87dc48b2018-10-12 11:42:33 -0700105 public InvariantDeviceProfile() {}
Adam Cohen2e6da152015-05-06 11:42:25 -0700106
Sunny Goyalf633ef52018-03-13 09:57:05 -0700107 private InvariantDeviceProfile(InvariantDeviceProfile p) {
Hyunyoung Song35c3c7f2015-05-28 15:33:40 -0700108 this(p.name, p.minWidthDps, p.minHeightDps, p.numRows, p.numColumns,
Sunny Goyalb1d222e2018-01-30 20:52:27 -0800109 p.numFolderRows, p.numFolderColumns,
Jon Mirandab28c4fc2017-06-20 10:58:36 -0700110 p.iconSize, p.landscapeIconSize, p.iconTextSize, p.numHotseatIcons,
Adam Cohen27824492017-09-22 17:10:55 -0700111 p.defaultLayoutId, p.demoModeLayoutId);
Hyunyoung Song35c3c7f2015-05-28 15:33:40 -0700112 }
113
Sunny Goyalf633ef52018-03-13 09:57:05 -0700114 private InvariantDeviceProfile(String n, float w, float h, int r, int c, int fr, int fc,
Adam Cohen27824492017-09-22 17:10:55 -0700115 float is, float lis, float its, int hs, int dlId, int dmlId) {
Adam Cohen2e6da152015-05-06 11:42:25 -0700116 name = n;
117 minWidthDps = w;
118 minHeightDps = h;
119 numRows = r;
120 numColumns = c;
121 numFolderRows = fr;
122 numFolderColumns = fc;
123 iconSize = is;
Jon Mirandab28c4fc2017-06-20 10:58:36 -0700124 landscapeIconSize = lis;
Adam Cohen2e6da152015-05-06 11:42:25 -0700125 iconTextSize = its;
126 numHotseatIcons = hs;
Adam Cohen2e6da152015-05-06 11:42:25 -0700127 defaultLayoutId = dlId;
Adam Cohen27824492017-09-22 17:10:55 -0700128 demoModeLayoutId = dmlId;
Adam Cohen2e6da152015-05-06 11:42:25 -0700129 }
130
Sunny Goyalbbf01842015-10-08 07:41:15 -0700131 @TargetApi(23)
Sunny Goyald0e360a2018-06-29 14:40:18 -0700132 private InvariantDeviceProfile(Context context) {
Sunny Goyal87dc48b2018-10-12 11:42:33 -0700133 initGrid(context);
134 mConfigMonitor = new ConfigMonitor(context,
135 APPLY_CONFIG_AT_RUNTIME.get() ? this::onConfigChanged : this::killProcess);
136 }
137
138 private void initGrid(Context context) {
Adam Cohen2e6da152015-05-06 11:42:25 -0700139 WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
140 Display display = wm.getDefaultDisplay();
141 DisplayMetrics dm = new DisplayMetrics();
142 display.getMetrics(dm);
143
144 Point smallestSize = new Point();
145 Point largestSize = new Point();
146 display.getCurrentSizeRange(smallestSize, largestSize);
147
Hyunyoung Song35c3c7f2015-05-28 15:33:40 -0700148 // This guarantees that width < height
Adam Cohen2e6da152015-05-06 11:42:25 -0700149 minWidthDps = Utilities.dpiFromPx(Math.min(smallestSize.x, smallestSize.y), dm);
150 minHeightDps = Utilities.dpiFromPx(Math.min(largestSize.x, largestSize.y), dm);
151
Sunny Goyal819e1932016-07-07 16:43:58 -0700152 ArrayList<InvariantDeviceProfile> closestProfiles = findClosestDeviceProfiles(
153 minWidthDps, minHeightDps, getPredefinedDeviceProfiles(context));
Hyunyoung Song35c3c7f2015-05-28 15:33:40 -0700154 InvariantDeviceProfile interpolatedDeviceProfileOut =
155 invDistWeightedInterpolate(minWidthDps, minHeightDps, closestProfiles);
Adam Cohen2e6da152015-05-06 11:42:25 -0700156
Hyunyoung Song35c3c7f2015-05-28 15:33:40 -0700157 InvariantDeviceProfile closestProfile = closestProfiles.get(0);
Adam Cohen2e6da152015-05-06 11:42:25 -0700158 numRows = closestProfile.numRows;
159 numColumns = closestProfile.numColumns;
160 numHotseatIcons = closestProfile.numHotseatIcons;
Adam Cohen2e6da152015-05-06 11:42:25 -0700161 defaultLayoutId = closestProfile.defaultLayoutId;
Adam Cohen27824492017-09-22 17:10:55 -0700162 demoModeLayoutId = closestProfile.demoModeLayoutId;
Adam Cohen2e6da152015-05-06 11:42:25 -0700163 numFolderRows = closestProfile.numFolderRows;
164 numFolderColumns = closestProfile.numFolderColumns;
165
Hyunyoung Song35c3c7f2015-05-28 15:33:40 -0700166 iconSize = interpolatedDeviceProfileOut.iconSize;
Jon Mirandab28c4fc2017-06-20 10:58:36 -0700167 landscapeIconSize = interpolatedDeviceProfileOut.landscapeIconSize;
Sunny Goyal53d7ee42015-05-22 12:25:45 -0700168 iconBitmapSize = Utilities.pxFromDp(iconSize, dm);
Hyunyoung Song35c3c7f2015-05-28 15:33:40 -0700169 iconTextSize = interpolatedDeviceProfileOut.iconTextSize;
Sunny Goyal53d7ee42015-05-22 12:25:45 -0700170 fillResIconDpi = getLauncherIconDensity(iconBitmapSize);
Adam Cohen2e6da152015-05-06 11:42:25 -0700171
172 // If the partner customization apk contains any grid overrides, apply them
173 // Supported overrides: numRows, numColumns, iconSize
174 applyPartnerDeviceProfileOverrides(context, dm);
Sunny Goyalc6205602015-05-21 20:46:33 -0700175
176 Point realSize = new Point();
177 display.getRealSize(realSize);
Hyunyoung Song35c3c7f2015-05-28 15:33:40 -0700178 // The real size never changes. smallSide and largeSide will remain the
Sunny Goyalc6205602015-05-21 20:46:33 -0700179 // same in any orientation.
180 int smallSide = Math.min(realSize.x, realSize.y);
181 int largeSide = Math.max(realSize.x, realSize.y);
182
183 landscapeProfile = new DeviceProfile(context, this, smallestSize, largestSize,
Sunny Goyald70e75a2018-02-22 10:07:32 -0800184 largeSide, smallSide, true /* isLandscape */, false /* isMultiWindowMode */);
Sunny Goyalc6205602015-05-21 20:46:33 -0700185 portraitProfile = new DeviceProfile(context, this, smallestSize, largestSize,
Sunny Goyald70e75a2018-02-22 10:07:32 -0800186 smallSide, largeSide, false /* isLandscape */, false /* isMultiWindowMode */);
Sunny Goyal6f866092016-03-17 17:04:15 -0700187
188 // We need to ensure that there is enough extra space in the wallpaper
189 // for the intended parallax effects
190 if (context.getResources().getConfiguration().smallestScreenWidthDp >= 720) {
191 defaultWallpaperSize = new Point(
192 (int) (largeSide * wallpaperTravelToScreenWidthRatio(largeSide, smallSide)),
193 largeSide);
194 } else {
195 defaultWallpaperSize = new Point(Math.max(smallSide * 2, largeSide), largeSide);
196 }
Adam Cohen2e6da152015-05-06 11:42:25 -0700197 }
198
Sunny Goyal87dc48b2018-10-12 11:42:33 -0700199 public void addOnChangeListener(OnIDPChangeListener listener) {
200 mChangeListeners.add(listener);
201 }
202
203 private void killProcess(Context context) {
204 Log.e("ConfigMonitor", "restarting launcher");
205 android.os.Process.killProcess(android.os.Process.myPid());
206 }
207
208 private void onConfigChanged(Context context) {
209 // Config changes, what shall we do?
210 InvariantDeviceProfile oldProfile = new InvariantDeviceProfile(this);
211
212 // Re-init grid
213 initGrid(context);
214
215 int changeFlags = 0;
216 if (numRows != oldProfile.numRows ||
217 numColumns != oldProfile.numColumns ||
218 numFolderColumns != oldProfile.numFolderColumns ||
219 numFolderRows != oldProfile.numFolderRows ||
220 numHotseatIcons != oldProfile.numHotseatIcons) {
221 changeFlags |= CHANGE_FLAG_GRID;
222 }
223
224 if (iconSize != oldProfile.iconSize || iconBitmapSize != oldProfile.iconBitmapSize) {
225 changeFlags |= CHANGE_FLAG_ICON_SIZE;
226 }
227
228 // Create a new config monitor
229 mConfigMonitor.unregister();
230 mConfigMonitor = new ConfigMonitor(context, this::onConfigChanged);
231
232 for (OnIDPChangeListener listener : mChangeListeners) {
233 listener.onIdpChanged(changeFlags, this);
234 }
235 }
236
Sunny Goyal819e1932016-07-07 16:43:58 -0700237 ArrayList<InvariantDeviceProfile> getPredefinedDeviceProfiles(Context context) {
238 ArrayList<InvariantDeviceProfile> profiles = new ArrayList<>();
239 try (XmlResourceParser parser = context.getResources().getXml(R.xml.device_profiles)) {
240 final int depth = parser.getDepth();
241 int type;
242
243 while (((type = parser.next()) != XmlPullParser.END_TAG ||
244 parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
245 if ((type == XmlPullParser.START_TAG) && "profile".equals(parser.getName())) {
246 TypedArray a = context.obtainStyledAttributes(
247 Xml.asAttributeSet(parser), R.styleable.InvariantDeviceProfile);
248 int numRows = a.getInt(R.styleable.InvariantDeviceProfile_numRows, 0);
249 int numColumns = a.getInt(R.styleable.InvariantDeviceProfile_numColumns, 0);
250 float iconSize = a.getFloat(R.styleable.InvariantDeviceProfile_iconSize, 0);
251 profiles.add(new InvariantDeviceProfile(
252 a.getString(R.styleable.InvariantDeviceProfile_name),
253 a.getFloat(R.styleable.InvariantDeviceProfile_minWidthDps, 0),
254 a.getFloat(R.styleable.InvariantDeviceProfile_minHeightDps, 0),
255 numRows,
256 numColumns,
257 a.getInt(R.styleable.InvariantDeviceProfile_numFolderRows, numRows),
258 a.getInt(R.styleable.InvariantDeviceProfile_numFolderColumns, numColumns),
Sunny Goyal819e1932016-07-07 16:43:58 -0700259 iconSize,
Jon Mirandab28c4fc2017-06-20 10:58:36 -0700260 a.getFloat(R.styleable.InvariantDeviceProfile_landscapeIconSize, iconSize),
Sunny Goyal819e1932016-07-07 16:43:58 -0700261 a.getFloat(R.styleable.InvariantDeviceProfile_iconTextSize, 0),
262 a.getInt(R.styleable.InvariantDeviceProfile_numHotseatIcons, numColumns),
Adam Cohen27824492017-09-22 17:10:55 -0700263 a.getResourceId(R.styleable.InvariantDeviceProfile_defaultLayoutId, 0),
264 a.getResourceId(R.styleable.InvariantDeviceProfile_demoModeLayoutId, 0)));
Sunny Goyal819e1932016-07-07 16:43:58 -0700265 a.recycle();
266 }
267 }
268 } catch (IOException|XmlPullParserException e) {
269 throw new RuntimeException(e);
270 }
271 return profiles;
Hyunyoung Song35c3c7f2015-05-28 15:33:40 -0700272 }
273
Sunny Goyal53d7ee42015-05-22 12:25:45 -0700274 private int getLauncherIconDensity(int requiredSize) {
275 // Densities typically defined by an app.
276 int[] densityBuckets = new int[] {
277 DisplayMetrics.DENSITY_LOW,
278 DisplayMetrics.DENSITY_MEDIUM,
279 DisplayMetrics.DENSITY_TV,
280 DisplayMetrics.DENSITY_HIGH,
281 DisplayMetrics.DENSITY_XHIGH,
282 DisplayMetrics.DENSITY_XXHIGH,
283 DisplayMetrics.DENSITY_XXXHIGH
284 };
285
286 int density = DisplayMetrics.DENSITY_XXXHIGH;
287 for (int i = densityBuckets.length - 1; i >= 0; i--) {
288 float expectedSize = ICON_SIZE_DEFINED_IN_APP_DP * densityBuckets[i]
289 / DisplayMetrics.DENSITY_DEFAULT;
290 if (expectedSize >= requiredSize) {
291 density = densityBuckets[i];
292 }
293 }
294
295 return density;
296 }
Hyunyoung Song35c3c7f2015-05-28 15:33:40 -0700297
Adam Cohen2e6da152015-05-06 11:42:25 -0700298 /**
299 * Apply any Partner customization grid overrides.
300 *
301 * Currently we support: all apps row / column count.
302 */
Hyunyoung Song35c3c7f2015-05-28 15:33:40 -0700303 private void applyPartnerDeviceProfileOverrides(Context context, DisplayMetrics dm) {
304 Partner p = Partner.get(context.getPackageManager());
Adam Cohen2e6da152015-05-06 11:42:25 -0700305 if (p != null) {
306 p.applyInvariantDeviceProfileOverrides(this, dm);
307 }
308 }
309
Hyunyoung Song35c3c7f2015-05-28 15:33:40 -0700310 @Thunk float dist(float x0, float y0, float x1, float y1) {
311 return (float) Math.hypot(x1 - x0, y1 - y0);
Adam Cohen2e6da152015-05-06 11:42:25 -0700312 }
313
Hyunyoung Song35c3c7f2015-05-28 15:33:40 -0700314 /**
315 * Returns the closest device profiles ordered by closeness to the specified width and height
316 */
317 // Package private visibility for testing.
318 ArrayList<InvariantDeviceProfile> findClosestDeviceProfiles(
319 final float width, final float height, ArrayList<InvariantDeviceProfile> points) {
Adam Cohen2e6da152015-05-06 11:42:25 -0700320
321 // Sort the profiles by their closeness to the dimensions
Hyunyoung Song35c3c7f2015-05-28 15:33:40 -0700322 ArrayList<InvariantDeviceProfile> pointsByNearness = points;
323 Collections.sort(pointsByNearness, new Comparator<InvariantDeviceProfile>() {
324 public int compare(InvariantDeviceProfile a, InvariantDeviceProfile b) {
Winson46a06da2015-09-29 16:58:02 -0700325 return Float.compare(dist(width, height, a.minWidthDps, a.minHeightDps),
326 dist(width, height, b.minWidthDps, b.minHeightDps));
Adam Cohen2e6da152015-05-06 11:42:25 -0700327 }
328 });
329
330 return pointsByNearness;
331 }
332
Hyunyoung Song35c3c7f2015-05-28 15:33:40 -0700333 // Package private visibility for testing.
334 InvariantDeviceProfile invDistWeightedInterpolate(float width, float height,
335 ArrayList<InvariantDeviceProfile> points) {
Adam Cohen2e6da152015-05-06 11:42:25 -0700336 float weights = 0;
Adam Cohen2e6da152015-05-06 11:42:25 -0700337
Hyunyoung Song35c3c7f2015-05-28 15:33:40 -0700338 InvariantDeviceProfile p = points.get(0);
339 if (dist(width, height, p.minWidthDps, p.minHeightDps) == 0) {
340 return p;
Adam Cohen2e6da152015-05-06 11:42:25 -0700341 }
342
Hyunyoung Song35c3c7f2015-05-28 15:33:40 -0700343 InvariantDeviceProfile out = new InvariantDeviceProfile();
344 for (int i = 0; i < points.size() && i < KNEARESTNEIGHBOR; ++i) {
345 p = new InvariantDeviceProfile(points.get(i));
346 float w = weight(width, height, p.minWidthDps, p.minHeightDps, WEIGHT_POWER);
347 weights += w;
348 out.add(p.multiply(w));
Adam Cohen2e6da152015-05-06 11:42:25 -0700349 }
Hyunyoung Song35c3c7f2015-05-28 15:33:40 -0700350 return out.multiply(1.0f/weights);
Adam Cohen2e6da152015-05-06 11:42:25 -0700351 }
Hyunyoung Song35c3c7f2015-05-28 15:33:40 -0700352
353 private void add(InvariantDeviceProfile p) {
354 iconSize += p.iconSize;
Jon Mirandab28c4fc2017-06-20 10:58:36 -0700355 landscapeIconSize += p.landscapeIconSize;
Hyunyoung Song35c3c7f2015-05-28 15:33:40 -0700356 iconTextSize += p.iconTextSize;
Hyunyoung Song35c3c7f2015-05-28 15:33:40 -0700357 }
358
359 private InvariantDeviceProfile multiply(float w) {
360 iconSize *= w;
Jon Mirandab28c4fc2017-06-20 10:58:36 -0700361 landscapeIconSize *= w;
Hyunyoung Song35c3c7f2015-05-28 15:33:40 -0700362 iconTextSize *= w;
Hyunyoung Song35c3c7f2015-05-28 15:33:40 -0700363 return this;
364 }
365
Sunny Goyal27835952017-01-13 12:15:53 -0800366 public DeviceProfile getDeviceProfile(Context context) {
367 return context.getResources().getConfiguration().orientation
368 == Configuration.ORIENTATION_LANDSCAPE ? landscapeProfile : portraitProfile;
369 }
370
Hyunyoung Song35c3c7f2015-05-28 15:33:40 -0700371 private float weight(float x0, float y0, float x1, float y1, float pow) {
372 float d = dist(x0, y0, x1, y1);
373 if (Float.compare(d, 0f) == 0) {
374 return Float.POSITIVE_INFINITY;
375 }
376 return (float) (WEIGHT_EFFICIENT / Math.pow(d, pow));
377 }
Sunny Goyal6f866092016-03-17 17:04:15 -0700378
379 /**
380 * As a ratio of screen height, the total distance we want the parallax effect to span
381 * horizontally
382 */
383 private static float wallpaperTravelToScreenWidthRatio(int width, int height) {
384 float aspectRatio = width / (float) height;
385
386 // At an aspect ratio of 16/10, the wallpaper parallax effect should span 1.5 * screen width
387 // At an aspect ratio of 10/16, the wallpaper parallax effect should span 1.2 * screen width
388 // We will use these two data points to extrapolate how much the wallpaper parallax effect
389 // to span (ie travel) at any aspect ratio:
390
391 final float ASPECT_RATIO_LANDSCAPE = 16/10f;
392 final float ASPECT_RATIO_PORTRAIT = 10/16f;
393 final float WALLPAPER_WIDTH_TO_SCREEN_RATIO_LANDSCAPE = 1.5f;
394 final float WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT = 1.2f;
395
396 // To find out the desired width at different aspect ratios, we use the following two
397 // formulas, where the coefficient on x is the aspect ratio (width/height):
398 // (16/10)x + y = 1.5
399 // (10/16)x + y = 1.2
400 // We solve for x and y and end up with a final formula:
401 final float x =
402 (WALLPAPER_WIDTH_TO_SCREEN_RATIO_LANDSCAPE - WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT) /
403 (ASPECT_RATIO_LANDSCAPE - ASPECT_RATIO_PORTRAIT);
404 final float y = WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT - x * ASPECT_RATIO_PORTRAIT;
405 return x * aspectRatio + y;
406 }
407
Sunny Goyal87dc48b2018-10-12 11:42:33 -0700408 public interface OnIDPChangeListener {
409
410 void onIdpChanged(int changeFlags, InvariantDeviceProfile profile);
411 }
Hyunyoung Song35c3c7f2015-05-28 15:33:40 -0700412}