How do I tell when a configuration change is happening in Froyo? - android

In my app, I want a media file to play, and to keep playing if the user rotates the screen (destroying the Activity), but I want it to stop playing if the user moves to a different Activity or another Activity appears over this one, they press the back button, whatever.
I believe there's an API for this in Honeycomb, but I need something that will work in Android 2.2.
I'd rather not use onConfigurationChanged to handle all the configuration changes myself--that sounds like a lot of work and potential bugs--and the issue with onRetainNonConfigurationInstance() is that it doesn't run until after onStop fires--but onPause would be the logical place to pause the media, if appropriate.
Is there a way, in onPause, to tell if the Activity is being paused for a configuration change versus another reason?

Your solution has at least one potential problem.
According to Android documentation:
Some device configurations can change during runtime (such as screen orientation, keyboard availability, and language). When such a change occurs, Android restarts the running Activity (onDestroy() is called, followed by onCreate()).
Testing for a change in rotation only handles one case. Since this is likely to be the most common cause of configuration change, it's an OK solution. But what if you could handle all cases, existing or added in some future version of Android, without the overhead of handling configuration yourself?
Updated to use onRetainNonConfigurationInstance. Android docs have this to say about it:
Called by the system, as part of destroying an activity due to a configuration change, when it is known that a new instance will immediately be created for the new configuration.
Also changed chain to super.onDestroy() to happen after stopping media, but not entirely sure about that. Guess it depends on what stopping media means, and what effect destroying may have on stopping media.
private Boolean mConfigurationChange = false;
public Object onRetainNonConfigurationInstance() {
mConfigurationChange = true;
return null;
}
public void onDestroy() {
if (!mConfigurationChange) {
// Code to stop media file goes here.
}
super.onDestroy();
}

Prior to Honeycomb, onRetainNonConfigurationInstance() as mentioned in the accepted answer is the best way to do this.
Starting with Honeycomb, that's deprecated, but there is a much simpler way: call isChangingConfigurations() instead, during any of your onPause()/onStop()/onDestroy().
Finally, if using the support library FragmentActivity (
android.support.v4.app.FragmentActivity) on pre-Honeycomb, onRetainNonConfigurationInstance() is declared final, but you can override onRetainCustomNonConfigurationInstance() instead for the same effect.

I finally figured out the answer to my own question!
public class MyActivity extends Activity {
Display display;
int originalRotation;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
display = ((WindowManager) getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
originalRotation = display.getRotation();
}
public boolean endingDueToConfigurationChanging() {
int newRotation = display.getRotation();
return (newRotation != originalRotation);
}
}
Display.getRotation() is designed to return a constant indicating whether the device is 0, 90, 180 or 270 degrees off its natural rotation. However, when the device is rotated, this value is updated before the activity ends--it's actually updated as early as in onPause()! So if you compare the value in onPause() (the new value) with the one in onCreate() (the original value) then you know the Activity it shutting down due to a screen rotation or not.

You can try listening for the ACTION_CONFIGURATION_CHANGED intent and setting an internal flag indicating the pause is from a configuration change.
I'm not sure if you will receive the intent in time or that you will be guaranteed it will arrive in time as it is probably asynchronous. Might be worth a try though.

You can also try using View#onConfigurationChanged in case its applicable in your case(i.e - you have a view for your media player and have a reference to it in the view).

Related

Is there a way to know that an Activity was recreated because of a device rotation

I have an activity class that locks itself with a custom PIN whenever it is resumed/recreated (so that I can lend my phone to someone secure in the knowledge they can't see that app's data).
The problem this causes is that rotating the device recreates the Activity and redisplays the PIN lock, which is a bit unsmooth.
So, is there some way the Activity to know either
I am being destroyed because of an orientation change.
I am being created as a result of an orientation change.
I would like to avoid solutions based on android:configChanges="orientation|screenSize" if possible.
EDIT: For posterity, I ended up doing this by having all Activities in the app inherit from this LockableActivity class.
A very simple solution is to check the time passed between 'onPause' and 'onResume'. If it is less than 0.2 seconds then you haven't handed your phone over ...
In fact, you could make this a user controllable security feature: how long away from the activity before pin entry is needed again could be set by the user.
For the truly obsessive, you could ask the user to reorient their phone during the set up phase to determine the associated time lapse and set that as the minimum.
As noted elsewhere, you could also use onSaveInstanceState. In this approach that is when you would store the time for comparison later.
Put the below code in your activity, it will get triggered when the device is rotated
#Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
//perform your operations here
}
If you also want to check whether it is in landscape or portrait mode , you can apply below conditions
if(newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE){
}
else {
}
If your activity is normally destroyed, onSaveInstanceState() will never be called. During orientation change (or any other change that needs to load different resources), onSaveInstanceState() will be called. In this method, you can save your data that you want your activity to have during it's re-creation.
If your activity is normally created, savedInstanceState will be null. But if it was created because of re-creation (like change in orientation), then savedInstanceState will not be null and you can use this to fetch the data that you saved during onSaveInstanceState().
It's a bad idea to override onConfigurationChanged(), instead you could just use these methods that Android provide to persist your data or application logic . In your case, it could be something like this in your onCreate().
if(savedInstanceState != null){
boolean loggedIn= savedInstanceState.getBoolean("LoggedIn",false);
if(!loggedIn){
// Not logged in, hence show pinlock
}
}
You could do something like this.

Getting around onDestroy being called on orientation change?

I have a stock Nexus 5 running 4.4.2 (using ART if it matters) and I've found an interesting scenario. I have this as my onDestroy():
#Override
protected void onDestroy() {
super.onDestroy();
t.setText("onDestroy");
t.show();
}
It's a GPS oriented app so I'm up and walking around. I am using the technique mentioned in this question to show a lot of debug toast messages.
Anyway, when I rotate my app, the toast appears. I understand that the activity is destroyed and recreated for the new orientation, but how can I know what's really going on? How can I tell when my app is REALLY getting destroyed and not just being rotated? Similar to this question, I want to log out when a particular activity is destroyed.
Since Honeycomb, the isChangingConfigurations() method can be queried to check whether the Activity is being recreated due to configuration changes. Alternatively, the isFinishing() method can be queried on any API level to check whether the Activity is actually being finished, or is only being destroyed temporarily by the system.
As far as I can determine, the two methods should always return mutually consistent results in practice. The only point where they might have diverged is when the system kills the process to clear memory, but there are no callbacks or interaction with the app at that point.
The documentation of the onDestroy() method mentions the use of the isFinishing() method:
Perform any final cleanup before an activity is destroyed. This can happen either because the activity is finishing (someone called finish() on it, or because the system is temporarily destroying this instance of the activity to save space. You can distinguish between these two scenarios with the isFinishing() method.
You can put it in a fragment with setRetainInstanceState(true) set. Place your code in the onDestroy() method of the fragment. Then, the fragment will not be destroyed on orientation changes.
First of all, you should not use onDestroy() to do anything because its not guaranteed to be called. I would put things on the onPause() method; I wouldn't even put things in onStop().
Also, Im not sure why you want to log out a user when they navigate away from the app. I would rather implement some kind of timer on the app or server to log out after x time.
Now, the answer lies in the documentation: http://developer.android.com/reference/android/app/Activity.html#ConfigurationChanges
You might want to override onConfigurationChanged so that your activity is not restarted.
I found a couple of solutions which are really just patterns to detect when the screen rotates. Alternatively, you can determine that the device was actually destroyed by checking some static data member to see if it was initialized or not.
Configuration changed solutions:
The first one involves handling all of the configuration changes in the onConfigurationChanged callback.
"Note that this will only be called if you have selected
configurations you would like to handle with the configChanges
attribute in your manifest."
The second involves listening for Display.getRotation() which returns a Surface.ROTATION_* object. Which is the new orientation of your screen relative to the natural state of the device orientation.
Again, you can use the configuration changes along with the static member.
Add ConfigChanges.UiMode flag to the ConfigurationChanges attribute for your MainActivity class, and this solves the problem.
More details: Android Launcher "OnDestroy" gets called twice

Know when rotation is finished using setRequestedOrientation

I am trying to write an .apk to measure how long it takes to rotate the screen of an Android device (looping through a couple rotations).
I am using calls to setRequestedOrientation() to reoriente the screen in the 4 positions (PORTRAIT, LANDSCAPE, REVERSE_PORTRAIT and REVERSE_LANDSCAPE).
My problem is I am not able to tell when the rotation is finished. I have tried a few solutions, but each have their problems.
I tried to use onConfigurationChanged(). The problem is that this method gets triggered when the orientation change happens from the sensor, and not from setRequestedOrientation(). I actually have to call setRequestedOrientation() with the FULL_SENSOR parameter for onConfigurationChanged to work after my previous call to setRequestedOrientation().
I have tried making setRequestedOrientation() sort of "blocking", by adding a while loop to check the screen orientation (in degrees). I called android.view.Display.getRotation() before setRequestedOrientation() and block with a while loop until it has changed. Unfortunately, it doesn't work either. It returns before the UI gets redisplayed.
I have tried to follow the life cycle of the activity, by waiting for onCreate() to get called again, to assume the rotation was done. Previously, I was looping until my counter reached a value, in each iteration calling setRequestedOrientation(). In this scenario, my while loop becomes an if statement, and I assume onCreate will call the function each time, but onCreate() doesn't get called after a couple of iterations. The UI doesn't seem to be redrawn as well.
A final solution that I have found online (but haven't fully been able to implement) is to use the waitForIdleSync() method right after setRequestedOrientation(). waitForIdleSync() is part of the Instrumentation class, and I think this is just made for testing and not for a standard .apk.
Any ideas would be really welcome. I would really appreciate your help.
You should try using:
http://developer.android.com/reference/android/view/OrientationEventListener.html
You need to call enable() first, and then you get notified on EVERY rotation change your device is having.
There's no direct way to know when setRequestedOrientation() finished via Android API. The only way to know is to check the current orientation before calling it and after calling it in onCreate().
In your Activity class do:
long timeStamp;
public void onCreate(Bundle savedInstanceState) {
if (savedInstanceState == null) {
timeStamp = System.currentTimeMillis();
}
else {
timeStamp = savedInstanceState.getLong("state_time_stamp");
}
// For example, check how long it takes to switch from portrait to landscape.
// You can modify this part to check PORTRAIT, LANDSCAPE, REVERSE_PORTRAIT and REVERSE_LANDSCAPE.
final int orientation = getResources().getConfiguration().orientation;
if (orientation == Configuration.SCREEN_ORIENTATION_PORTRAIT) {
setRequestedOrientation(ActivityInfo.ORIENTATION_LANDSCAPE);
return;
}
// We will reach here only after setRequestedOrientation() is done
// and here's the time it took to change the orientation:
final long elapsedTime = System.currentTimeMillis() - timeStamp;
}
public void onSaveInstanceState(Bundle outState) {
outState.putLong("state_time_stamp", timeStamp);
}

Save cache when rotate device

I have a Gallerywiew.
I'm using lazyload to download images but when I rotate device it reloads all images and does not use the cache.
If I do android:configChanges="keyboardHidden|orientation" the current images are in size of latest orientation.
To get the images to show full size I do:
Display display = ((WindowManager)getSystemService(Context.WINDOW_SERVICE))
.getDefaultDisplay();
int width = display.getWidth();
int height = display.getHeight();
Overriding onConfigurationChanged() is discouraged because there's so much work you have to do to get it right.
What you want to do is implement onRetainNonConfigurationInstance() in your activity. This is called just before your activity is killed when the system knows it will be restarting it in a moment (e.g. for screen rotation).
Your implementation of onRetainNonConfigurationInstance() may return any object it likes ('this' is a good choice, or in your case, your cache). This object will be held and made available to the next invocation of your activity.
In your onCreate() method, call getLastNonConfigurationInstance() to retrieve the object the system is saving for you. If this function returns null, proceed as you would normally. If it returns non-null, then that will be the object you previously passed back from onRetainNonConfigurationInstance() and you can extract any information you want from it. This generally means that you don't need anything from the savedInstanceState bundle or from saved preferences.
I believe even open sockets, running threads, and other objects can be preserved across configuration changes this way.
Adding on: If you do pass this through onRetaineNonConfigurationInstance(), don't hang on to it. You'll keep huge amounts of resources from being freed. Extract the information you need and then release it.
You need to override your onConfigurationChanged event.
In my app I was playing music and when you flipped the phone it would stop and then I found out that if I overrode the config change I could just tell it to start my timer again and nothing was lost. I know this inst exactly what you wanted but it may help get you going in the right direction.
#Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
startTimer();
}
When you rotate the screen, you go through the activity lifecycle. The activity goes through onDestroy() and its onCreate() method gets called. You must redo any display calculations and view changes in onCreate and use the savedInstanceState to persist app data.
I hope this is what you were asking

Gracefully handling screen orientation change during activity start

I'm trying to find a way to properly handle setting up an activity where its orientation is determined from data in the intent that launched it. This is for a game where the user can choose levels, some of which are int portrait orientation and some are landscape orientation. The problem I'm facing is that setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) doesn't take effect until the activity is fully loaded. This is a problem for me because I do some loading and image processing during startup, which I'd like to only have to do once.
Currently, if the user chose a landscape level:
the activity starts onCreate(), defaulting to portrait
discovers from analysing its launching Intent that it should be in landscape orientation
continues regardless all the way to onResume(), loading information and performing other setup tasks
at this point setRequestedOrientation kicks in so the application runs through onPause() to onDestroy()
it then again starts up from onCreate() and runs to onResume() repeating the setup from earlier
Is there a way to avoid that and have it not perform the loading twice? For example, ideally, the activity would know before even onCreate was called whether it should be landscape or portrait depending on some property of the launching intent, but unless I've missed something that isn't possible. I've managed to hack together a way to avoid repeating the loading by checking a boolean before the time-consuming loading steps, but that doesn't seem like the right way of doing it. I imagine I could override onSaveInstanceState, but that would require a lot of additional coding. Is there a simple way to do this?
Thanks!
Solution:
As per Daniel's answer, this was actually quite easy to fix. I just needed to make a few small changes. In my 'menu' Activity, where the player would choose which level to play, I just had to add an if/else check to choose which class would be started by my Intent. This was done with a simple int representing portrait or landscape, determined when the player selected a level. I then created a second class extending my 'GameLogic' class; this is the class which contained most of the code for the game itself, rather than the menus, instructions, etc.
public class GameLandscape extends GameLogic{
}
Literally that simple and completely empty. That way it inherited all the code from my previous activity where I had already coded it to handle things differently depending on the orientation. Lastly I just had to add a line to the manifest stating that GameLandscape would always run in landscape, and GameLogic would always run in portrait.
So a simple problem indeed.
You could make two Activities - one for portrait levels, the other for landscape levels - and then set the Activity's orientation in AndroidManifest.xml, using the android:screenOrientation attribute. You won't even have to duplicate code if you use inheritance; use your current Activity as the base activity, and just create the landscape/portrait Activities as subclasses of that Activity.
I think a better solution would be for the Intent to open the correct Activity of these two, though if you must have everything be routed via Intent extra analysis, you could forward all levels to a third Activity that does nothing more than analyse the Intent and then forward it to the proper Activity.
You could also override onRetainNonConfigurationInstance(). This lets you temporarily store one item that you can retrieve by calling getLastNonConfigurationInstance(). That way you can load all of the stuff that you need and in your onRetainNonConfigurationInstance() method you can save it all into a data structure and return it. The in your onCreate() you can call getLastNonConfigurationInstance() and if that returns null load, load all of your stuff, if it return something, then you have it all loaded. Here's a quick example:
public class MyActivity extends Activity
{
#Override
public void onCreate(Bundle savedInstanceState)
{
DataStructure myData = (DataStructure)getLastNonConfigurationInstance();
if(myData == null)
{
// Load everything in
}
else
{
// Unpack myData
}
}
#Override
public Object onRetainNonConfigurationInstance()
{
DataStructure myData = new DataStructure();
// Put everything in to myData
return myData;
}
}

Categories

Resources