I want to calculate the resolution of android phones. In some phones have navigation bar and some phone have not navigation bar. When I run this code in android devices then I'm getting exact resolution 1520x720 but In other devices it returns 1461x720(exact resolution 1480x720) so how to get resolution in all devices.
fun test(view: View) {
val height= getScreenHeight()
val width=getScreenWidth()
Log.d("res",(height+getNavigationBarHeight(this)).toString()+"x"+width.toString())
}
// for getting screen width
fun getScreenWidth(): Int {
return Resources.getSystem().getDisplayMetrics().widthPixels
}
// for getting screen height
fun getScreenHeight(): Int {
return Resources.getSystem().getDisplayMetrics().heightPixels
}
// for getting navigation bar height
private fun getNavSize() : Int{
val resources: Resources = applicationContext.resources
val resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android")
return if (resourceId > 0) {
resources.getDimensionPixelSize(resourceId)
} else 0
}
Related
bitmap?.let {
cropimg = Bitmap.createBitmap(
it,
0,
56+28,//each mobile has different height of actionbar and status bar
it.width,
it.height -(56+28)
)
}
I am taking Screenshot of mobile screen and I am getting bitmap as a result
I want to crop the status bar or navigation bar from that bitmap
How to do that? I try above solution but it is not accurate.
Step 1: Get the dynamic height of the status bar
fun getStatusBarHeight(context: Context): Int {
val resources: Resources = context.resources
val resourceId: Int = resources.getIdentifier("status_bar_height", "dimen", "android")
return if (resourceId > 0) resources.getDimensionPixelSize(resourceId) else 0
}
Step 2: Point out the starting point of your new bitmap like this
val statusbarHeight = getStatusBarHeight(this)
bitmap?.let {
cropimg = Bitmap.createBitmap(
it,
0,
0 + statusbarHeight,
it.width,
it.height
)
}
Happy Conding! :)
I need the width of the screen. But recently found Android defaultDisplay deprecacted with message:
Getter for defaultDisplay: Display!' is deprecated. Deprecated in Java
Code:
val displayMetrics = DisplayMetrics()
windowManager.defaultDisplay.getMetrics(displayMetrics)
return displayMetrics.widthPixels
Please suggest an alternative.
defaultDisplay was marked as deprecated in API level 30 (Android R) and above.
This means if you have a minimum SDK configuration below API level 30, you should have both implementations with the old deprecated code and the new recommended code.
After fixing the problem correctly you can use #Suppress("DEPRECATION") to suppress warnings
Example: Kotlin solution
val outMetrics = DisplayMetrics()
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) {
val display = activity.display
display?.getRealMetrics(outMetrics)
} else {
#Suppress("DEPRECATION")
val display = activity.windowManager.defaultDisplay
#Suppress("DEPRECATION")
display.getMetrics(outMetrics)
}
WindowManager.getDefaultDisplay() was deprecated in API level 30 in favour of Context.getDisplay() method which requires minimum API level 30.
At the moment, androidx.core.content.ContextCompat doesn't seem to offer any backwards compatible getDisplay() method.
If you only need to retrieve the default display, instead of using different methods for different API levels as other answers suggest, you can you DisplayManager.getDisplay(Display.DEFAULT_DISPLAY) method (supported since API 17) to achieve the same result.
Deprecated code:
val defaultDisplay = getSystemService<WindowManager>()?.defaultDisplay
New code:
val defaultDisplay = getSystemService<DisplayManager>()?.getDisplay(Display.DEFAULT_DISPLAY)
Ref: androidx.core source code
If what you need is to get the size of the Window, the new Jetpack WindowManager library provides a common API surface for new Window Manager features (e.g. foldable devices and Chrome OS) throughout old and new platform versions.
dependencies {
implementation "androidx.window:window:1.0.0-beta02"
}
Jetpack WindowManager offers two ways to retrieve WindowMetrics information, as an asynchronous stream or synchronously.
Asynchronous WindowMetrics flow:
Use WindowInfoRepository#currentWindowMetrics to get notified by the library when there’s a window size change, independent of whether this change fires a configuration change.
import androidx.window.layout.WindowInfoRepository
import androidx.window.layout.WindowInfoRepository.Companion.windowInfoRepository
import androidx.window.layout.WindowMetrics
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.flowWithLifecycle
lifecycleScope.launch(Dispatchers.Main) {
windowInfoRepository().currentWindowMetrics.flowWithLifecycle(lifecycle)
.collect { windowMetrics: WindowMetrics ->
val currentBounds = windowMetrics.bounds // E.g. [0 0 1350 1800]
val width = currentBounds.width()
val height = currentBounds.height()
}
}
Note: lifecycleScope and flowWithLifecycle() are part of Jetpack Lifecycle library.
Synchronous WindowMetrics:
Use WindowMetricsCalculator when writing code in a view where the asynchronous API can be too hard to deal with (such as onMeasure or during testing).
import androidx.window.layout.WindowMetricsCalculator
import androidx.window.layout.WindowMetrics
val windowMetrics = WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(activity)
val currentBounds = windowMetrics.bounds // E.g. [0 0 1350 1800]
val width = currentBounds.width()
val height = currentBounds.height()
Ref: Unbundling the WindowManager | Android Developers Medium
This method was deprecated in API level 30.
Use Context.getDisplay() instead.
Deprecated method: getDefaultDisplay
New Method: getDisplay
Try something like this:
private Display getDisplay(#NonNull WindowManager windowManager) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
// This one (context) may or may not have a display associated with it, due to it being
// an application context
return getDisplayPostR();
} else {
return getDisplayPreR(windowManager);
}
}
#RequiresApi(api = Build.VERSION_CODES.R)
private Display getDisplayPostR() {
// We can't get the WindowManager by using the context we have, because that context is a
// application context, which isn't associated with any display. Instead, grab the default
// display, and create a DisplayContext, from which we can use the WindowManager or
// just get that Display from there.
//
// Note: the default display doesn't have to be the one where the app is on, however the
// getDisplays which returns a Display[] has a length of 1 on Pixel 3.
//
// This gets rid of the exception interrupting the onUserLogin() method
Display defaultDisplay = DisplayManagerCompat.getInstance(context).getDisplay(Display.DEFAULT_DISPLAY);
Context displayContext = context.createDisplayContext(defaultDisplay);
return displayContext.getDisplay();
}
#SuppressWarnings("deprecation")
private Display getDisplayPreR(#NonNull WindowManager windowManager) {
return windowManager.getDefaultDisplay();
}
or to get the actual size:
private Point getScreenResolution() {
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
if (wm == null) {
return null;
}
Display display = getDisplay(wm);
return getSize(display, wm);
}
private Point getSize(Display forWhichDisplay, WindowManager windowManager) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
return getSizePostR(windowManager);
} else {
return getSizePreR(forWhichDisplay);
}
}
#RequiresApi(api = Build.VERSION_CODES.R)
private Point getSizePostR(#NonNull WindowManager windowManager) {
WindowMetrics currentWindowMetrics = windowManager.getCurrentWindowMetrics();
Rect bounds = currentWindowMetrics.getBounds();
// Get the insets, such as titlebar and other decor views
WindowInsets windowInsets = currentWindowMetrics.getWindowInsets();
Insets insets = windowInsets.getInsets(WindowInsets.Type.navigationBars());
// If cutouts exist, get the max of what we already calculated and the system's safe insets
if (windowInsets.getDisplayCutout() != null) {
insets = Insets.max(
insets,
Insets.of(
windowInsets.getDisplayCutout().getSafeInsetLeft(),
windowInsets.getDisplayCutout().getSafeInsetTop(),
windowInsets.getDisplayCutout().getSafeInsetRight(),
windowInsets.getDisplayCutout().getSafeInsetBottom()
)
);
}
// Calculate the inset widths/heights
int insetsWidth = insets.right + insets.left;
int insetsHeight = insets.top + insets.bottom;
// Get the display width
int displayWidth = bounds.width() - insetsWidth;
int displayHeight = bounds.height() - insetsHeight;
return new Point(displayWidth, displayHeight);
}
// This was deprecated in API 30
#SuppressWarnings("deprecation")
private Point getSizePreR(Display display) {
Point size = new Point();
if (isRealDisplaySizeAvailable()) {
display.getRealSize(size);
} else {
display.getSize(size);
}
return size;
}
private static boolean isRealDisplaySizeAvailable() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1;
}
I use DisplayCompatManager to get width and height on android R-Above and use DisplayMatrics to get it on other android version.
So, this is my code ( + #Suppress("DEPRECATION") )
private fun screenValue() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val defaultDisplay =
DisplayManagerCompat.getInstance(this).getDisplay(Display.DEFAULT_DISPLAY)
val displayContext = createDisplayContext(defaultDisplay!!)
width = displayContext.resources.displayMetrics.widthPixels
height = displayContext.resources.displayMetrics.heightPixels
Log.e(tag, "width (ANDOIRD R/ABOVE): $width")
Log.e(tag, "height (ANDOIRD R/ABOVE) : $height")
} else {
val displayMetrics = DisplayMetrics()
#Suppress("DEPRECATION")
windowManager.defaultDisplay.getMetrics(displayMetrics)
height = displayMetrics.heightPixels
width = displayMetrics.widthPixels
Log.e(tag, "width (BOTTOM ANDROID R): $width")
Log.e(tag, "height (BOTTOM ANDROID R) : $height")
}
}
If you want see my gist in github
In Api Level 31 method Display.getRealMetrics() was deprecated as well. The recommended way is to use WindowManager#getCurrentWindowMetrics(). I prefer the following approach to get screen size:
object ScreenSizeCompat {
private val api: Api =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) ApiLevel30()
else Api()
/**
* Returns screen size in pixels.
*/
fun getScreenSize(context: Context): Size = api.getScreenSize(context)
#Suppress("DEPRECATION")
private open class Api {
open fun getScreenSize(context: Context): Size {
val display = context.getSystemService(WindowManager::class.java).defaultDisplay
val metrics = if (display != null) {
DisplayMetrics().also { display.getRealMetrics(it) }
} else {
Resources.getSystem().displayMetrics
}
return Size(metrics.widthPixels, metrics.heightPixels)
}
}
#RequiresApi(Build.VERSION_CODES.R)
private class ApiLevel30 : Api() {
override fun getScreenSize(context: Context): Size {
val metrics: WindowMetrics = context.getSystemService(WindowManager::class.java).currentWindowMetrics
return Size(metrics.bounds.width(), metrics.bounds.height())
}
}
}
And to get, for example, screen height use it in Activity:
ScreenSizeCompat.getScreenSize(this).height
Anyone looking to do it in java here you go:
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) {
Display display = this.getDisplay();
DisplayMetrics displayMetrics = new DisplayMetrics();
display.getRealMetrics(displayMetrics);
float density = getResources().getDisplayMetrics().density;
float dpHeight = displayMetrics.heightPixels / density;
float dpWidth = displayMetrics.widthPixels / density;
Log.d(TAG, "OmesChecka R: "+"second width:"+dpWidth+"second h:"+dpHeight);
}else {
Display display = getWindowManager().getDefaultDisplay();
DisplayMetrics outMetrics = new DisplayMetrics ();
display.getMetrics(outMetrics);
float density = getResources().getDisplayMetrics().density;
float dpHeight = outMetrics.heightPixels / density;
float dpWidth = outMetrics.widthPixels / density;
Log.d(TAG, "OmesChecka: "+"second width:"+dpWidth+"second h:"+dpHeight);
}
Since I had to go to several places to get the complete answer, here it is in one post:
getDefaultDisplay method is depreciated as of Android API 30 (Android 11), but still works.
Even so, because it's depreciated you should use the new method for Android 11, and still implement the old method for Android 10 or earlier.
Here's the code, with comments: (To run this you need viewBinding enabled in your build.gradle (since I'm not using findViewByID), an activity_main.xml with your top level layout named "main_activity", a button view named "calculate_button", and three text views named "android_version", "screen_height" and "screen_width".)
package com.example.test
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.DisplayMetrics
import android.view.WindowManager
import com.example.test.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.calculateButton.setOnClickListener { calcScreenSize() }
}
private fun calcScreenSize() { //All in one function to calculate and display screen size in pixels
val metrics = DisplayMetrics()
var width: Int = 0
var height: Int = 0
val version = android.os.Build.VERSION.SDK_INT //Check android version. Returns API # ie 29 (Android 10), 30 (Android 11)
if (version >= android.os.Build.VERSION_CODES.R) { //If API is version 30 (Android 11) or greater, use the new method for getting width/height
binding.androidVersion.setText("Android API Version: $version")
binding.mainActivity.display?.getRealMetrics(metrics) //New method
width = metrics.widthPixels
height = metrics.heightPixels
binding.screenHeight.setText("Height: $height") //Display Width and Height in Text Views
binding.screenWidth.setText("Width: $width")
} else {
binding.androidVersion.setText("Android API Version: $version") //If API is less than version 30 (ie 29 (Android 10)), use the old method for getting width/height
#Suppress("DEPRECATION") //Suppress the "Deprecation" warning
windowManager.defaultDisplay.getMetrics(metrics) //Old method
width = metrics.widthPixels
height = metrics.heightPixels
binding.screenHeight.setText("Height: $height") //Display Width and Height in Text Views
binding.screenWidth.setText("Width: $width")
}
}
}
Here is a java solution attempting the least amount of code, imports, and further depreciated methods as possible. I leave potential device insets in my game to keep the view as large as possible. However, the commented code will shorten width and height for insets.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
WindowMetrics windowMetrics = ((Activity) context).getWindowManager().getCurrentWindowMetrics();
//Insets insets = windowMetrics.getWindowInsets().getInsetsIgnoringVisibility(WindowInsets.Type.systemBars());
screenWidth = windowMetrics.getBounds().width();// - insets.left - insets.right;
screenHeight = windowMetrics.getBounds().height();// - insets.top - insets.bottom;
} else {
Display display = ((Activity) context).getWindowManager().getDefaultDisplay();//getDefaultDisplay depreciated getMetrics() depreciated
Point size = new Point();
display.getSize(size);
screenWidth = size.x;
screenHeight = size.y;
try {
Point realSize = new Point();
Display.class.getMethod("getRealSize", Point.class).invoke(display, realSize);
screenWidth = realSize.x;
screenHeight = realSize.y;
} catch (Exception ignored) {
}
}
I know there are already a bunch of questions related to the "trying to use a recycled bitmap" crash, but none helped me.
Details:
There are no calls to Bitmap.recycle() anywhere in this project
All images are loaded using Glide (4.11.0)
Glide calls are all simple, and not using placeholders
The crash seems to happen randomly when switching fragments
Only thing I could think of were the transformations.
There are only 2 transformations in this project.
CircleTransformation (clip image as a circle with a custom radius):
class CircleTransformation(private val radius: Float) : BitmapTransformation() {
companion object {
private const val ID = "com.project.transformation.circle"
private val ID_BYTES: ByteArray = ID.toByteArray()
}
public override fun transform(pool: BitmapPool, source: Bitmap, outWidth: Int, outHeight: Int): Bitmap {
val paint = Paint()
paint.isAntiAlias = true
paint.shader = BitmapShader(source, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
val halfWidth = source.width / 2f
val output = Bitmap.createBitmap(source.width, source.height, Bitmap.Config.ARGB_8888)
val canvas = Canvas(output)
canvas.drawCircle(
halfWidth,
(source.height / 2).toFloat(),
halfWidth * radius,
paint
)
return output
}
// Caching helpers
override fun equals(other: Any?): Boolean {
return other is CircleTransformation && other.hashCode() == hashCode()
}
override fun hashCode(): Int {
return ID.hashCode()
}
override fun updateDiskCacheKey(messageDigest: MessageDigest) {
messageDigest.update(ID_BYTES)
}
}
And ClipWhiteTransformation (remove white border from image):
class ClipWhiteTransformation() : BitmapTransformation() {
companion object {
private const val ID = "com.project.transformation.clipWhite"
private val ID_BYTES: ByteArray = ID.toByteArray()
// Config
const val white = 253 // White pixel, if all channels are equal or greater than this
const val transparent = 50 // Transparent pixel, if Less than this
}
public override fun transform(pool: BitmapPool, source: Bitmap, outWidth: Int, outHeight: Int): Bitmap {
val width = source.width - 1
val height = source.height - 1
val halfX = width / 2
val halfY = height / 2
var startY = 0
// Left Margin
var left = 0
for (x in 0 until halfX) {
val pixel = source.getPixel(x, halfY)
// Transparent?
if (Color.alpha(pixel) < transparent) continue
// Not white?
if (Color.red(pixel) < white || Color.green(pixel) < white || Color.blue(pixel) < white) {
left = x
if (x > 2) {
startY = 2
}
break
}
}
// Right Margin
var right = 0
for (x in 0 until halfX) {
val pixel = source.getPixel(width - x, halfY)
// Transparent?
if (Color.alpha(pixel) < transparent) continue
// Not white?
if (Color.red(pixel) < white || Color.green(pixel) < white || Color.blue(pixel) < white) {
right = x
if (x > 2) {
startY = 2
}
break
}
}
// Top Margin
var top = 0
for (y in startY until halfY) {
val pixel = source.getPixel(halfX, y)
// Transparent?
if (Color.alpha(pixel) < transparent) continue
// Not white?
if (Color.red(pixel) < white || Color.green(pixel) < white || Color.blue(pixel) < white) {
top = y
break
}
}
// Bottom Margin
var bottom = 0
for (y in startY until halfY) {
val pixel = source.getPixel(halfX, height - y)
// Transparent?
if (Color.alpha(pixel) < transparent) continue
// Not white?
if (Color.red(pixel) < white || Color.green(pixel) < white || Color.blue(pixel) < white) {
bottom = y
break
}
}
// Clip, scale and return
val newWidth = width - (left + right)
val newHeight = height - (top + bottom)
val scale = if (abs(newWidth - outWidth) > abs(newHeight - outHeight)) outWidth / newWidth.toFloat() else outHeight / newHeight.toFloat()
val matrix = Matrix().apply { setScale(scale, scale) }
return Bitmap.createBitmap(source, left, top, newWidth, newHeight, matrix, false)
}
// Caching helpers
override fun equals(other: Any?): Boolean {
return other is ClipWhiteTransformation && other.hashCode() == hashCode()
}
override fun hashCode(): Int {
return ID.hashCode()
}
override fun updateDiskCacheKey(messageDigest: MessageDigest) {
messageDigest.update(ID_BYTES)
}
}
Was using the BitmapPool initially, removing it didn't stop the crash.
By the way, this is the extension used to load images:
fun ImageView.setURL(url: String,
#DrawableRes error: Int? = null,
#DrawableRes placeholder: Int? = null,
size: Int? = null,
options: ((RequestBuilder<Drawable>) -> Unit)? = null,
completion: ((resource: Drawable?) -> Unit)? = null) {
// No URL, use Placeholder if exists, if not, use the error image
if (url.isEmpty()) {
placeholder?.also{ setImageResource(it) } ?: run { error?.also{ setImageResource(it) } }
return
}
Glide.with(applicationInstance) // (I'm using an application instance directly here)
.load(url).apply {
completion?.also { completion ->
this.listener(object : RequestListener<Drawable> {
override fun onLoadFailed(e: GlideException?, model: Any?, target: Target<Drawable>?, isFirstResource: Boolean): Boolean {
completion(null)
return false
}
override fun onResourceReady(resource: Drawable?, model: Any?, target: Target<Drawable>?, dataSource: DataSource?, isFirstResource: Boolean): Boolean {
completion(resource)
return false
}
})
}
}
.apply { size?.also { this.override(it)} }
.apply { options?.invoke(this) }
.placeholder(placeholder ?: 0)
.error(error ?: 0)
.transition(DrawableTransitionOptions.withCrossFade(350))
.into(this)
}
Sorry for pasting so much code here (hope it's useful to someone).
Can these transformations or loader cause the crash?
To shape(circle/square/oval) your image You do not need to Transform your image .
MaterialDesign has introduce ShapeableImageView which let you shape your image at runtime, also you can add border with color .
add matrial dependecies :
implementation 'com.google.android.material:material:1.3.0-alpha01'
Add shapeableImageView in your xyz.xml:
<com.google.android.material.imageview.ShapeableImageView
android:id="#+id/imgStudent"
android:layout_width="100dp"
android:layout_height="100dp"
app:shapeAppearanceOverlay="#style/circleImageView"
android:padding="2dp"
app:strokeColor="#color/white"
app:strokeWidth="5dp"
android:scaleType="centerCrop"
android:adjustViewBounds="true"
tools:srcCompat="#drawable/ic_kid_placeholder"
/>
Add style inside res/values/style.xml file
<style name="circleImageView" parent="">
<item name="cornerFamily">rounded</item>
<item name="cornerSize">50%</item>
<item name="android:shadowRadius">100</item>
<item name="android:shadowColor">#color/gray</item>
<item name="backgroundOverlayColorAlpha">12</item>
</style>
And at last load your image with glide .
The app I'm working on went through a testing process with physical devices. The testing team reported that
Huawei P8 Lite Android 5.1: If an image of more than 4MB is uploaded, the application closes unexpectedly
I tried to replicate the issue with a "Google Pixel 3a with Android 5.1" emulator, but the process went ok. The code for this upload process include a resizing method so that no matter the image you choose, it won't be >4MB when it reaches the server.
I need to either reproduce the error and find a way to fix the issue or do something to the code that prevents such a crash (we believe it's an Out Of Memory crash) no matter how old the device is.
We're supporting Android 21+
private fun resize(image: Bitmap, maxWidth: Int, maxHeight: Int): Bitmap {
var imageResized = image
if (maxHeight > 0 && maxWidth > 0) {
val width = imageResized.width
val height = imageResized.height
val ratioBitmap = width.toFloat() / height.toFloat()
val ratioMax = maxWidth.toFloat() / maxHeight.toFloat()
var finalWidth = maxWidth
var finalHeight = maxHeight
if (ratioMax > ratioBitmap) {
finalWidth = (maxHeight.toFloat() * ratioBitmap).toInt()
} else {
finalHeight = (maxWidth.toFloat() / ratioBitmap).toInt()
}
imageResized = Bitmap.createScaledBitmap(imageResized, finalWidth, finalHeight, true)
return imageResized
} else {
return imageResized
}
}
fun captureImageResult(requestCode: Int, data: Intent?) {
if (requestCode == SELECT_PICTURE) {
data?.data?.let {
mCapturedImageURI = data.data
}
}
mCapturedImageURI.let {
val msm = MediaStorageManager((view as NewBaseFragment).activity!!)
val bmp = msm.getBitmapFromUri(mCapturedImageURI!!)
//save image bytes
imageBytes = msm.getBytesFromBitmap(bmp!!)
val bitmapScaled = resize(bmp, 1024, 1024)
imageBytes = msm.getBytesFromBitmap(bitmapScaled)
//image too big
if (imageBytes?.size ?: IMAGE_MAX_SIZE < IMAGE_MAX_SIZE) {
//show image selected
view.renderImage(bitmapScaled)
} else {
view.showErrorDialog()
imageBytes = null
}
}
}
I want to know if a device has a soft navigation bar. I use the following code for this purpose:
val hasNavBar = resources.getIdentifier("config_showNavigationBar", "bool", "android")
if(hasNavBar > 0 && resources.getBoolean(hasNavBar)){
val resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android")
if (resourceId > 0) {
ApiHelper.navHeight = resources.getDimensionPixelSize(resourceId)
}
}
But on some devices that have no soft navigation bar, resources.getDimensionPixelSize(resourceId) gives some height and hasNavBar is true.
The device with this problem is a Redmi Note 4 Pro. I don't know if other devices have the same problem.
I found the answer.
There are some functions in stackOverflow that doesn't work, but this worked.
How to tell whether an android device has hard keys
fun hasSoftKeys(windowManager: WindowManager): Boolean {
var hasSoftwareKeys = true
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
val d = context.getWindowManager().getDefaultDisplay()
val realDisplayMetrics = DisplayMetrics()
d.getRealMetrics(realDisplayMetrics)
val realHeight = realDisplayMetrics.heightPixels
val realWidth = realDisplayMetrics.widthPixels
val displayMetrics = DisplayMetrics()
d.getMetrics(displayMetrics)
val displayHeight = displayMetrics.heightPixels
val displayWidth = displayMetrics.widthPixels
hasSoftwareKeys = realWidth - displayWidth > 0 || realHeight - displayHeight > 0x<
} else {
val hasMenuKey = ViewConfiguration.get(context).hasPermanentMenuKey()
val hasBackKey = KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK)
hasSoftwareKeys = !hasMenuKey && !hasBackKey
}
return hasSoftwareKeys
}