| /* |
| * Copyright 2023 The Android Open Source Project |
| * |
| * 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 androidx.mediarouter.app; |
| |
| import android.content.Context; |
| import android.content.pm.PackageManager; |
| import android.content.res.Configuration; |
| import android.content.res.Resources; |
| import android.hardware.Sensor; |
| import android.hardware.SensorManager; |
| import android.os.Build; |
| |
| import androidx.annotation.Dimension; |
| import androidx.annotation.NonNull; |
| import androidx.annotation.Nullable; |
| import androidx.mediarouter.R; |
| |
| /** Utility methods for checking properties of the current device. */ |
| final class DeviceUtils { |
| @Nullable |
| private static Boolean sIsPhone; |
| @Nullable |
| private static Boolean sIsTablet; |
| @Nullable |
| private static Boolean sIsFoldable; |
| @Nullable |
| private static Boolean sIsSevenInchTablet; |
| @Nullable |
| private static Boolean sIsWearable; |
| @Nullable |
| private static Boolean sIsAuto; |
| @Nullable |
| private static Boolean sIsTv; |
| |
| /** The feature name for Auto devices. */ |
| private static final String FEATURE_AUTO = "android.hardware.type.automotive"; |
| |
| /** The feature names for TV devices, either of which will qualify. */ |
| private static final String FEATURE_TV_1 = "com.google.android.tv"; |
| |
| private static final String FEATURE_TV_2 = "android.hardware.type.television"; |
| private static final String FEATURE_TV_3 = "android.software.leanback"; |
| |
| /** The minimum screen width for a 7-inch tablet. */ |
| private static final @Dimension(unit = Dimension.DP) int |
| SEVEN_INCH_TABLET_MINIMUM_SCREEN_WIDTH_DP = 600; |
| |
| private DeviceUtils() { |
| } |
| |
| /** |
| * Returns a string representation of a device form factor. Supported form factors are: |
| * {@link #{R.string.mr_device_form_factor_phone} and |
| * @link #{R.string.mr_device_form_factor_tablet} and |
| * @link #{R.string.mr_device_form_factor_tv} and |
| * @link #{R.string.mr_device_form_factor_watch} and |
| * @link #{R.string.mr_device_form_factor_car} and |
| * @link #{R.string.mr_device_form_factor_unknown}} |
| */ |
| static String getDeviceFormFactorString(@NonNull Context context) { |
| if (isPhone(context) || isFoldable(context)) { |
| return context.getString(R.string.mr_device_form_factor_phone); |
| } else if (isTablet(context) || isSevenInchTablet(context)) { |
| return context.getString(R.string.mr_device_form_factor_tablet); |
| } else if (isTv(context)) { |
| return context.getString(R.string.mr_device_form_factor_tv); |
| } else if (isWearable(context)) { |
| return context.getString(R.string.mr_device_form_factor_watch); |
| } else if (isAuto(context)) { |
| return context.getString(R.string.mr_device_form_factor_car); |
| } else { |
| return context.getString(R.string.mr_device_form_factor_unknown); |
| } |
| } |
| |
| /** Returns {@code true} if the current device is considered a phone. */ |
| private static boolean isPhone(@NonNull Context context) { |
| if (sIsPhone == null) { |
| sIsPhone = |
| (!isTablet(context) |
| && !isWearable(context) |
| && !isAuto(context) |
| && !isTv(context)); |
| } |
| return sIsPhone; |
| } |
| |
| /** Returns {@code true} if the current device considered a tablet (Honeycomb+ and XLarge). */ |
| private static boolean isTablet(@NonNull Context context) { |
| return isTablet(context.getResources()); |
| } |
| |
| /** Returns {@code true} if the current device considered a tablet (Honeycomb+ and XLarge). */ |
| private static boolean isTablet(@NonNull Resources resources) { |
| if (resources == null) { |
| return false; |
| } |
| if (sIsTablet == null) { |
| // Consider it to be tablet if it's either a) xlarge-v11, or b) sw600dp-v13. |
| boolean isXlarge = |
| (resources.getConfiguration().screenLayout |
| & Configuration.SCREENLAYOUT_SIZE_MASK) |
| > Configuration.SCREENLAYOUT_SIZE_LARGE; |
| sIsTablet = isXlarge || isSevenInchTablet(resources); |
| } |
| return sIsTablet; |
| } |
| |
| /** |
| * Returns {@code true} if the current device considered a foldable (R+ and has a hinge). |
| * |
| * <p>Note: this is not mutually exclusive with {@link #sIsPhone} |
| */ |
| private static boolean isFoldable(@NonNull Context context) { |
| if (sIsFoldable == null) { |
| SensorManager sensorManager = |
| (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); |
| sIsFoldable = |
| Build.VERSION.SDK_INT >= Build.VERSION_CODES.R |
| && sensorManager != null |
| && sensorManager.getDefaultSensor(Sensor.TYPE_HINGE_ANGLE) != null; |
| } |
| return sIsFoldable; |
| } |
| |
| /** |
| * Returns {@code true} if the current device is considered to be a 7-inch tablet (e.g. and not |
| * a 10-inch tablet). |
| */ |
| private static boolean isSevenInchTablet(@NonNull Context context) { |
| return isSevenInchTablet(context.getResources()); |
| } |
| |
| /** |
| * Returns {@code true} if the current device is considered to be a 7-inch tablet (e.g. and not |
| * a 10-inch tablet). |
| */ |
| private static boolean isSevenInchTablet(@NonNull Resources resources) { |
| if (resources == null) { |
| return false; |
| } |
| if (sIsSevenInchTablet == null) { |
| Configuration configuration = resources.getConfiguration(); |
| sIsSevenInchTablet = |
| (configuration.screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) |
| <= Configuration.SCREENLAYOUT_SIZE_LARGE |
| && configuration.smallestScreenWidthDp |
| >= SEVEN_INCH_TABLET_MINIMUM_SCREEN_WIDTH_DP; |
| } |
| return sIsSevenInchTablet; |
| } |
| |
| /** |
| * Returns {@code true} if the current device is a wearable. As of 2019Q4, that is synonymous |
| * with being a watch. |
| * |
| * <p>For container release builds, returns {@code true} if the build type is {@code WEARABLE} |
| * and {@code false} for all other build types. |
| */ |
| private static boolean isWearable(@NonNull Context context) { |
| return isWearable(context.getPackageManager()); |
| } |
| |
| /** |
| * Returns {@code true} if the current device is a wearable. As of 2019Q4, that is synonymous |
| * with being a watch. |
| * |
| * <p>For container release builds, returns {@code true} if the build type is {@code WEARABLE} |
| * and {@code false} for all other build types. |
| */ |
| private static boolean isWearable(@NonNull PackageManager packageManager) { |
| if (sIsWearable == null) { |
| sIsWearable = |
| Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH |
| && packageManager.hasSystemFeature(PackageManager.FEATURE_WATCH); |
| } |
| return sIsWearable; |
| } |
| |
| /** |
| * Returns {@code true} if the device should be considered as a car. |
| * |
| * <p>For container release builds, returns {@code true} if the build type is {@code AUTO} and |
| * {@code false} for all other build types. |
| */ |
| private static boolean isAuto(@NonNull Context context) { |
| return isAuto(context.getPackageManager()); |
| } |
| |
| /** |
| * Returns {@code true} if the device should be considered as a car. |
| * |
| * <p>For container release builds, returns {@code true} if the build type is {@code AUTO} and |
| * {@code false} for all other build types. |
| */ |
| private static boolean isAuto(@NonNull PackageManager packageManager) { |
| if (sIsAuto == null) { |
| sIsAuto = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O |
| && packageManager.hasSystemFeature(FEATURE_AUTO); |
| } |
| return sIsAuto; |
| } |
| |
| /** |
| * Returns {@code true} if the device should be considered as a TV. |
| * |
| * <p>For container release builds, returns {@code true} if the build type is {@code ATV} and |
| * {@code false} for all other build types except {@code PHONE_PRE_LMP} (aka {@code PROD}). |
| */ |
| private static boolean isTv(@NonNull Context context) { |
| return DeviceUtils.isTv(context.getPackageManager()); |
| } |
| |
| /** |
| * Returns {@code true} if the device should be considered as a TV. |
| * |
| * <p>For container release builds, returns {@code true} if the build type is {@code ATV} and |
| * {@code false} for all other build types except {@code PHONE_PRE_LMP} (aka {@code PROD}). |
| */ |
| private static boolean isTv(@NonNull PackageManager packageManager) { |
| if (sIsTv == null) { |
| sIsTv = |
| packageManager.hasSystemFeature(FEATURE_TV_1) |
| || packageManager.hasSystemFeature(FEATURE_TV_2) |
| || packageManager.hasSystemFeature(FEATURE_TV_3); |
| } |
| return sIsTv; |
| } |
| } |