IllegalStateException when adding fragment in response to a screen lock - android

Firstly, I believe this is not a duplicate question, although the solution for the error has been asked a lot of times. I have tried atleast 5 different solutions but they either don't change anything or make things worse. I want to pause the activity when the screen is locked/focus changed and allow the user to unpause the activity when the app is opened again.
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (!hasFocus && pauseFragment == null) {
exercisePlayPause(isPaused, workoutExerciseNum);
isPaused = !isPaused;
}
}
exercisePlayPause calls the following method
public void PassExerciseNum(int exerciseNum, Boolean isPaused) {
if (!isPaused) {
pauseFragment = new PauseFragment();
pauseFragment.getExNum(exerciseNum);
getFragmentManager().beginTransaction().add(R.id.aworkout_layout, pauseFragment, "pause").commit();
} else {
getFragmentManager().beginTransaction().remove(pauseFragment).commit();
pauseFragment = null;
exercisePlayPause(true, exerciseNum);
}
}
This works fine when the home button is pressed or some other app is activated or the notification bar is clicked. But when the screen is locked then I get the following error
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at android.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1411)
at android.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1429)
at android.app.BackStackRecord.commitInternal(BackStackRecord.java:687)
at android.app.BackStackRecord.commit(BackStackRecord.java:663)
The offending line is getFragmentManager().beginTransaction().add(R.id.aworkout_layout. I am pausing few processes when any event that makes the user move away from the app. And when the user comes back to the app, he/she can resume from there. Hence, I am not destroying the activity. I have tried using commitAllowingStateLoss() but that makes me lose data for the paused processes and becomes a bit messy.
So in short the question is how to make a fragmenttransaction happen before onSaveInstanceState is triggered when the screen lock button is pressed?

The activity's orientation was sensorLandscape. When a screen locks it converts the screen to portrait and when the screen is unlocked it starts from a portrait mode and then becomes landscape. Hence, onDestroy was called when screen is locked. And when the screen is unlocked, onCreate gets called. This orientation change overrides the activity's orientation set in manifest. What happens when this forceful orientation happens can be controlled by adding android:configChanges="orientation|screenSize|keyboardHidden"/> to the manifest. This prevents onDestroy being called and the "illegalStateException" can be avaoided.

Related

How Do We Leave Picture-In-Picture Mode?

We have enterPictureInPictureMode() to move an activity from its current form into a picture-in-picture representation.
What is the means by which we revert that, returning the activity to its normal state, besides destroying the activity? There is no exitPictureInPictureMode(), leavePictureInPictureMode(), or janeGetMeOffThisCrazyPictureInPictureModeThing() method on Activity, and the documentation does not seem to cover an alternative.
I am interested in a solution for Android O, for picture-in-picture mode on mobile devices, though if that works for Android TV too, wonderful!
UPDATE 2017-04-08: If what you want is to return to normal mode when the user clicks the X button to exit picture-in-picture mode, you can do something like this:
#Override
public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
super.onPictureInPictureModeChanged(isInPictureInPictureMode);
if (!isInPictureInPictureMode) {
getApplication().startActivity(new Intent(this, getClass())
.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT));
}
}
The key bits are to call startActivity() to start the current activity again with FLAG_ACTIVITY_REORDER_TO_FRONT. With a singleTask activity, you need to call that on some non-Activity context, such as the Application singleton. This does not appear to trigger onStop() or onStart(), but it does trigger onNewIntent() (with whatever Intent you pass to startActivity()).
Move the activity to the back
activity.moveTaskToBack(false /* nonRoot */);
restore the activity to the front
Intent startIntent = new Intent(PipActivity.this, PipActivity.class);
startIntent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
activity.startActivity(startIntent);
I had the same problem:
MainActivity opens VideoPlayerActivity which has PIP mode enabled.
Press the back button to go to the PIP mode.
Press back button again until I exit from MainActivity.
Press close (X) button on the PIP window.
Open application, It will open VideoPlayerActivity.
While none of the above solutions work for me, I found out that the only way to listen to X button is to override onStop.
When the activity is restored from PIP, onResume and onPictureInPictureModeChanged are called, when the X button is clicked, the onStop and onPictureInPictureModeChanged are called.
So I tried to call finish() inside onPictureInPictureModeChanged when the onStop is alreay called.
override fun onStop() {
super.onStop()
onStopCalled = true
}
override fun onResume() {
super.onResume()
onStopCalled = false
}
override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean, newConfig: Configuration) {
if (isInPictureInPictureMode) {
// ...
} else {
if (onStopCalled) {
finish()
}
}
}
I don't think the activity can decide to leave Picture-in-picture mode.
From the Android O API preview doc:
The activity entering PIP mode goes into the paused state, but remains started. If the user taps the PIP activity, the system shows a menu for the user to interact with; no touch events reach the activity while it is in the PIP state.
The activity will be notified by onPictureInPictureModeChanged().
I've found a 100% reliable way to do this.
Set the taskAffinity attribute for your PIP activity in the manifest to something like "com.package.pip". Doesn't matter what, it just has to be different from your package name which is the default task affinity. This will cause it to launch in a completely separate stack as if it were another app altogether.
Whenever you want to exit PIP, launch it with startActivity(new Intent(this, PipActivity.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)) from within another activity.
I just looked at what happens when the app's main activity is currently in PIP mode and you launch it from the launcher and thought that I could replicate this, and I indeed could.
Official documentation says PIP window comes up with menu which let you toggle to full screen.
The PIP window is 240x135 dp and is shown at the top-most layer in one
of the four corners of the screen, chosen by the system. The user can
bring up a PIP menu that lets them toggle the PIP window to
full-screen, or close the PIP window, by holding down the Home button
on the remote. If another video starts playing on the main screen, the
PIP window is automatically closed.
And you can override PIP changed event to handle UI elements whenever the user toggles PIP mode.
link to onPictureInPictureModeChanged
#Override
public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
if (isInPictureInPictureMode) {
// Hide the controls in picture-in-picture mode.
...
} else {
// Restore the playback UI based on the playback status.
...
}
}
According to latest answers from another stackoverflow blog https://stackoverflow.com/a/71797433/3842263 , it should be this way. It works good for me.
#Override
public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode, Configuration newConfig) {
if (getLifecycle().getCurrentState() == Lifecycle.State.CREATED) {
//when user click on Close button of PIP this will trigger.
finishAndRemoveTask();
}
else if (getLifecycle().getCurrentState() == Lifecycle.State.STARTED){
//when PIP maximize this will trigger
}
super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig);
}

Android App Development: Handling Screen Orientation Changes

I have run into a problem with handling screen orientation changes (For example: portrait to landscape). I'm developing a flashlight app which has a toggle button that turns the LED light on or off on the person's phone. I set up my onPause() method to kill the app like so:
#Override
protected void onPause() {
super.onPause();
finish();
}
This is because if I was to open the app and then go something else (like open youtube) and then come back and press the button again it crashes. But because of this "finish();" thing I can't handle screen orientation changes because apparently when it switches orientation it calls the onPause() method which finishes!
SO my question is...how do I do both? I want to be able to fix the crash problem but also not have it crash when the screen orientation changes. I'm not asking for code (although it would be helpful) just some insight from a new set of eyes.
I thought about using an "if" statement...would this work?
Thanks in advance,
Andrew
When the screen orientation changes, the activity will do onPause(), onStop() and onCreate() again. If you don't want your activity do onPause when the orientation changes, try this:
In your manifest.xml, add android:configChanges="orientation|keyboardHidden" for your activity like:
<activity android:name=".yourname"
android:configChanges="orientation|keyboardHidden">
</activity>
In your activity, override this method:
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
}
But my advice is not to use finish() in onPause(), the system can handle the lifecycle just fine. If you want to finish your activity when the home or back button pressed, try to override onBackPress() and onUserLeaveHint(), these two method can catch back and home button pressed, and add finish() in these two method.
Hope it helps.

Activity return to Portrait when Screen is Locked

Related questions:
https://stackoverflow.com/questions/15648713/strange-behaviour-while-screen-lock-in-landscape
This is a strange behaviour i got in my Activities.
Portrait mode (It's normal)
Press screen lock, Activity: onPause();
Unlock the screen, Activity: onResume().
Landscape mode (It's strange)
Press screen lock, Activity: onPause() -> onStop() -> onDestroy() -> onCreate() -> onStart() -> onResume() which loads the Portrait layout;
Unlock the screen, Activity: onPause() -> onStop() -> onDestroy() -> onCreate() -> onStart() -> onResume() and loads the Landscape layout.
What I expect is:
Portrait mode: (same)
Landscape mode: (should act like Portrait mode)
Press screen lock, Activity: onPause();
Unlock the screen, Activity: onResume().
So my questions:
Why does my Activities behave like this?
How does your Activities behave?
Original text description of my question:
While i press the Lock Screen button of my phone, when my Activity is at its Landscape mode, i noticed (in the debug messages i output to Eclipse) the Activity is re-created to its Portrait mode (while the screen is all black, of course). Then when i press the Lock Screen button again to unlock the screen, the Activity was destroyed and re-created to its Portrait again.
As i remember (not 100% sure though), and what i expect is, my Activity should only undergo onSaveInstanceState() and onPause(), while Lock Screen in Landscape mode, like what it does in Portrait mode. Rather than re-creating the Activity to Portrait and going back to Landscape again.
Is it that i have messed up something with my phone? How can i fix it back to normal?
Thanks!
Thanks everyone for contributing into this issue. Especially thanks #HoanNguyen for his effort of testing for me in his devices. And Especially thanks #Raghunandan for having an in-depth discussion with me concerning this issue.
Summarising everyone's contributions so far, i have the following conclusions:
1. This is a normal phenomenon.
It seems that, on mobile phones, the running Activities, that are in Landscape mode, are switched into Portrait mode upon screen lock is a normal behaviour. At least it is true on the tested phones so far. So we have to make sure our lifecycle functions can take care this change elegantly always.
2. Guess this is because of the "default orientation" in the locked screen.
We do not have documentation or many resource talking about this issue. But the assumption that the running Activities switching back to the device's "default orientation" upon screen lock, as in most devices the locked screen is in Portrait, is quite logical.
Further study:
I just wonder how the Activities behave if we are having a landscape locked screen?
You can stop the activity from restarting on orientation change but this is generally a pretty bad idea.
The Android documentation has a section on handling runtime changes with this note:
Note: Handling the configuration change yourself can make it much more difficult to use alternative resources, because the system does not automatically apply them for you. This technique should be considered a last resort when you must avoid restarts due to a configuration change and is not recommended for most applications.
Android generally only recommends you supress recreating on rotation if you don't need alternate resources and, more importantly, have a performance requirement. A well-designed app shouldn't need to do this in most cases.
If you insist on going down the path of supressing default Android behavior, I'd modify Raghunandan's code and include a screen size attribute as well. As of API level 13, the screen size changes upon orientation change. So you must include screenSize unless you are only targeting API 12 and below.
<activity android:name=".MyActivity"
android:configChanges="orientation|screenSize"
android:label="#string/app_name">
To avoid activity from restarting
<activity android:name=".MyActivity"
android:configChanges="orientation|keyboardHidden"//add tthis in manifest
android:label="#string/app_name">
http://developer.android.com/guide/topics/resources/runtime-changes.html.
In normal circumstances when your screen is locked your activity is paused and when screen is unlocked activity resumes.
An issue when screen locked is: Current Activity may be stopped forcefully by the system if it finds shortage of memory, instead of moving Activity to background. In such a case, we should have to save (all the necessary data) the current state of the Activity.
Save you data in onSaveInstanceState() and restore data onRestoreInstanceState().
#Override
public void onSaveInstanceState(Bundle outState)
{
Log.v("$````$", "In Method: onSaveInstanceState()");
//if necessary,set a flag to check whether we have to restore or not
//handle necessary savings…
}
#Override
public void onRestoreInstanceState(Bundle inState)
{
Log.v("$````$", "In Method: onRestoreInstanceState()");
//if any saved state, restore from it…
}
In your onCreate()
IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON);
filter.addAction(Intent.ACTION_SCREEN_OFF);
filter.addAction(Intent.ACTION_USER_PRESENT);
mReceiver = new ScreenReceiver();
registerReceiver(mReceiver, filter); //register
public class ScreenReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent)
{
if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF))
{
Log.v("$$$$$$", "In Method: ACTION_SCREEN_OFF");
// onPause() will be called.
}
else if (intent.getAction().equals(Intent.ACTION_SCREEN_ON))
{
Log.v("$$$$$$", "In Method: ACTION_SCREEN_ON");
//onResume() will be called.
//Better check for whether the screen was already locked
// if locked, do not take any resuming action in onResume()
//Suggest you, not to take any resuming action here.
}
else if(intent.getAction().equals(Intent.ACTION_USER_PRESENT))
{
Log.v("$$$$$$", "In Method: ACTION_USER_PRESENT");
//Handle resuming events
}
}
In your onDestroy
#Override
public void onDestroy()
{
super.onDestroy();
Log.v("$$$$$$", "In Method: onDestroy()");
if (mReceiver != null)
{
unregisterReceiver(mReceiver); //unregister
mReceiver = null;
}
}

Identify that the lock screen was unlocked

I want to launch a Notification in my app in a specific situation only when a certain Activity is not visible.
I managed to do it doing the bind/unbind of the Service when I create and destroy the Activity (using onCreate/onDestroy) e saving in a boolean if this Activity is visible through onPause/onResume methods, as the following code bellow shows:
public void onCreate(Bundle savedInstanceState) {
// ...
bindService(...);
}
public void onDestroy() {
// ...
unbindService(mConnection);
}
public void onResume() {
// ...
// this method sets to true the Service's boolean which retain Activity's visibility.
mService.registerActivity(true);
}
public void onPause() {
mService.registerActivity(false);
}
And on the Service, I check this boolean to launch the Notification.
It works for all the cases except in a specific one: when the app is opened in this Activity but the lock screen is enabled.
By some tests I've made, when the lock screen appears, the Activity.onPause method is called. I was hoping that the Activity.onResume method was just called when the lock screen was unlocked, but that's not what happens. When I press the power button to summon the lock screen, the Activity.onResume method is called already. In this sense, what I am doing is not right.
How can I make the Activity.onResume method to be called only when the user unlock the lock screen? Or... how can I identify that the lock screen was unlocked and the user is REALLY looking at the Activity?
Activity.onWindowFocusChanged(boolean hasFocus) should return true every time your Activity regains focus after the screen is unlocked.
2 thoughts, untested and i'm still kind of an android noob :
ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
List<RunningAppProcessInfo> list2 = am.getRunningAppProcesses();
Then, filter your Activity from this list and check the importance property of it. Front running apps it's 100, don't know if it's still 100 when a lock screen is in front of it.
Or, you could create a running process what checks ever x seconds if the screen was locked, and, does something when it's unlocked again.

Detecting screen orientation change in OnPause()

I'm using MonoDroid but an equivalent Java answer can still help.
I'm using a portrait layout and a landscape layout so, if possible, I want to use the Android screen orientation to automatically destory/create activities.
My app is using TextToSpeech so in the activity's OnPause() I am stopping it, which works well when the Home key is pressed or an incoming call is happening. However I don't want to stop the TextToSpeech on a screen orientation change from the user.
Is there a simple way of detecting this change so that TextToSpeech isn't interrupted?
My activity's OnStop() code:
protected override void OnPause()
{
// I need this for Home key, intercepting phone calls, etc.
// But how can I prevent this for a screen orientation change?
// Need to enable screen orientation to get my portrait/landscape views
if(Text2Speech.IsTextToSpeechInitialised && Text2Speech.TextToSpeech != null)
Text2Speech.TextToSpeech.Stop();
base.OnPause();
}
As others have said in the comments on the question, I think you'll want to handle configuration changes yourself here. There are other configuration changes that can cause your activity to be restarted as well, such as revealing the device's hardware keyboard.
In Mono for Android you can specify which of these you want to handle yourself in the ActivityAttribute:
[Activity(ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.KeyboardHidden)]
public class MainActivity : Activity

Categories

Resources