/* * Copyright (c) Facebook, Inc. and its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.facebook.litho; import android.content.pm.PackageManager; import android.content.res.Configuration; import android.content.res.Resources; import android.util.DisplayMetrics; public class DoubleMeasureFixUtil { private static final byte UNSET = 0; private static final byte NORMAL = 1; private static final byte CHROMEBOOK = 2; private static byte deviceType = UNSET; /** * Correction for an Android bug on some devices with "special" densities where the system will * double-measure with slightly different widths in the same traversal. See * https://issuetracker.google.com/issues/73804230 for more details. * * <p>This hits Litho extra-hard because we create {@link LayoutState}s in measure in many places, * so we try to correct for it here by replacing the widthSpec given to us from above with what we * think the correct one is. Even though the double measure will still happen, the incorrect width * will not propagate to any vertical RecyclerViews contained within. */ public static int correctWidthSpecForAndroidDoubleMeasureBug( Resources resources, PackageManager packageManager, int widthSpec) { final @SizeSpec.MeasureSpecMode int mode = SizeSpec.getMode(widthSpec); if (mode == SizeSpec.UNSPECIFIED) { return widthSpec; } // Will cache the device type to avoid repetitive package manager calls. if (deviceType == UNSET) { try { // Required to determine whether device used is a Chromebook. // See https://stackoverflow.com/questions/39784415/ for details. deviceType = packageManager.hasSystemFeature("org.chromium.arc.device_management") ? CHROMEBOOK : NORMAL; } catch (RuntimeException e) { // To catch RuntimeException("Package manager has died") that can occur on some version of // Android, when the remote PackageManager is unavailable. I suspect this sometimes occurs // when the App is being reinstalled. deviceType = NORMAL; } } final Configuration configuration = resources.getConfiguration(); final DisplayMetrics displayMetrics = resources.getDisplayMetrics(); final float screenDensity = displayMetrics.density; final float screenWidthDp = configuration.screenWidthDp; // If device used is a Chromebook we need to use the window size instead of the screen size to // avoid layout issues. final int screenWidthPx = (deviceType == CHROMEBOOK) ? (int) (screenWidthDp * screenDensity + 0.5f) : displayMetrics.widthPixels; // NB: Logic taken from ViewRootImpl#dipToPx final int calculatedScreenWidthPx = (int) (screenDensity * screenWidthDp + 0.5f); if (screenWidthPx != calculatedScreenWidthPx && calculatedScreenWidthPx == SizeSpec.getSize(widthSpec)) { return SizeSpec.makeSizeSpec(screenWidthPx, mode); } return widthSpec; } }