Commit 8681028b authored by Sergey Galin's avatar Sergey Galin

Merge branch 'feature/hiding_navbar_support' into 'master'

Hiding navbar support in KeyboardHeightProvider

See merge request qt/qtandroidextensions!96
parents 6aefaa1d dd00bfc8
...@@ -37,6 +37,7 @@ ...@@ -37,6 +37,7 @@
package ru.dublgis.androidhelpers; package ru.dublgis.androidhelpers;
public interface KeyboardHeightObserver public interface KeyboardHeightObserver
{ {
void onKeyboardHeightChanged(int height); void onKeyboardHeightChanged(int height);
......
/* /*
Offscreen Android Views library for Qt Offscreen Android Views library for Qt
Author: Authors:
Timur N. Artikov <t.artikov@gmail.com> Timur N. Artikov <t.artikov@gmail.com>
Sergey A. Galin <sergey.galin@gmail.com>
Distrbuted under The BSD License Distrbuted under The BSD License
Copyright (c) 2018, DoubleGIS, LLC. Copyright (c) 2019, DoubleGIS, LLC.
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
...@@ -38,34 +39,42 @@ ...@@ -38,34 +39,42 @@
package ru.dublgis.androidhelpers; package ru.dublgis.androidhelpers;
import android.app.Activity; import android.app.Activity;
import android.content.res.Configuration;
import android.graphics.Point; import android.graphics.Point;
import android.graphics.Rect; import android.graphics.Rect;
import android.os.Build;
import android.util.DisplayMetrics;
import android.view.Display;
import android.view.Gravity; import android.view.Gravity;
import android.view.View; import android.view.View;
import android.view.ViewTreeObserver; import android.view.ViewTreeObserver;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams; import android.view.WindowManager.LayoutParams;
import android.widget.PopupWindow; import android.widget.PopupWindow;
/* /*
The keyboard height provider. The keyboard height provider.
It uses a PopupWindow to calculate the window height when the floating keyboard is opened and closed. It uses a PopupWindow to calculate the window height when the floating keyboard is opened and closed.
*/ */
public class KeyboardHeightProvider public class KeyboardHeightProvider
extends PopupWindow extends PopupWindow
implements ViewTreeObserver.OnGlobalLayoutListener implements ViewTreeObserver.OnGlobalLayoutListener
{ {
public static final String TAG = "Grym/KeyboardHeightProvider"; public static final String TAG = "Grym/KeyboardHeightProvider";
private KeyboardHeightObserver mObserver; private KeyboardHeightObserver mObserver = null;
private View mPopupView; private View mPopupView = null;
private Activity mActivity; private Activity mActivity = null;
public KeyboardHeightProvider(Activity activity, View parentView, KeyboardHeightObserver observer)
public KeyboardHeightProvider(
final Activity activity,
final View parentView,
final KeyboardHeightObserver observer)
{ {
super(activity); super(activity);
try try {
{
mActivity = activity; mActivity = activity;
mObserver = observer; mObserver = observer;
...@@ -81,47 +90,103 @@ public class KeyboardHeightProvider ...@@ -81,47 +90,103 @@ public class KeyboardHeightProvider
mPopupView.getViewTreeObserver().addOnGlobalLayoutListener(this); mPopupView.getViewTreeObserver().addOnGlobalLayoutListener(this);
showAtLocation(parentView, Gravity.NO_GRAVITY, 0, 0); showAtLocation(parentView, Gravity.NO_GRAVITY, 0, 0);
} } catch (final Throwable e) {
catch (final Throwable e)
{
Log.e(TAG, "constructor exception: " + e); Log.e(TAG, "constructor exception: " + e);
} }
} }
public void stop()
{ public void stop() {
try try {
{
mPopupView.getViewTreeObserver().removeOnGlobalLayoutListener(this); mPopupView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
dismiss(); dismiss();
} } catch (final Throwable e) {
catch (final Throwable e)
{
Log.e(TAG, "stop exception: " + e); Log.e(TAG, "stop exception: " + e);
} }
} }
@Override
public void onGlobalLayout()
{
try
{
Point screenSize = new Point();
mActivity.getWindowManager().getDefaultDisplay().getSize(screenSize);
Rect rect = new Rect(); private int nominalScreenHeight() {
mPopupView.getWindowVisibleDisplayFrame(rect); final Point screenSize = new Point();
mActivity.getWindowManager().getDefaultDisplay().getSize(screenSize);
return screenSize.y;
}
private int visibleDisplayFrameHeight() {
final Rect rect = new Rect();
mPopupView.getWindowVisibleDisplayFrame(rect);
return rect.bottom;
}
int keyboardHeight = screenSize.y - rect.bottom; private int getKeyboardSize() {
// keyboardHeight can be negative, when navigation panel is hided int keyboardHeight = 0;
// screenSize doesn't take into account the possibility of hiding panel try {
final int screenHeight = nominalScreenHeight();
keyboardHeight = screenHeight - visibleDisplayFrameHeight();
if (keyboardHeight == 0
|| !SystemNavigationBarInfo.deviceMayHaveFullscreenMode(mActivity) // Fast (cached)
|| mActivity.getResources().getConfiguration().orientation ==
Configuration.ORIENTATION_LANDSCAPE)
{
return keyboardHeight;
}
// Below are additional calculations and workarounds for devices that have dynamically
// shown and hidden navigation panels.
// These panels have non-standard implementation by Samsung, Huawei, ViVo and some
// others so the code to support them all is quite weird and may cause problems
// in the future. But we really have no choice.
/*
Log.d(TAG"getNavigationBarHeightFromConfiguration=" +
SystemNavigationBarInfo.getNavigationBarHeightFromConfiguration(
Configuration.ORIENTATION_PORTRAIT) +
", screenHeight=" + screenHeight +
", isInFullscreenMode=" + SystemNavigationBarInfo.isInFullscreenMode(mActivity) +
", hasVerticalNavBarSpace=" +
SystemNavigationBarInfo.hasVerticalNavBarSpace(mActivity) +
", getActualNavigationBarControlHeight=" +
SystemNavigationBarInfo.getActualNavigationBarControlHeight(mActivity) +
", deviceMayHaveFullscreenMode=" +
SystemNavigationBarInfo.deviceMayHaveFullscreenMode(mActivity));
*/
final boolean fullscreen = SystemNavigationBarInfo.isInFullscreenMode(mActivity);
final boolean hasNavBarSpace = SystemNavigationBarInfo.hasVerticalNavBarSpace(mActivity);
// Correcting for "control is too high above the keyboard" case
if (!fullscreen && !hasNavBarSpace) {
return keyboardHeight - SystemNavigationBarInfo.getActualNavigationBarControlHeight(mActivity);
}
// Correcting for "control is partially or fully covered by the keyboard" case
if (fullscreen &&
hasNavBarSpace &&
SystemNavigationBarInfo.getActualNavigationBarControlHeight(mActivity) == 0)
{
return keyboardHeight + SystemNavigationBarInfo.getNavigationBarHeightFromConfiguration(
Configuration.ORIENTATION_PORTRAIT);
}
} catch (final Throwable e) {
Log.e(TAG, "getKeyboardSize exception: " + e);
}
return keyboardHeight;
}
@Override
public void onGlobalLayout()
{
try {
int keyboardHeight = getKeyboardSize();
if (keyboardHeight < 0) { if (keyboardHeight < 0) {
Log.e(TAG, "Invalid keyboard height calculated: " + keyboardHeight);
keyboardHeight = 0; keyboardHeight = 0;
} }
mObserver.onKeyboardHeightChanged(keyboardHeight); mObserver.onKeyboardHeightChanged(keyboardHeight);
} } catch (final Throwable e) {
catch (final Throwable e)
{
Log.e(TAG, "onGlobalLayout exception: " + e); Log.e(TAG, "onGlobalLayout exception: " + e);
} }
} }
......
...@@ -42,8 +42,8 @@ import android.view.ViewTreeObserver; ...@@ -42,8 +42,8 @@ import android.view.ViewTreeObserver;
import android.view.Window; import android.view.Window;
public class ScreenLayoutHandler implements public class ScreenLayoutHandler implements
ViewTreeObserver.OnGlobalLayoutListener, ViewTreeObserver.OnGlobalLayoutListener,
ViewTreeObserver.OnScrollChangedListener, ViewTreeObserver.OnScrollChangedListener,
KeyboardHeightObserver KeyboardHeightObserver
{ {
...@@ -51,6 +51,7 @@ public class ScreenLayoutHandler implements ...@@ -51,6 +51,7 @@ public class ScreenLayoutHandler implements
private volatile long native_ptr_ = 0; private volatile long native_ptr_ = 0;
private KeyboardHeightProvider mKeyboardHeightProvider; private KeyboardHeightProvider mKeyboardHeightProvider;
public ScreenLayoutHandler(long native_ptr) public ScreenLayoutHandler(long native_ptr)
{ {
Log.i(TAG, "ScreenLayoutHandler constructor"); Log.i(TAG, "ScreenLayoutHandler constructor");
...@@ -58,6 +59,7 @@ public class ScreenLayoutHandler implements ...@@ -58,6 +59,7 @@ public class ScreenLayoutHandler implements
subscribeToLayoutEvents(); subscribeToLayoutEvents();
} }
//! Called from C++ to notify us that the associated C++ object is being destroyed. //! Called from C++ to notify us that the associated C++ object is being destroyed.
public void cppDestroyed() public void cppDestroyed()
{ {
...@@ -65,6 +67,7 @@ public class ScreenLayoutHandler implements ...@@ -65,6 +67,7 @@ public class ScreenLayoutHandler implements
native_ptr_ = 0; native_ptr_ = 0;
} }
public void subscribeToLayoutEvents() public void subscribeToLayoutEvents()
{ {
runOnUiThread(new Runnable(){ runOnUiThread(new Runnable(){
...@@ -92,6 +95,7 @@ public class ScreenLayoutHandler implements ...@@ -92,6 +95,7 @@ public class ScreenLayoutHandler implements
}); });
} }
public void unsubscribeFromLayoutEvents() public void unsubscribeFromLayoutEvents()
{ {
runOnUiThread(new Runnable(){ runOnUiThread(new Runnable(){
...@@ -120,24 +124,28 @@ public class ScreenLayoutHandler implements ...@@ -120,24 +124,28 @@ public class ScreenLayoutHandler implements
}); });
} }
@Override @Override
public void onGlobalLayout() public void onGlobalLayout()
{ {
nativeGlobalLayoutChanged(native_ptr_); nativeGlobalLayoutChanged(native_ptr_);
} }
@Override @Override
public void onScrollChanged() public void onScrollChanged()
{ {
nativeScrollChanged(native_ptr_); nativeScrollChanged(native_ptr_);
} }
@Override @Override
public void onKeyboardHeightChanged(int height) public void onKeyboardHeightChanged(int height)
{ {
nativeKeyboardHeightChanged(native_ptr_, height); nativeKeyboardHeightChanged(native_ptr_, height);
} }
private View getDecorView() private View getDecorView()
{ {
final Activity context = getActivity(); final Activity context = getActivity();
...@@ -155,6 +163,7 @@ public class ScreenLayoutHandler implements ...@@ -155,6 +163,7 @@ public class ScreenLayoutHandler implements
return window.getDecorView(); return window.getDecorView();
} }
final public boolean runOnUiThread(final Runnable runnable) final public boolean runOnUiThread(final Runnable runnable)
{ {
try try
...@@ -180,6 +189,7 @@ public class ScreenLayoutHandler implements ...@@ -180,6 +189,7 @@ public class ScreenLayoutHandler implements
} }
} }
public native Activity getActivity(); public native Activity getActivity();
public native void nativeGlobalLayoutChanged(long nativeptr); public native void nativeGlobalLayoutChanged(long nativeptr);
public native void nativeScrollChanged(long nativeptr); public native void nativeScrollChanged(long nativeptr);
......
/*
Offscreen Android Views library for Qt
Author:
Sergey A. Galin <sergey.galin@gmail.com>
Distrbuted under The BSD License
Copyright (c) 2019, DoubleGIS, LLC.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the DoubleGIS, LLC nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS
BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
THE POSSIBILITY OF SUCH DAMAGE.
*/
package ru.dublgis.androidhelpers;
import android.app.Activity;
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Point;
import android.os.Build;
import android.provider.Settings;
import android.util.DisplayMetrics;
import android.view.Display;
import android.view.View;
import android.view.WindowManager;
public class SystemNavigationBarInfo {
private static final String TAG = "Grym/SystemNavBarInfo";
private static volatile boolean mDeviceMayHaveFullscreenModeCache = false;
private static volatile boolean mDeviceMayHaveFullscreenModeCacheSet = false;
// Check if application is running in full screen mode (i.e. nav bar is hidden).
// Uses vendor-specific code so may not work properly on future devices.
// Call deviceMayHaveFullscreenMode(Context) first for optimized behaviour.
public static boolean isInFullscreenMode(final Context context) {
if (Build.VERSION.SDK_INT < 21) {
return false;
}
try {
final ContentResolver cr = context.getContentResolver();
return
// Huawei
Settings.System.getInt(cr, "navigationbar_is_min", 0) == 1 ||
Settings.Global.getInt(cr, "navigationbar_is_min", 0) == 1 ||
// Samsung
Settings.System.getInt(cr, "navigationbar_hide_bar_enabled", 0) == 1 ||
Settings.Global.getInt(cr, "navigationbar_hide_bar_enabled", 0) == 1 ||
// Xiaomi MIUI
Settings.Global.getInt(cr, "force_fsg_nav_bar", 0) != 0 ||
// ViVo
Settings.Secure.getInt(cr, "navigation_gesture_on", 0) != 0 ||
// Other
"immersive.navigation=*".equals(Settings.System.getString(cr, "policy_control")) ||
"immersive.navigation=*".equals(Settings.Global.getString(cr, "policy_control"));
} catch (final Throwable e) {
Log.e(TAG, "getFullscreenMode exception: ", e);
return false;
}
}
// Check if real screen size is bigger than nominal screen size, e.g. contains
// additional system panels not included into the nominal size.
// This may also return true for phones with non-hiding nav bar,
// and also when device is emulating smaller display (adb shell wm size).
// Some devices like Huawei P20 may not allocate nav bar space when activity is started
// with hidden nav bar but may (or may not) resize the screen later.
public static boolean hasVerticalNavBarSpace(final Activity activity) {
try {
if (Build.VERSION.SDK_INT < 17) { // 4.2
return false;
}
final Display display = activity.getWindowManager().getDefaultDisplay();
final DisplayMetrics displayMetrics = new DisplayMetrics();
final DisplayMetrics realDisplayMetrics = new DisplayMetrics();
display.getMetrics(displayMetrics);
display.getRealMetrics(realDisplayMetrics);
return realDisplayMetrics.heightPixels > displayMetrics.heightPixels;
} catch (final Throwable e) {
Log.e(TAG, "hasVerticalNavBarSpace exception: ", e);
return false;
}
}
// Get height of the navigation bar by looking up "navigationBarBackground" control.
// The function may return 0 if the nav bar is hidden.
// This function may break in some future version of Android.
public static int getActualNavigationBarControlHeight(final Activity activity) {
try {
final View bar = activity.getWindow().getDecorView().findViewById(
android.R.id.navigationBarBackground);
if (bar != null) {
return bar.getMeasuredHeight();
}
} catch (final Throwable e) {
Log.e(TAG, "getActualNavigationBarControlHeight exception: ", e);
}
return 0;
}
// Get navigation bar height from system configuration.
// Works on "normal" devices with fixed nav bar as well.
// This function may break in some future version of Android.
public static int getNavigationBarHeightFromConfiguration(final int orientation) {
try {
final int resourceId = Resources.getSystem().getIdentifier(
(orientation == Configuration.ORIENTATION_LANDSCAPE)
? "navigation_bar_height_landscape"
: "navigation_bar_height",
"dimen",
"android");
if (resourceId > 0) {
return Resources.getSystem().getDimensionPixelSize(resourceId);
}
} catch (final Resources.NotFoundException e) {
Log.d(TAG, "getNavigationBarHeightFromConfiguration: key not found.");
} catch (final Throwable e) {
Log.e(TAG, "getNavigationBarHeightFromConfiguration exception: ", e);
}
return 0;
}
// Detecting device that may have full screen mode (collapsible nav panel).
// This is just a fast heuristic-based check that currently looks solely on the aspect ratio
// of the screen and Android version. It will work improperly if someone release
// a device with wide screen and collapsible navbar. TODO...
public static boolean deviceMayHaveFullscreenMode(final Context context) {
if (mDeviceMayHaveFullscreenModeCacheSet) {
return mDeviceMayHaveFullscreenModeCache;
}
if (Build.VERSION.SDK_INT < 21) { // 5.0
mDeviceMayHaveFullscreenModeCache = false;
mDeviceMayHaveFullscreenModeCacheSet = true;
return false;
}
// Assuming that so far only the devices with very high aspect ratio screen may have
// a hiding nav bar. This check may not work properly for some future devices.
try {
final Point sz = new Point();
((WindowManager)context.getSystemService(Context.WINDOW_SERVICE))
.getDefaultDisplay().getRealSize(sz);
if (((sz.x >= sz.y) ? sz.x / sz.y : sz.y / sz.x) > 1.97f) {
mDeviceMayHaveFullscreenModeCache = true;
}
} catch (final Throwable e) {
Log.e(TAG, "deviceMayHaveFullscreenMode exception: ", e);
}
mDeviceMayHaveFullscreenModeCacheSet = true;
return mDeviceMayHaveFullscreenModeCache;
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment