How to detect picture in popup dismissed (drag down to dismiss)? - android

We are using the calls page with a picture in picture(pip) feature.
Problem :
In case user dismiss the pip popup window (by drag down to dismiss) we not able to detect that.
How to detect picture in picture window drag down to dismiss?
Update:
Set picture in picture
// RemoteRenderLayout -> call preview layout. You can set anyone view
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
Rational aspectRatio = new Rational(
remoteRenderLayout.getWidth(), remoteRenderLayout.getHeight());
PictureInPictureParams params = new PictureInPictureParams.Builder()
.setAspectRatio(aspectRatio)
.build();
enterPictureInPictureMode(params);
}
else{
enterPictureInPictureMode();
}

When the activity enters or exits picture-in-picture mode the system calls Activity.onPictureInPictureModeChanged() or Fragment.onPictureInPictureModeChanged().
You can implement your code inside this:
override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean,
newConfig: Configuration) {
if (isInPictureInPictureMode) {
// Hide the full-screen UI (controls, etc.) while in picture-in-picture mode.
} else {
// Restore the full-screen UI.
}
}

Related

Weird issue with Samsung keyboard on Android with Libgdx

In my game, when a textfield is touched, the view moves up along with the keyboard.
Here's the code in AndroidLauncher:
onCreate(){
//other codes...
setListenerToRootView()
}
private fun setListenerToRootView() {
val activityRootView: View = window.decorView.findViewById(content)
activityRootView.viewTreeObserver.addOnGlobalLayoutListener(keyboardLayoutListener)
}
private var keyboardLayoutListener: OnGlobalLayoutListener? = OnGlobalLayoutListener {
val visibleDisplayFrame = Rect()
window.decorView.getWindowVisibleDisplayFrame(visibleDisplayFrame)
sizeChanged(visibleDisplayFrame.width(), visibleDisplayFrame.height())
}
override fun sizeChanged(width: Int, height: Int) {
val heightRatio = Gdx.graphics.height / main.worldHeight
val worldHeightChanged = height / heightRatio
val keyboardStatus = if (height == Gdx.graphics.height) KeyboardStatus.HIDE else KeyboardStatus.SHOW
main.platformsObservable.notifyObservers(Triple(ObservableKeys.SCREEN_SIZE_CHANGED, keyboardStatus, worldHeightChanged))
log.error("SCREEN_SIZE_CHANGED, status = $keyboardStatus")
}
The above code gets the keyboard height and status to send to my libgdx game class for the Camera to move the screen up/down.
With a normal keyboard it would send something like this for when the keyboard is shown:
SCREEN_SIZE_CHANGED, status = SHOW
and when the keyboard is hidden:
SCREEN_SIZE_CHANGED, status = HIDE
But on Samsung devices with the keyboard id of "com.samsung.android.honeyboard/.service.HoneyBoardService" then it does all this when the keyboard is shown once:
SCREEN_SIZE_CHANGED, status = HIDE
SCREEN_SIZE_CHANGED, status = SHOW
SCREEN_SIZE_CHANGED, status = SHOW
SCREEN_SIZE_CHANGED, status = SHOW
And this is making the keyboard blocking the textfield in my game because the view doesn't move up.
My gdxVersion is 1.11.0
How can I fix this?
Hi This isn't a libGDX issue. You need to provide parameters to your activity specifically saying what you want for this value (being the screen keyboard as opposed to a hardware one)
android:windowSoftInputMode
as described here
https://developer.android.com/guide/topics/manifest/activity-element.html#wsoft
and consider what you want for the parameters
adjustResize
adjustPan

jetpack compose Image on entire screen

I have a simple screen implemented with Jetpack Compose,
here is a preview of the screen.
There is a button like the sun on the TopAppBar which the user clicks to take a screenshot.
The problem is that I want to show the screenshot of the entire screen but without showing the status bar and the navigation bar.
Is there a solution to do this?
The composables are tied to a View which you can access with LocalView.current and through that View you can retrieve its .context and through that you can get to its Activity and to the Window object.
Once you have a reference to the Window (and to the View, if you have to support API level lower than 30), you can control status bar and navigation bar visibility.
A code example:
#Composable
fun MyFullScreenComposable() {
val view = LocalView.current
SideEffect {
requestFullScreen(view)
}
Box {
// ... other content
Button(
onClick = { requestFullScreen(view) }
) {
Text("Go fullscreen")
}
}
}
fun requestFullScreen(view: View) {
// !! should be safe here since the view is part of an Activity
val window = view.context.getActivity()!!.window
WindowCompat.getInsetsController(window, view).hide(
WindowInsetsCompat.Type.statusBars() or
WindowInsetsCompat.Type.navigationBars()
)
}
fun Context.getActivity(): Activity? = when (this) {
is Activity -> this
// this recursion should be okay since we call getActivity on a view context
// that should have an Activity as its baseContext at some point
is ContextWrapper -> baseContext.getActivity()
else -> null
}
In your case you might have to hide your action/title bar as well (or maybe you are already doing that).
Note that when the user taps on the screen while in full-screen mode, the bars will slide back. That's why I added a button as an example how you can let the user go back into full-screen mode through an action. In your case that could be a FloatingActionButton or some action on a semi-transparent top action bar.

How to detect android status bar visibility with android foreground service showing a sytem overlay?

I am running a foreground service which shows a system overlay. I want to detect change in status bar visibility.
So for example, if any video is being watched or any game is being played, the running activity is in full screen mode and status bar is hidden. But when a user swipes from top or any notification arrives, the status bar is visible.
I want to detect this changes in the status bar's visibility.
I have gone through this another post which discussed a similar use case. Not much help.
Detect Status Bar Visibility / TYPE_SYSTEM_OVERLAY not resizing automatically
I am using the code below to inflate my system overlay
private fun setupListenerToDetectStatusBar() {
val p = WindowManager.LayoutParams()
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O)
p.type = WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY
else
p.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
p.gravity = Gravity.RIGHT or Gravity.TOP
p.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
p.width = 1
p.height = ViewGroup.LayoutParams.MATCH_PARENT
p.format = PixelFormat.OPAQUE
helperWnd = View(ctx) //View helperWnd;
windowManager.addView(helperWnd, p)
Log.d("status-bar", "added-global-layout listener")
helperWnd?.viewTreeObserver?.addOnGlobalLayoutListener(layoutListener)
helperWnd?.setBackgroundColor(Color.BLACK)
helperWnd?.setOnSystemUiVisibilityChangeListener{
Log.d("status-bar", "UI visibility: $it $isFullScreen")
}
}
The GlobalLayoutListener is ineffective and doesn't detect any change while the systemUIVisibilityListener, however deprecated, detects the app going full screen either when the app is launched or the notification shade is open and closed.
I also tried detecting this by using Accessibility service.
The change in the visibility of status bar is not being detected by any of the methods.
I am able to detect all the other the events in the service.
val LOG_TAG = "MyAccessibilityService"
var recordScreen = false
override fun onAccessibilityEvent(event: AccessibilityEvent?) {
event?.let {
var canIgnore = false
val eventPackage = it.packageName
logger.log("onAccessibilityEvent: A event package $eventPackage")
}
}
Is there any other way this can be achieved?

How to check if the current activity has a dialog in front?

I am using a third-party library and sometimes it pops up a dialog. Before I finish the current activity, I want to check whether there is a dialog popped up in the current context.
Is there any API for this?
You can check it running over the active fragments of that activity and checking if one of them is DialogFragment, meaning that there's a active dialog on the screen:
public static boolean hasOpenedDialogs(FragmentActivity activity) {
List<Fragment> fragments = activity.getSupportFragmentManager().getFragments();
if (fragments != null) {
for (Fragment fragment : fragments) {
if (fragment instanceof DialogFragment) {
return true;
}
}
}
return false;
}
I faced a similar problem, and did not want to modify all locations where dialogs were being created and shown. My solution was to look at whether the view I was showing had window focus via the hasWindowFocus() method. This will not work in all situations, but worked in my particular case (this was for an internal recording app used under fairly restricted circumstances).
This solution was not thoroughly tested for robustness but I figured I would post in in case it helped somebody.
This uses reflection and hidden APIs to get the currently active view roots. If an alert dialog shows this will return an additional view root. But careful as even a toast popup will return an additional view root.
I've confirmed compatibility from Android 4.1 to Android 6.0 but of course this may not work in earlier or later Android versions.
I've not checked the behavior for multi-window modes.
#SuppressWarnings("unchecked")
public static List<ViewParent> getViewRoots() {
List<ViewParent> viewRoots = new ArrayList<>();
try {
Object windowManager;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
windowManager = Class.forName("android.view.WindowManagerGlobal")
.getMethod("getInstance").invoke(null);
} else {
Field f = Class.forName("android.view.WindowManagerImpl")
.getDeclaredField("sWindowManager");
f.setAccessible(true);
windowManager = f.get(null);
}
Field rootsField = windowManager.getClass().getDeclaredField("mRoots");
rootsField.setAccessible(true);
Field stoppedField = Class.forName("android.view.ViewRootImpl")
.getDeclaredField("mStopped");
stoppedField.setAccessible(true);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
List<ViewParent> viewParents = (List<ViewParent>) rootsField.get(windowManager);
// Filter out inactive view roots
for (ViewParent viewParent : viewParents) {
boolean stopped = (boolean) stoppedField.get(viewParent);
if (!stopped) {
viewRoots.add(viewParent);
}
}
} else {
ViewParent[] viewParents = (ViewParent[]) rootsField.get(windowManager);
// Filter out inactive view roots
for (ViewParent viewParent : viewParents) {
boolean stopped = (boolean) stoppedField.get(viewParent);
if (!stopped) {
viewRoots.add(viewParent);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return viewRoots;
}
AFAIK - there is no public API for this.
Recommended way is to have a reference to the dialog, and check for isShowing() and call dismiss() if necessary, but since you're using a third party library, this may not be an options for you.
Your best bet is to check the documentation for the library you use. If that doesn't help, you're out of luck.
Hint: Activity switches to 'paused' state if a dialog pops up. You may be able to 'abuse' this behavior ;)
You can override activity method onWindowFocusChanged(boolean hasFocus) and track the state of your activity.
Normally, if some alert dialog is shown above your activity, the activity does not get onPause() and onResume() events. But it loses focus on alert dialog shown and gains it when it dismisses.
For anyone reading this and wondering how to detect a Dialog above fragment or activity, my problem was that inside my base fragment I wanted to detect if I'm displaying a Dialog on top of my fragment. The dialog itself was displayed from my activity and I didn't want to reach it there, so the solution I came up with (Thanks to all answers related to this kind of question) was to get the view (or you can get the view.rootView) of my fragment and check whether any of its children have the focus or not. If none of its children have no focus it means that there is something (hopefully a Dialog) being displayed above my fragment.
// Code inside my base fragment:
val dialogIsDisplayed = (view as ViewGroup).children.any { it.hasWindowFocus() }
Solution in kotlin
Inside Fragment
val hasWindowFocus = activity?.hasWindowFocus()
In Activity
val hasWindowFocus = hasWindowFocus()
If true, there is no Dialog in the foreground
if FALSE , there is a view/dialog in the foreground and has focus.
I am assuming, you are dealing with third party library and you don't have access to dialog object.
You can get the root view from the activity,
Then you can use tree traversal algorithm to see if you can reach any of the child view. You should not reach any of your child view if alert box is displayed.
When alert view is displayed ( check with Ui Automator ), the only element present in UI tree are from DialogBox / DialogActivity. You can use this trick to see if dialog is displayed on the screen. Though it sounds expensive, it could be optimized.
If you are using Kotlin just:
supportFragmentManager.fragments.any { it is DialogFragment }

Preventing status bar expansion

Is there anyway to prevent users from sliding the status bar (expand) or collapsing back?
I'm trying out a lockscreen replacement and seems like it's a must-have feature. Is there any possible way to do it without requiring root privileges?
You can actually prevent the status bar from expanding, without rooting phone. See this link. This draws a window the height of the status bar and consumes all touch events.
Call the following code just after onCreate().
public static void preventStatusBarExpansion(Context context) {
WindowManager manager = ((WindowManager) context.getApplicationContext()
.getSystemService(Context.WINDOW_SERVICE));
Activity activity = (Activity)context;
WindowManager.LayoutParams localLayoutParams = new WindowManager.LayoutParams();
localLayoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
localLayoutParams.gravity = Gravity.TOP;
localLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE|
// this is to enable the notification to recieve touch events
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL |
// Draws over status bar
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
localLayoutParams.width = WindowManager.LayoutParams.MATCH_PARENT;
//https://stackoverflow.com/questions/1016896/get-screen-dimensions-in-pixels
int resId = activity.getResources().getIdentifier("status_bar_height", "dimen", "android");
int result = 0;
if (resId > 0) {
result = activity.getResources().getDimensionPixelSize(resId);
}
localLayoutParams.height = result;
localLayoutParams.format = PixelFormat.TRANSPARENT;
customViewGroup view = new customViewGroup(context);
manager.addView(view, localLayoutParams);
}
public static class customViewGroup extends ViewGroup {
public customViewGroup(Context context) {
super(context);
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
}
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.v("customViewGroup", "**********Intercepted");
return true;
}
}
Short answer: this is impossible!
Now should you be interested to know why this is impossible:
There are two permissions for manipulating the system status bar, EXPAND_STATUS_BAR and STATUS_BAR. The former can be requested by any application, but the later is reserved for applications signed with the platform signature (system applications, not third-party). It is possible to expand/ collapse the system status bar (see "How to open or expand status bar through intent?") but note that reflection is required because the StatusBarManager class is not part of the SDK. The disable method, which is used by the Android dialer to prevent the status bar from being expanded, cannot be accessed by an application without the aforementioned STATUS_BAR permission.
Sources: personal experience :-)
First of all, it's impossible to modify the Status Bar if your app is not signed with the phone's rom certified, if you try to modify it you'll get an Security Exception.
Update: In new APIs the method is "collapsePanels" instead of "collapse".
The only way I've found after several hours of work is by overriding the "onWindowFocusChanged" method of the activity and when it loses the focus (maybe the user has touched the notifications bar), force to collapse the StatusBar, here is the code (working on a Defy+ 2.3.5).
You need to declare the following permission on the manifest:
<uses-permission android:name="android.permission.EXPAND_STATUS_BAR"/>
And override the following method:
public void onWindowFocusChanged(boolean hasFocus)
{
try
{
if(!hasFocus)
{
Object service = getSystemService("statusbar");
Class<?> statusbarManager = Class.forName("android.app.StatusBarManager");
Method collapse = statusbarManager.getMethod(Build.VERSION.SDK_INT > 16 ? "collapsePanels" : "collapse");
collapse.setAccessible(true);
collapse.invoke(service);
}
}
catch(Exception ex)
{
}
}
Update: You will have to use your own custom Alert Dialog by overriding it their onWindowFocusChanged method too, because Alert Dialogs have their own focus.
This actually can be done via a little hack that I accidentally discovered, but requires the permission android.permission.SYSTEM_ALERT_WINDOW:
What you do is add a view the exact size of the status bar directly to the WindowManager with the certain parameters that covers the status bar and prevents it from receiving touch events:
View disableStatusBarView = new View(context);
WindowManager.LayoutParams handleParams = new WindowManager.LayoutParams(
WindowManager.LayoutParams.FILL_PARENT,
<height of the status bar>,
// This allows the view to be displayed over the status bar
WindowManager.LayoutParams.TYPE_SYSTEM_ALERT,
// this is to keep button presses going to the background window
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
// this is to enable the notification to recieve touch events
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL |
// Draws over status bar
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN,
PixelFormat.TRANSLUCENT);
handleParams.gravity = Gravity.TOP;
context.getWindow().addView(disableStatusBarView, handleParams);
This will basically create a transparent view over the status bar that will receive all the touch events and block the events from reaching the status bar and therefore prevents it from being expanded.
NOTE: This code is untested, but the concept works.
I tried the solutions mentioned by GrantLand and PoOk but both didn't work in my case. Though, Another solution Clear/Hide Recent Tasks List did the trick for me. I am writing a launcher app for which I had to disable a recent applications menu so user cannot open a locked app from it. Also, I had to prevent against notification bar expansion and this trick made me achieve both. Override OnWindowFocusChanged method in your activity and check if this is what u wanted.
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
Log.d("Focus debug", "Focus changed !");
if(!hasFocus) {
Log.d("Focus debug", "Lost focus !");
Intent closeDialog = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
sendBroadcast(closeDialog);
}
}
For a lockscreen Why don't you just use the following in your main activity:
#Override
public void onAttachedToWindow() {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
}
If the user doesn't have a secure lockscreen set the app will let the user pull the status bar down and open activities but that doesn't matter as the user obviously doesn't want a secure screen anyway.
If the user does have a secure locksreen set then the app will show the status bar but will not allow interactions with it. This is because the phone is still actually locked and only your activity is allowed to operate until the user unlocks the phone. Also closing your app in anyway will open the standard secure lockscreen. All this is highly desirable because you don't have to spend all that time coding secure features that you can't guarantee will be as secure as the stock ones.
If you really don't want the user to be able to interact with the status bar, maybe you can leave out the flag FLAG_DISMISS_KEYGUARD call. Then just before you are about to unlock the phone set the flag like I showed in the first block. I don't know if this last part works but it's worth a try.
Hope that helps
Sorry but it does not work. using FLAG_LAYOUT_IN_SCREEN prevents you from catching any touch events.
And BTW:
to add a view to the window use the window manager:
WindowManager winMgr = (WindowManager)getSystemService(WINDOW_SERVICE);
winMgr.addView(disableStatusBar, handleParams);
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);

Categories

Resources