blob: fa1d8791bc87a490a4cfe46fac3c09d5b765f0ad [file] [log] [blame]
/*
* Copyright (C) 2021 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 com.android.wallpaper.util
import android.app.Activity
import android.content.Context
import android.graphics.Point
import android.hardware.display.DisplayManager
import android.util.Log
import android.view.Display
import android.view.DisplayInfo
import android.view.Surface.ROTATION_270
import android.view.Surface.ROTATION_90
import com.android.systemui.shared.recents.utilities.Utilities
import kotlin.math.min
/**
* Utility class to provide methods to find and obtain information about displays via {@link
* DisplayManager}
*
* Always pass [Context] or [Display] for the current display, instead of using the context in this
* class, which is fine for stateless info.
*/
class DisplayUtils(private val context: Context) {
companion object {
private const val TAG = "DisplayUtils"
private val ROTATION_HORIZONTAL_HINGE = setOf(ROTATION_90, ROTATION_270)
private const val TABLET_MIN_DPS = 600f // See Sysui's Utilities.TABLET_MIN_DPS
}
private val displayManager: DisplayManager by lazy {
context.applicationContext.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
}
fun hasMultiInternalDisplays(): Boolean {
return getInternalDisplays().size > 1
}
/**
* Returns the internal {@link Display} with the largest area to be used to calculate wallpaper
* size and cropping.
*/
fun getWallpaperDisplay(): Display {
val internalDisplays = getInternalDisplays()
return internalDisplays.maxWithOrNull { a, b -> getRealArea(a) - getRealArea(b) }
?: internalDisplays[0]
}
/**
* Checks if the device only has one display or unfolded screen in horizontal hinge orientation.
*/
fun isSingleDisplayOrUnfoldedHorizontalHinge(activity: Activity): Boolean {
return !hasMultiInternalDisplays() || isUnfoldedHorizontalHinge(activity)
}
/**
* Checks if the device is a foldable and it's unfolded and in horizontal hinge orientation
* (portrait).
*/
fun isUnfoldedHorizontalHinge(activity: Activity): Boolean {
return activity.display?.rotation in ROTATION_HORIZONTAL_HINGE &&
isOnWallpaperDisplay(activity) &&
hasMultiInternalDisplays()
}
fun getMaxDisplaysDimension(): Point {
val dimen = Point()
getInternalDisplays().let { displays ->
dimen.x = displays.maxOf { getRealSize(it).x }
dimen.y = displays.maxOf { getRealSize(it).y }
}
return dimen
}
/**
* This flag returns true if the display is:
* 1. a large screen device display, e.g. tablet
* 2. an unfolded display from a foldable device
*
* This flag returns false the display is:
* 1. a handheld device display
* 2. a folded display from a foldable device
*/
fun isLargeScreenOrUnfoldedDisplay(activity: Activity): Boolean {
// Note that a foldable is a large screen device if the largest display is large screen.
// Ths flag is true if it is a large screen device, e.g. tablet, or a foldable device.
val isLargeScreenOrFoldable = isLargeScreenDevice()
// For a single display device, this flag is always true.
// For a multi-display device, it is only true when the current display is the largest
// display. For the case of foldable, it is true when the display is the unfolded one, and
// false when it is folded.
val isSingleDisplayOrUnfolded = isOnWallpaperDisplay(activity)
return isLargeScreenOrFoldable && isSingleDisplayOrUnfolded
}
/**
* Returns true if this device's screen (or largest screen in case of multiple screen devices)
* is considered a "Large screen"
*/
fun isLargeScreenDevice(): Boolean {
// We need to use MaxDisplay's dimensions because if we're in embedded mode, our window
// will only be the size of the embedded Activity.
val maxDisplaysDimension = getRealSize(getWallpaperDisplay())
val smallestWidth = min(maxDisplaysDimension.x, maxDisplaysDimension.y)
return Utilities.dpiFromPx(
smallestWidth.toFloat(),
context.resources.configuration.densityDpi
) >= TABLET_MIN_DPS
}
/**
* Returns `true` if the current display is the wallpaper display on a multi-display device.
*
* On a multi-display device the wallpaper display is the largest display while on a single
* display device the only display is both the wallpaper display and the current display.
*
* For single display device, this is always true.
*/
fun isOnWallpaperDisplay(activity: Activity): Boolean {
return activity.display?.uniqueId == getWallpaperDisplay().uniqueId
}
/** Gets the real width and height of the display. */
fun getRealSize(display: Display): Point {
val displayInfo = DisplayInfo()
display.getDisplayInfo(displayInfo)
return Point(displayInfo.logicalWidth, displayInfo.logicalHeight)
}
/**
* Returns the smallest display on a device
*
* For foldable devices, this method will return the outer display or the primary display when
* the device is folded. This is always the smallest display in foldable devices.
*/
fun getSmallerDisplay(): Display {
val internalDisplays = getInternalDisplays()
val largestDisplay = getWallpaperDisplay()
val smallestDisplay = internalDisplays.firstOrNull() { it != largestDisplay }
return smallestDisplay ?: largestDisplay
}
private fun getRealArea(display: Display): Int {
val displayInfo = DisplayInfo()
display.getDisplayInfo(displayInfo)
return displayInfo.logicalHeight * displayInfo.logicalWidth
}
private fun getInternalDisplays(): List<Display> {
val allDisplays: Array<out Display> =
displayManager.getDisplays(DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED)
if (allDisplays.isEmpty()) {
Log.e(TAG, "No displays found on context ${context.applicationContext}")
throw RuntimeException("No displays found!")
}
return allDisplays.filter { it.type == Display.TYPE_INTERNAL }
}
fun getInternalDisplaySizes(): List<Point> {
return getInternalDisplays().map { getRealSize(it) }
}
}