Usually, Android calls onPause on your Activity when it starts being obscured or hidden, followed by onStop when it's no longer shown at all. In my game, I pause the game in onPause, so the user doesn't lose the game while looking elsewhere.
However, when the user drags down the notification bar, it covers my Activity, but neither onPause nor onStop are called. This doesn't seem to be mentioned in the documentation. The game ticks away in the background with nobody looking at it. Is there some other way to tell my Activity has been obscured when this happens, so I can pause the game before the user loses? I can't find anything at all about this on the Android Developers site.
The onWindowFocusChanged(boolean hasFocus) of Activity should serve the required purpose. It is called with false when the notification area is dragged down, and with true once the area is dragged back up. The corresponding Android documentation states that this method "is the best indicator of whether this activity is visible to the user". It also explicitly states that the callback is triggered when the "status bar notification panel" is shown.
It is important to note that this method is also called in other situations. A good example is the display of an AlertDialog. onWindowFocusChanged is even called when the activity itself shows an AlertDialog. This might require consideration, depending on if your game uses AlertDialogs or something else which causes a focus change.
In a scenario similar to the one described in this question, we've used the onWindowFocusChanged method successfully, e.g. on a Nexus 5 with Android 4.4, or a Sony Xperia Tablet with Android 4.1.
Since the StatusBarManager isn't part of the official API, I find it unlikely that there is a way to detect it. Even using reflection, none of the statusbar-classes seem to have a hook for listeners.
If it is feasible, you could deactivate the statusbar. Otherwise, I think you are out of luck :(
Interacting with the status bar has 2 cases:
Case 1: If your activity is already hiding the status bar, and the user pulls down the status bar area without showing the notification: this can be handled by registering OnSystemUiVisibilityChangeListener listener to get notified of system UI visibility changes
boolean mStatusBarShown;
View decorView = getWindow().getDecorView();
decorView.setOnSystemUiVisibilityChangeListener
(new View.OnSystemUiVisibilityChangeListener() {
#Override
public void onSystemUiVisibilityChange(int visibility) {
// Note that system bars will only be "visible" if none of the
// LOW_PROFILE, HIDE_NAVIGATION, or FULLSCREEN flags are set.
if ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) {
// TODO: The system bars are visible. Make any desired
// adjustments to your UI, such as showing the action bar or
// other navigational controls.
mStatusBarShown = true;
} else {
// TODO: The system bars are NOT visible. Make any desired
// adjustments to your UI, such as hiding the action bar or
// other navigational controls.
mStatusBarShown = false;
}
}
});
Case 2: If the status bar is already shown to the user, and the user pulls it down to show the notification area; to detect that, you need to override onWindowFocusChanged(boolean hasFocus) in the
activity, where hasFocus parameter value will be 'false' in case the user pulls the status bar down, and when the user pushes the status bar up again; then the onWindowFocusChanged will be invoked again but this time hasFocus will be true
#Override
public void onWindowFocusChanged(boolean hasFocus) {
// handle when the user pull down the notification bar where
// (hasFocus will ='false') & if the user pushed the
// notification bar back to the top, then (hasFocus will ='true')
if (!hasFocus) {
Log.i("Tag", "Notification bar is pulled down");
} else {
Log.i("Tag", "Notification bar is pushed up");
}
super.onWindowFocusChanged(hasFocus);
}
Check this link for more info.
Related
What I am trying to do/What I have done: I am trying to make a very basic version of TalkBack for visually impaired users. I have made a simple accessibility service that reads the contentDescription of a button clicked by the user and reads it out loud.
#Override
public void onAccessibilityEvent(AccessibilityEvent event) {
// get the source node of the event
AccessibilityNodeInfo source = event.getSource();
if (source == null) {
return;
}
// Check if a button is clicked and speak out the content
if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_CLICKED
&& BUTTON_CLASS_NAME.equals(source.getClassName()) {
Log.d("Button clicked", source.getViewIdResourceName().toString());
convertTextToSpeech(source.getContentDescription().toString());
}
if (source != null)
source.recycle();
return;
}
The problem: But, this way the user can't listen to the button's description BEFORE actually performing the action that fires when the button is clicked. By the time the user listens to the description, the button has already been clicked and an action has been performed.
Question: How do I interrupt the action (eg: opening a new activity after clicking the button) from being performed so that the user can safely explore the current views on the screen with the feedback I provide without starting new activities or firing other actions?
Sort of like what happens in Talkback: the user single taps to hear the description, and double taps to perform the action. How does Talkback prevent an action from happening unless the user double taps? I have looked into TouchExplorationMode but I guess it is mostly used for gestures rather than clicks.
You are looking in the wrong place. Once onAccessibilityEvent receives an event, the action has already taken place. It is simply informing you, a click event has occurred. It is already much too late to stop it.
You do indeed want TouchExplorationMode. Here's a quick and dirty implementation of TalkBack's handling, and how it causes the UI to behave the way it does, without a lot of the extra junk and exception handling. I've only contained the portions of this that matter for this feature. There's of course a lot of other necessary scaffolding, but this would distract from the key elements.
Contents of serviceConfig.xml:
<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:accessibilityEventTypes="typeAllMask"
android:accessibilityFlags="flagRequestTouchExplorationMode"
android:canRequestTouchExplorationMode="true"
/>
Partial Contents of A11yService.java
#Override
public void onAccessibilityEvent(AccessibilityEvent e) {
switch (e.getEventType()) {
case AccessibilityEvent.TYPE_VIEW_HOVER_ENTER: {
e.getSource().performAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
}
}
}
TalkBack then intercepts gestures in onGesture, and for swipe right or swipe left, captures them, and does the same action (accessibility focus) just on the next or previous element in the AccessibilityNode traversal. Easy peasy!
I am trying to work out how to show the "up" arrow in Xamarin.Forms without a pushing a page onto the stack. I.E. I just want to perform an action when the back button is pressed. I am completely stuck on this so any help would be appreciated.
I have tried creating a custom renderer which handles a view property called DisplayHomeAsBack. Which in the renderer calls the following:
FormsAppCompatActivity context = ((FormsAppCompatActivity)Forms.Context);
Android.Support.V7.App.ActionBar actionBar = context.SupportActionBar;
if (actionBar != null)
{
actionBar.SetDisplayHomeAsUpEnabled(element.DisplayHomeAsBack);
}
Unfortunately it seems this does absolutely nothing, even though all online tutorials and stackoverflow question for android suggest this method.
The plan is that I can then use the "OnBackButtonPressed" override in MasterDetailPage, which should allow me to perform this action. Unfortunately displaying the back button has been the larger hurdle so far!
Any idea of a better way to do this or how I can get the current mechanism to work?
EDIT
I have created a project and uploaded it to this question on the Xamarin support forums, if it helps.
http://forums.xamarin.com/discussion/comment/186330#Comment_186330
Sorry to keep you waiting so long!
Warning that I did not actually run this code and changed it from my own so I would be surprised if it worked perfectly without some changes.
So below should add a back button where there was not one before (so like when there is not really a page to go back to) and then we will add a custom action to perform when it gets pressed.
I would suggest you push a new page onto the stack without using animation so it is transparent to the user and also makes all of this much simpler, but if you absolutely do not want to do that, the below method should work.
MainActivity:
//Use this to subscribe to the event which will create the back button
public override bool OnCreateOptionsMenu(IMenu menu) {
if(menu != null && App.AppMasterPage != null) { //You will need this to make sure you are on your MasterDetailPage, just store a global reference to it in the App class or where ever
Xamarin.Forms.MessagingCenter.Unsubscribe<string>(this, "CreateBackButton");
Xamarin.Forms.MessagingCenter.Subscribe<string>(this, "CreateBackButton", stringWeWillNotUse => { //Use this to subscribe to the event that creates the back button, then when you want the back button to show you just run Xamarin.Forms.MessagingCenter.Send<string>(this, "CreateBackButton")
ActionBar.DisplayOptions = ActionBarDisplayOptions.ShowTitle | ActionBarDisplayOptions.ShowHome | ActionBarDisplayOptions.UseLogo | ActionBarDisplayOptions.HomeAsUp; //You may need to play with these options to get it working but the important one is 'HomeAsUp' which should add the back button
});
} else {
Xamarin.Forms.MessagingCenter.Unsubscribe<string>(this, "CreateBackButton");
}
return base.OnCreateOptionsMenu(menu);
}
Now the next step is do do a custom action when it is pressed. I think you can either override OnBackPressed() or OnOptionsItemSelected() in MainActivity or maybe you can override the MasterDetailPage method. I am not sure.
Which ever one works for you, inside of that override, I would simply check to see if you are on your App.AppMasterPage like we did above, and if so, send a MessagingCenter message which your App.AppMasterPage has already subscribed to in order for it to handle the custom action.
If you get stuck let me know!
I know it sounds like a bit of a hack, but the best "solution" I have found so far is to add a page behind the current page (behind the root) so it is not visible. Then when the user presses the back button, handle it by removing that page.
I'm using ActivityInstrumentationTestCase2 to test my UI. My tests will change some value that will appear on the screen. However, after setting the value, I then test the screen by taking a snapshot image, run a checksum on the bitmap and compare the checksum value to the expected value. But after setting the UI value, Android has not completed its updating of the UI. The only solution I've figured out is to use a delay to wait for several seconds although this is not desirable as it has unnecessary waiting time that adds up with enough tests. Is there some way of knowing when Android has actually finished updating my UI?
Doing some more research I came across this. Have yet to try it:
To get notified of system UI visibility changes, register an
View.OnSystemUiVisibilityChangeListener to your view. This is
typically the view you are using to control the navigation visibility.
For example, you could add this code to your activity's onCreate()
method:
View decorView = getWindow().getDecorView();
decorView.setOnSystemUiVisibilityChangeListener
(new View.OnSystemUiVisibilityChangeListener() {
#Override
public void onSystemUiVisibilityChange(int visibility) {
// Note that system bars will only be "visible" if none of the
// LOW_PROFILE, HIDE_NAVIGATION, or FULLSCREEN flags are set.
if ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) {
// TODO: The system bars are visible. Make any desired
// adjustments to your UI, such as showing the action bar or
// other navigational controls.
} else {
// TODO: The system bars are NOT visible. Make any desired
// adjustments to your UI, such as hiding the action bar or
// other navigational controls.
}
}
});
http://developer.android.com/training/system-ui/visibility.html
I have an activity which have multiple piece of UI panel(you can think them as view in android), these panels will be invisible by default.
Now when user trigger action1, PanelA will display, when trigger action2, PanelB will display(at different location of the screen).
Both PanelA and PanelB is visible at the moment, now when user hit the back menu, the PanelB should disappear, and PanelA should disappear when hit the back menu again.
At first, I use View to hold different panels, however I found it is difficult to keep the state consist, for example, the activity will be a little different between PanelA and PanelB.
Then I found the fragment, however after I tried and tested, I found that the addTobackStack() can not apply to my us-case, since the PanelA and PanelB are at different location, android can not save their state by default.
So I wonder if there is any other solution for my requirement?
You need to manually handle this scenario inside onBackPressed() method of an Activity.
For instance -
#Override
public void onBackPressed() {
if (panelB.isOpened()) {
panelB.close()
} else if (panelA.isOpened()) {
panelA.close()
} else {
super.onBackPressed();
}
}
When panelB is opened, it will close only panelB and wont do anything else. Same goes for panelA, if its opened and when both the panel are closed then it will exit the app like normal.
I highly recommend to use DialogFragments here as you can call show() and dismiss() any point of time on that and they can handle custom views pretty well.
Hope it helps.
Im trying to dim the status bar at the bottom of the screen in a fragment, then show it again when the fragment goes away. Here's the code:
#Override
public void onPause()
{
super.onPause();
getActivity().getActionBar().show();
getView().setSystemUiVisibility(View.STATUS_BAR_VISIBLE);
}
#Override
public void onStart()
{
super.onStart();
getActivity().getActionBar().hide();
getView().setSystemUiVisibility(View.STATUS_BAR_HIDDEN);
}
If the user launches my fragment, it works. It dims correctly. But if they hit "back", it seems like the status bar gets shown again correctly, but then after a split second, it goes dim again by itself. Has any one else seen this behavior? I think the system is doing something automatically with the status bar, but I cant figure out what it is. If I take out my call to show the status bar, it still shows it by itseft if the user hits back, but then a split second later, it gets dimmed again.
I had the same issue. I think the problem was that I wasn't setting the visibility on the main content view. I ended up copying the code from HoneycombGallery's ContentFragment.java and that finally worked for me.