I am using the Android MediaRouter (API 16) and Presentation (API 17) classes to generate & manage a secondary display. I followed the example in ApiDemos to create unique non-mirrored output, and so far it works fine (testing with Nexus 10 connected to HDTV via HDMI cable).
Now, I have a situation where I want the Presentation object created in Activity X to continue running on the secondary display, even if Activity X invokes another Activity Y. In this situation, Activity X is still on the stack, but Activity Y is now on top of it.
The problem is that when Activity Y starts, the physical secondary display reverts to mirroring. When I exit Activity Y, the content of Activity X's Presentation goes back (since I never called dismiss() on it).
So the question is: How can I keep a Presentation attached to a secondary display running on that display, even if a subordinate Activity is running on the local device?
UPDATE: One way I thought of doing this is to instantiate the Presentation object from a background thread; then the subsequent creation of another Activity should not interfere with the content being written by the background thread to its Presentation object. But I'm not sure this will work, as it's generally not allowed to update a UI from a background thread.
Another approach would be to disable the use of secondary displays by the subordinate Activity, if possible, thereby preventing it from reverting the secondary display to mirroring when the new Activity becomes active. But I haven't found a way to do this either. Thanks again for any suggestions.
I implemented one of the approaches suggested by #CommonsWare (and independently by Mark Allison in answer to my question on his blog). Thanks for your suggestions!
In review, THE PROBLEM was I couldn't keep a second screen presentation running in the background across Activity invocations on a local device. This was because the Presentation class is implemented as a subclass of Dialog, and is therefore tied to an Activity instance. So when a new Activity started up, the second screen went back to mirroring (instead of displaying other content I was specifically generating for it).
THE SOLUTION was to refactor all "subordinate" Activities into Fragments of the original Activity (i.e., the one that launched the second screen). Then, instead of calling startActivity(), I start/stop the new Fragments using FragmentTransactions. The net effect is that the Activity that started the Presentation is still running, so the secondary display is no longer interrupted when a new Activity starts.
My case was further complicated by the fact that the top level Activity (which starts the second screen) was actually a SherlockFragmentActivity that uses a ViewPager and FragmentStatePagerAdapter -- so I had to cram all this into a Fragment. It also required explicit management of ActionBar tabs, menu items, and home icon.
Overall, I think the code is a little less transparent ... but it works!
NOTE: It's good that Google has implemented a secondary screen interface. But I'm not sure why they did it the way they did. Rather than shoe-horning the Presentation class into Dialog, it would have been nice if they provided a more general solution that could easily run in the background, i.e., regardless of foreground Activities on the device. A solution like this would have saved me from a lot of code refactoring, as described above.
Bringing this question back from the dead willing to help someone with the same problem somewhere in time,
I've recently came into a far deeper, but similar, problem: I had to display a presentation anywhere on the system (I work with embedded android) and in the main screen any app could be used.
I first thought of creating a Service that managed the display of the presentation and was initialized at the startup of the application. But the problem was that I couldn't show the presentation because, as you mention, it inherits from a Dialog and the same problem that happens when you call getApplicationContext() when building a dialog, occurred.
My solution was:
There is a WindowManager.LayoutParam called TYPE_SYSTEM_ALERT that is used to display alerts like the Low Battery alert Dialog. Using this property you can create a Dialog from a service and display it properly and as the Presentation class is a children of the Dialog, simply setting this property made it work.
The magic happens here:
WindowManager.LayoutParams l = mPresentation.getWindow()
.getAttributes();
l.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
mPresentation.show();
Just reminding that in order to achieve that your, application XML should have the SYSTEM_ALERT_WINDOW permission.
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
I think that this should solve your issue as well, but it is a little bit delicated and you need a proper treatment to stop the presentation as soon as you needed.
Related
Library used: appcompat-v7:22.2.1,design:22.2.1
Theme used:
Devices/Android versions reproduced on: Nexus 6
Issue: Return activity quickly redraws/appears then fades in with desired behaviour, only with "Don't keep activities alive".
I am wondering if this is a bug or expected behaviour. I have a very simple setup. Activity A contains a toolbar wrapped in an AppbarLayout and CoorindinatorLayout. The toolbar contains a Cardview and a TextView. Upon click of the TextView, Activity A launches Activity B. I am using shared elements and passing them through as Option's via ActivityCompat.StartActivity(bundle, options);
My shared elements work perfectly, even after device rotation. After reading about how I can PostPoneEnterTransition and combo it up with PreDrawListeners I am able to successfully achieve the desired transition even after rotation. My actual activity contains a Viewpager / TabLayout and 2+ fragments but for simplicity sake, I've stripped it back in the video as well as to see if something else was causing this issue.
While dealing with rotation and postponing of the enter transition back to Activity A, I decided to open developer options and check "Don't keep activities alive". The video depicts the app running with that option enabled. If you look closely, you can see upon return to Activity A, it is completely drawn and hidden very quickly and then the fade in occurs as well as the shared element transition.
I've also excluded the navigation bar and status bar in the animations so that I don't see those flicker (redraw redundantly).
My questions are:
Is this a bug, or am I missing a step in order to prevent this.
Why would the app/transitions behave differently with "Don't keep activities alive" vs a plain old device rotation (destroy/recreate).
I've noticed by playing around with some google apps, this behaviour does not occur, or at least that I could find. Is there a way to concretely check if the activity I am returning too is "completely destroyed" so I can cancel the animation? Or do something different?
I can include specifics and code samples if required but my setup is very simple, and reflects a bunch of boilerplate examples from the Android documentation / Stack-overflow.
Sorry I meant to respond to this earlier. What I ended up doing was recreating the example in a completely fresh project following code samples and tutorials as best I could. First making it work with a single image view, and then of course adding my custom layout which was a floating search bar. Everything worked as expected. I went back and reviewed my actual project source (which was littered with different attempts and commented out code while trying to debug this issue) and cleaned it up. I can't say for sure, but I believe it came down to two possible issues:
"Unless you do something unusual..." - Most likely I "was" doing something unusual by the time I created this issue do to my debugging efforts and lack of full comprehension of the shared elements transition framework and lifecycle.
I think what was happening was the shared element transition was failing do to views not being mapped properly. I was excluding the statusBarBackground inside a transition defined in XML. My statusBarBackground was set to transparent so that I had the nice overlay effect for an expanded drawer layout. I found out that while trying to add the statusbarbackground as a shared element via code, the view was actually null resulting in a crash (NPE). As well as I had set a background color (instead of transparent) to my drawer layout. I can't say for sure, but a combination of these mistakes lead to the strange behaviour.
To conclude, I would say that this issue should be closed and everything is working as intended. It would be nice to get a little more insight on handling a transparent status bar as a shared element.
Is this a bug, or am I missing a step in order to prevent this?
No. Everything is working as intended.
Why would the app/transitions behave differently with "Don't keep activities alive" vs a plain old device rotation (destroy/recreate)?
It doesn't. When everything is setup proper and your timing and mapping of shared elements is correct, "Don't keep activities alive" is a concrete way to test your transitions against configuration changes.
I've noticed by playing around with some google apps, this behaviour does not occur, or at least that I could find. Is there a way to concretely check if the activity I am returning too is "completely destroyed" so I can cancel the animation? Or do something different?
This is because the Google dev's did it right :)
For anyone struggling with shared elements, here is a bit of advice.
Start small. Use a single view first and confirm you are getting the correct behaviour in all circumstances, even after rotation and config changes, then you can add complexity.
Use SharedElementCallback to debug your transitions. You can check which views are mapped, which view failed etc.
I have seen this pattern on both Tumblr and Telegram apps.
When you are in a detail view (Searching for a Hashtag on Tumblr or in a conversation on Telegram) and swipe from left to right (like if you were going to pull a Navigation Drawer) you can see the previous activity come up from there. If you release it, the Activity is finished.
How is this behavior implemented?
[Disclaimer: the following dissertation was written after just one hour or so examining Telegram's source code, so it may contain important flaws or mistakes. Feel free to comment]
The case of Telegram looks to be quite different from what Teovald says. In Telegram's case it is not a fixed image. You can check this by having for instance an open chat where someone is writing half swiped. You will see that both the "chat activity" and the "list of chats activity" are updated in real time, which could not be the case if the effect were accomplished by using a static image.
Since Telegram is open sourced, you can have a look at its code base to figure out how it is actually done.
After examining the code myself for a while, it looks like what they do is not a visual trick. In fact, its a trick which goes far beyond doing small trickery to fake some visual effect. Whatever the case, it smells like some non-standard stuff.
First of all, you can have a look at Telegram's manifest file to see that they only define three activities (LaunchActivity, IntroActivity and PopupNotificationActivity)... three activities! Anyone using Telegram would agree that the application seems to have more than three activities, so whatever they are doing it looks like in practice they only have a single activity running at the same time (LaunchActivity), which through some mechanism shows different "activity like" content.
Indeed, if you go to LaunchActivity you will see that it inherits from a so called ActionBarActivity, which would be the base class for all real activities in Telegram (although in practice only LaunchActivity seems to extend it). This activity seems to take care of implementing, along with other classes, the action bar. Yes, that means that Telegram does not use the standard action bar, nor defines any kind of wrapper to use a "mock" version on Android < 11 and the real version on Android >= 11. It's quite twisted, but you can have a look at their implementation of the action bar (class ActionBar as well as other dependent classes) and see that they are literally creating a raw FrameLayout and embedding it to emulate the action bar. This means, among other things, that they do not inflate XML resources to define the action bar for each "activity", they populate it by hand instead. Very cumbersome and very non-standard, which in my opinion makes Telegram kind of weak regarding improvements of the action bar in future versions of Android.
Now, the thing that concerns me the most is that they are completely ignoring the Android's activities and fragments system, rolling out their own version. To be precise it's not like they are ignoring Android's activities and fragments altogether, but they are using some techniques that (in my humble opinion) would be frowned upon by probably most of Android developers out there.
If you look at ActionBarActivity you will see that it extends a regular Android Activity. It removes (via its theme, and also programatically) the standard action bar and title of the activity in the onCreate() method (this confirms that they use their own customized version of the action bar). But most importantly, they define a "stack" of "fragments". A "fragment" here is not what one understands by "Android fragment", but some weird version they rolled out too to implement the concept of "fragment". They use the BaseFragment class to define a "fragment", which is basically a class that creates its view via a callback (just as Android's fragments do), and which defines some "life cycle" methods such as onPause() and onFragmentDestroy(). Then you can see that what we would consider to be regular Android activities is actually implemented via this weird mechanism of BaseFragments. For instance, the "chat activity" is implemented by the ChatActivity class, however this does not inherit from Android's Activity, but from BaseFragment instead.
So to summarize what I have explained so far, Telegram seems to be an application running a single activity (LaunchActivity, which in turn inherits from ActionBarActivity), which implements a customized action bar and a weird "fragment" framework which emulates regular Android activities, at least at the visual level.
Now, if you look at the onTouchEvent() method of the ActionBarActivity class, this seems to be the one in charge of implementing the core algorithm of the "swipe to dismiss" effect. This tracks the user's finger and moves the view of the fake fragment accordingly. When the user lifts the finger, if the condition to dismiss the "fragment" is true (backAnimation is false), the fragment is removed: when the animation ends, the onSlideAnimationEnd() method is called, which will then remove the "fragment" from the fragment stack kept by the ActionBarActivity class.
So, in short, Telegram accomplishes the "swipe to dismiss" effect by implementing a funky "fragments" system in which what seems to be an Activity is a raw view modeled via a subclass of BaseFragment. This forces them to roll out their own version of the action bar, and also to follow some very weird and "non-standard" Android techniques.
I did not actually know that Telegram was implemented this way, so at this moment I am kind of disappointed. I was expecting them to access some hidden Android APIs, which of course would be wrong, but at least it would not be so cumbersome and ugly as implementing their own action bar and "fragments" system. From now on, to me, Telegram's code will be a reference of what you must not ever do in Android.
Like any good visual feature, it is a trick :-) .
If you use the UI Automator tool to dump the screen content while in a middle of one of these back slides, you can see that the 'old fragment' is in fact just an image view.
It is only when the slide is finished that the previous fragment/activity is brought from the back stack.
I have just implemented this, check it out here
SwipeFinishableActivity.
Generally speaking, you need a translucent top activity so that when you drag the top activity you can see the second top one below. And you should track your touch event to move your views in your activities.
The main activity of my app is defined in the Android manifest file with the following attribute:
android:theme="#style/Theme.Translucent.NoTitleBar"
This makes the activity transparent, which in turn makes it possible to control the transparency of my app in code by manipulating the main View object (e.g., by invoking setVisibility(View.INVISIBLE) on the main view). This works fine.
However, one undesirable consequence of this approach is that when the app is launched there is no visible response until my main View is displayed. Normally, the default black background of an app's main activity is immediately visible when an app is launched, which provides immediate confirmation that the app is starting to run in response to the user tapping its icon in the launcher. But with a transparent background, the user continues to look through the background at the display from which the app is being launched until the main view is displayed, and so it appears (during that interval) as if nothing has occurred.
Even on a device with mediocre performance (e.g., the Motorola Droid) my view comes up in about one second, which is not too bad. However, on a really slow device (e.g., the G1) it can take almost four seconds. While this is not a disaster, I'd prefer an immediate response so that the user is not left wondering whether the app was in fact triggered.
I have tried removing the transparent theme, which results in immediate confirmation via a black background, as usual. However, I've been unable to set the activity background to transparent in code once the app has been initialized.
I've invoked setTheme() on the activity just prior to calling setContentView() for the first time, passing it a transparent theme, but this does not make the activity transparent.
I've also tried this in onCreate() (again, just prior to calling setContentView()):
ColorDrawable transparentDrawable = new ColorDrawable(Color.TRANSPARENT);
getWindow().setBackgroundDrawable(transparentDrawable);
This also appears to have no effect.
I've also tried using a theme in my manifest that has android:windowBackground set to a drawable that is a mostly transparent PNG, but with some text (e.g., the app's name) superimposed on the transparent background that would provide a cue to the user that the app was loading. Unfortunately, the moment I use a drawable as part of the theme, the background fails to display at all until after the main view is initialized.
All time-consuming initializations are already being done in a worker thread, so I'm not looking for advice on how to accomplish that. The view itself just takes a certain amount of time to display, and while it is fairly quick, nothing beats the instantaneous response of seeing the main activity's background as soon as the app is launched.
Even on a device with mediocre performance (e.g., the Motorola Droid) my view comes up in about one second, which is not too bad. However, on a really slow device (e.g., the G1) it can take almost four seconds.
It should come up in milliseconds. Make sure you are not doing excessive work on the main application thread.
The view itself just takes a certain amount of time to display, and while it is fairly quick, nothing beats the instantaneous response of seeing the main activity's background as soon as the app is launched.
Then initially display something else that is cheaper to bring up (e.g., ProgressBar), replacing it with your regular UI when it is ready.
I've upvoted CommonsWare's answer, because he pointed me in the right direction, which is away from trying to change the transparency of the main activity after it is launched (something I'm beginning to suspect cannot be easily done).
However, that advice cannot itself be the accepted answer, given that it is only a pointer in the right direction.
The answer I decided upon, given this guidance, was to create a splash display. However, I could not find a truly good android splash example anywhere. So, I devised one, and it is working very well for me, and completely solves my problem.
Because creating a splash display is a more general question than the one I started out with, I have placed my detailed description of how that can be done as an answer to a question about how to implement a splash screen, and have linked to that answer below:
Create a true splash screen
Sorry, I know that this topic has been covered a bit. I've read the related posts and am still a bit confused. I am working on an app that while the prototype will have 3 main screens, it will eventually have dozens. Each screen will present either dynmically changing status or take user input. To visualize, it is required to be laid out similar to how MS Word or a typical PC is. It has a status bar at the top and a navigation bar at the bottom that is common to all screens (slight tweaks for some screens, like different icons) in the middle is what I would call a view pane that needs to be updated with a applicable layout.
The status, nav bar, and each screen are defined in their own layout xml file. For my first swag at it I just used a ViewFlipper and loaded the 3 screen layouts into it. However that means that currently I have one main Activity which will not be maintainable as I continue to add screens.
It feels right to me that each screen layout should have an associated Activity class that understands how to control that screen. I need to figure out how to load that into the center pane dynamically. However I thought I read in another post that using multiple Activities can be a CPU and RAM drain.
Currently I tried making one of the screens it's own Activity and kick that off from the main Activity by creating an Intent and than calling startActivity. However that causes the new screen Activity to reside on top of the main Activity. The interesting thing is that then pressing the back button dismissed that activity and returns me to the main.
So far I haven't figured out how to setup having a different Activity control what happens in the center pane.
If I continue down the multiple Activity path, should my main Activity be inheriting from ActivityGroup?
Are using View classes more applicable in this case?
I know this has been a long post. I'd appreciate any advice.
Thanks!
CB
As you noticed, Android will implicitly track a stack of started activities in a task, and the 'back' button ends the top one, reactivating the next one down. I would advise you to think about which kinds of things the user might expect the back button to do, and make it so that activities are separated along those lines.
I haven't played with ActivityGroup so I can't advise you there. If you go with completely separate activities, you can have them all use the same "shell" content view with the common nav/status bar. Have a superclass or utility class handle populating and managing that from there. Then use a a LayoutInflater (you can call getLayoutInflater()) to fill in the middle with your Activity-specific view.
If you want one of the activities to have multiple screens, you might still end up with a ViewFlipper in the center slot. Again, you want to have an Activity transition wherever you want the user to be able to go "back"; that also means you may NOT want to have a change of activities in cases where screens are closely related or part of the same logical thing-being-done. (You can override the back button's behavior, but unless you have a good reason to, it's best to just arrange the app so that Android's basic setup helps your app's UI rather than working at cross purposes.)
If you want to use activities in the fashion you talked about, you might look into using a tab activity. It actually works in the way you want, you just need to hide the tab widget and put your navigation bar there instead. Or, you could go a little deeper and make you own similar tab-like ActivityGroup like Walter mentioned if you have more time.
You could use a view pager with fragments to accomplish the flip between the different views but still allow your activity to have full control over it. The activity can control the menus while the fragment controls your viewing area. This way your back button will properly dismiss the activity containing all pages related to the activity instead of walking down the stack.
I am working on an Android app that has multiple screens the user will need to navigate between and I am curious what the best practices are when switching between those screens. I am torn between creating a new Activity for each screen and simply changing the view (setContentView(R.layout.whatever)). The screens all share at least some variable values so I'm leaning toward changing views and using class level variables, but I'm worried a single activity could become very large and confusing with logic for multiple screens in a single file. I'd like to keep the code clean and separated, but I also don't want to be passing several variables around between views if that isn't needed.
Being new to Android development, I'm hoping some more experienced members of the community could share their thoughts and let me know how best to handle it.
Thanks!
Note:
I wasn't planning on using a viewflipper. My thought was to use a button click event and then call setContentView() to a new view for the page I wanted to bring up next.
Example: My application starts up using R.layout.main as it's view. User clicks the Help button and it calls a method that runs setContentView(R.layout.help); to display the help screen as opposed to switching to a help activity.
You should use an activity per screen as this will make the best use of the framework and allow the OS to selectively kill off screens if things get tight.
If you have a single activity and resources get tight the OS has two choices; kill everything or kill nothing, and if the user is not using your app then it's most likely it'll kill everything.
If you use an Activity per screen the OS can kill off some of the screens the user hasn't visited for a while, whilst still allowing others to remain active which allows the user to go back to them quickly.
As for sharing variables and values, you could use the SQLite database or SharedPreferences stores for passing them around if they are widely shared, or use the putExtra methods in Intent if they're only of use from one screen to the next.
If you have variables that you will reuse make a base class for them, that you will extend.
This can be a your custom activity that extends Activity.
As far I can tell you have to create separate activities for each views, only a few situation can be handled by viewflippers.