I'm facing a head-scratching moment similar to what this person (from Jan 2008) experienced when I realized that there is no cancel button in Android's progress dialog or spinners. It is now July 2009 and I've just installed the cupcake version of Android. Has this thing changed? If not, are you adding a cancel button into the dialogs and how do you do it?
not sure about the whole cancel button...i've heard reports of the onCancel() method not firing properly. my solution just consists of making a normal button on the dialog with a call to return whenever the button is pressed.
private void createCancelProgressDialog(String title, String message, String buttonText)
{
cancelDialog = new ProgressDialog(this);
cancelDialog.setTitle(title);
cancelDialog.setMessage(message);
cancelDialog.setButton(buttonText, new DialogInterface.OnClickListener()
{
public void onClick(DialogInterface dialog, int which)
{
// Use either finish() or return() to either close the activity or just the dialog
return;
}
});
cancelDialog.show();
}
then just use a simple call method from elsewhere in your activity
createCancelProgressDialog("Loading", "Please wait while activity is loading", "Cancel");
rather simple solution, but it does the trick ;)
also just to note that cancelDialog is an activity wipe variable, if you dont need to call it from elsewhere, then you should be able to get away with just limiting the scope of the variable to that method.
I'm no Android user or developer, but I think the answer given in the linked-to thread is pretty decent: there's a hardware "back" key on all Android devices. Users are supposed to know to press Back to back out of whatever activity they're currently in.
Thus, the UI designers feel it's unnecessary to include a GUI back/cancel button. This could be viewed as the UI application of the DRY principle; if there's already one way of doing something, that's enough.
The hardware key is the answer here. I'd be careful about generalising the DRY principle to UIs. There are plenty of cases where you need to hammer, hammer, hammer the same point to the user repeatedly via headings, body text, colours and images.
Users dont "read" UIs the way you read a novel. They scan read.
I can't speak for other apps, but in mine anything that might cause the UI thread to wait is executed in a seperate thread. The most I'll do is show a small progress spinner in the titlebar to let the user know something is going on in the background.
As an Android user, and developer, I can say, in my opinion, and based around my understanding of the platform, that there is a good reason for not having a cancel button by default on the cancel-free progress dialogs.
As a developer, these dialogs can not be cancelled by default, that is, you have to explicitely set them as cancelable.
This makes sense, because their purpose is to alert the user, via the UI thread, that some work is going on elsewhere that is important to the updating of the UI thread, before the user should continue their use of the application.
For example, when fetching a list of data to occupy an empty screen, there is nothing to interact with, and the user needs to be made aware that something is going on, and to expect there to be nothing available to interact with until this process is complete.
However, there may be cases, such as data retrieval from the internet, where the process is "sketchy" and due to connectivity issues, you may not be able to complete the request, and get stuck here.
This as a develop is where you enable the dialog to be cancel-able.
Now as a user, one that clearly understands the UI paradigm of Android, I know that if I want to go back to what I was doing before, or "cancel" the current operation, I should hit the back key.
As a user, it's no different to knowing that in Android, the menu key can often reveal hidden options, some times on a seemingly blank screen with no way to interact.
This is the behaviour a user expects, and the way the platform is designed. You could very well add a cancel button, but from a users perspective that understands their phone, this makes no difference, the back key is clearly the intended choice for this purpose.
Activities and UIs have a flow, you flow through activities and UI "screens" which are "stacked" and the back button essentially "pops" the last thing off the stack to return you to where you were previously. If you see the dialog as another of these activities, you want to pop it from the top of the stack to return to what is underneath, or an activity previous to that.
If a developer has a dialog that can not be cancelled by back, it is either, by design, for which there can, in cases, be very good reason for, or, it is poor development and an oversight on the devloper's part.
Related
I have defined a method for my DialogFragment which will pop another (alert) dialog, where I confirm that the user wants to dismiss the DialogFragment. If that is the case, then I call DialogFragment.dismiss(). If not, the dismissal of the DialogFragment should simply be ignored and the user should return to it as was before.
This method (say, confirmCancel()) is used for the 'Cancel' button at the bottom of the DialogFragment. Since I also want this to appear when the user presses the back button, or when they touch outside the DialogFragment, I have set confirmCancel as its onCancelListener (of course, I have also used getDialog().setCanceledOnTouchOutside(true) too).
This is the code for confirmCancel():
public void confirmCancel()
{
(new AlertDialog.Builder(getActivity())
.setIcon(R.drawable.ic_baseline_warning_24)
.setTitle("Discard changes")
.setMessage("Are you sure you want to discard changes and go back?")
.setPositiveButton("Yes", ( dialogInterface, i ) -> dismiss())
.setNegativeButton("No", ( dialogInterface, i ) -> {})
.show()).setCanceledOnTouchOutside(true);
}
This works almost perfectly, except for the fact that by the time the AlertDialog is shown on screen, the DialogFragment is already dismissed, and the actions taken in the AlertDialog are of no use at all.
So what I need now is a way to 'cancel' the dismissal of the DialogFragment, or a method that is called before the its dismissal. How do I solve this?
P.S.: getDialog().setCancelable(false) is not helpful to me since I do want the dialog to be cancelled; it's just that I want it cancelled conditionally.
Neither DialogFragment nor Dialog offer a condition that is checked before a cancel/dismiss event, that you can set.
They only offer listeners that are notified after said events have been fired, when the damage has already been done.
I have combed through the source code and have determined their is no way to force it into a state to ignore the first cancel/dismiss call but still allow the listeners to fire so you can catch the event, I have considered reflection to mess with fields, but it got to messy. I also tried forcing exceptions, to create invalid states, but there are no invalid states that would allow the functionality you want.
Here are the two sources.
https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/app/Dialog.java
https://android.googlesource.com/platform/frameworks/support/+/refs/heads/androidx-activity-release/fragment/fragment/src/main/java/androidx/fragment/app/DialogFragment.java
You could play with the containing Activity's Window to handle touch events and send them back to the DialogFragment using a FragmentResultListener, ie the DialogFragment is set to non-cancelable and when the user touches the outside the
Activity picks it up and calls setFragmentResult to send it back to the DialogFragment. This may or may not work depending on how dialogs detect/receive outside touch events, does it consume them or let them fall through, when its non-cancalable.
The easiest solution by far is to copy the source code above, probably just the DialogFragment and make your own to solve this problem.
OR OR OR
I'm totally missing the most obvious solution.
The app I'm working on shows some sensitive information that must not be shown on the "Recent Tasks" screen when stopping the app by pressing the home button.
I'd like to blur the sensitive data in the screenshot or show the app logo instead.
I am aware of the following approaches but they don't fit my requirements:
Setting the actvitie's android:excludeFromRecents to true in the manifiest prevents the app from being shown at all in the recent tasks. This would disrupt the user experience.
Using FLAG_SECURE results in a blank card on the recents tasks screen. (How do I prevent Android taking a screenshot when my app goes to the background?) I don't like the blank screen. However, I'll stick to this solution if there is no workaround.
Overriding onCreateThumbnail seems like the ideal solution but, unfortunately, doesn't work as it's currently not invoked by the OS :( (https://code.google.com/p/android/issues/detail?id=29370)
And then there are some workarounds that I tried out but that didn't work as hoped:
Start a new activity that shows the app logo in onPause so that it's screenshot is shown instead of the actual activitie's one. But the new activity takes too long to open and it disrupts the user experience.
Set the activitie's content view to an image of the app logo in onPause. That seemed like a great solution to me. Unfortunately, the screenshot for the recent tasks screen is taken at an unspecified time. During testing the app logo quickly appears before the app is closed when pressing 'Home' but the resulting screenshot shows the activity a short time before that.
Removing the sensitive data from the widgets (e.g. textView.setText("")) has the same problem of screenshot timing just mentioned.
Any alternative ideas or solutions to the listed workarounds?
I looked into this a couple of months ago for the same purpose as you.
Unfortunately, I had to conclude that it is simply not possible. I dug through the android source code and confirmed it.
There is no callbacks or methods from android that allows you to customize it (that works anyway). Besides FLAG_SECURE, this part of the code does not accept any input or change.
OnPause and similar lifecycle methods are called too late (the screenshot is taken already). All lifecycle methods that would hint that you're about to go into the background runs too late.
The image you see in the recent tasks is an actual screenshot - and thus isn't affected by changes you do (too late) to your view. That means you can't modify your view just-in-time (like making it invisible, replacing with something else, adding SECURE_FLAG, or any other obstruction of the view). As an aside, these images can be found on an emulator at /data/system_ce/0/recent_images.
The only exception is using FLAG_SECURE, which will prevent the screenshot from being taken of your application. I experimented with setting this FLAG in onPause and removing it in onResume, however as mentioned already these lifecycle methods runs after the screenshot is taken already, and thus had absolutely no effect.
As discussed in How to change the snapshot shown by recent apps list? there used to be a callback that you could use to customize the thumbnail: onCreateThumbnail. However, this does not work and it is never called. To be clear, the callback is still there, it is simply never called by the OS. The fact that it stopped working is poorly documented, but apparently was silently deprecated/removed in 4.0.3
As for the thumbnail itself, it is a screenshot taken serverside. It is taken before onPause is called (or in fact before any callbacks indicating that your activity is about to go into the background is called).
When your app does go into the background, your actual view is animated (to get that zoom-out transition). That animation can be affected through changes you do in onPause (if you're fast enough that is) (I experimented with setting opacity to 0 on the window among other things). This will however only affect the animation. When the animation is finished, the view is replaced by the screenshot taken earlier.
Also see these questions that discuss this:
When does Android take its recent apps switcher screenshot?
Show custom application image in task manager on ICS or JB
Android never call method onCreateThumbnail
Currently (28/10/2020) is impossibile customizing app thumbnail in recent apps screen.
As explained by #Dellkan in the previous answer, the onCreateThumbnail method is not called anymore by the OS.
Unfortunately, also the suggestion to create a kind of launcher/splash screen without the FLAG_SECURE flag to let the app take a screenshot of that activity is not working, because the screenshot is taken on the activity you see and not at the launch of the app.
You cannot even customize the color of window background when using FLAG_SECURE as reported here.
How about implementing a layout overlay on top of your entire activity?
Make it transparent, it's click-through by default, so no negative impact on UX while in use.
In onPause() set a half-transparent, blurred image as the background of that layout, the data will be scrambled behind it. In onResume() change the background to fully transparent again. Voila.
It might be faster than other types of overlays. The positive side effect is, if you do the unblurring as a short animation effect when the user goes back (with a proper library that uses C++ instead of Java), it might even look cool and the users wouldnt even mind seeing it.
I haven't tried this myself, but it's something you haven't tried yet.
Since onPause is called to late, I use WindowFocusChangeListener to observe when the Fragment loses focus. At this moment we can hide all view which show sensitive data:
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
view.getViewTreeObserver().addOnWindowFocusChangeListener(new ViewTreeObserver.OnWindowFocusChangeListener() {
#Override
public void onWindowFocusChanged(boolean hasFocus) {
// hide sensitive data when window moves to background (before system screenshot is captured)
myViewWithSensitiveData.setVisibility(hasFocus ? View.VISIBLE : View.INVISIBLE);
}
});
There is a way to customize it. You need your Activities with sensitive data to FLAG_SECURE in onCreate before you setContentView. Then you need an empty Activity, which renders whatever you want to have as the customized thumbnail. This usually is some sort of splash screen. This new Activity needs to be the launcher and is the only Activity not FLAG_SECURE. This Activity is launched and in onResume starts your actual Activity with the sensitive data.
Android OS will take a screenshot of that new Activity at the beginning of your App. Unfortunately the users will also see this Activity for a short moment. Since every other Activity is FLAG_SECURE, Android OS will use the only available screenshot it made at the beginning.
Was looking for a solution and found some dirty things in case you don't want to use 'FLAG_SECURE'. It doesn't give a nice picture but protects data and doesn't prevent making screenshots for the user while they are in the app.
protected void onPause () {
this.getWindow().getDecorView().getRootView().setScaleX((float)200);
this.getWindow().getDecorView().getRootView().setScaleY((float)200);
super.onPause();
}
protected void onResume () {
this.getWindow().getDecorView().getRootView().setScaleX((float)1);
this.getWindow().getDecorView().getRootView().setScaleY((float)1);
super.onResume();
}
I think this can only achieve through BroadCastReceiver but there is no receiver present. So therefore you first disable default screenshot functionality in android and then implementing your own functionality to take screenshot and before taking screenshot you should blur your secure information.
I created an android app. The screen never turns off:
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
Now I want to show a picture if the screen is not pressed for 5 minutes or something else. The app should not be closed, when pressing on the image the app should be open.
How can I realize that?
I would discourage you from taking this approach. Users expect to have a consistent user experience between various apps on their devices, and likely have a preference to how their device sleeps, either by having specified a sleep timeout or displaying a daydream as introduced in Android 4.2.
If you'd like to provide users with the option to display a screensaver associated with your app, I suggest including a Daydream in your app and otherwise acknowledging the user's preferences.
That being said, if you cannot use Daydream, you could observe if the app is being used or not. Two things come to mind:
Have the root view of your activity intercept touch events to observe if any of its children have been touched.
Observe the activity's onPause() and onResume() to acknowledge that the activity is still being displayed.
You could then invoke a Runnable by posting it to a view using postDelayed(Runnable action, long delayMillis), being wary to remove it when the activity is paused or the timer should be reset using removeCallbacks(Runnable).
I solved the problem!!!
I used that event:
public boolean dispatchTouchEvent(MotionEvent ev)
{
super.dispatchTouchEvent(ev);
// cancel my Timer
return true;
}
Thanks!!
I'm kind of new to android so please bear with me.
I need help with something, I have a rss feed that has energy tips in it. Instead of displaying all of them inside a listview I would like to display them one at a time on an activity that has a timer that automatically switches them inside a textview this activity would also have two buttons "prev" and "next" which would override the timer. Is it possible? Any pointers would be appreciated.
Thanks,
It sounds like the kind of view you might be looking for is a ViewSwitcher. This will allow you to switch and animate between different views in your app. While you can imagine more elaborate configurations (really, anything), this might be a good choice to you that's already baked into the framework. How to control the view is a slightly different question, however, there are multiple ViewSwitcher tutorials out there, and you can easily implement a Timer which switches between the views. What you do is fairly simple: tell the timer to fire after some interval. If the user presses some button before the timer fires, cancel the timer and (perhaps) reset it?
I'm trying to develop a feature for my app that pulls a string of text off of the internet (sort of like twitter, but a lot more basic) and displays it on the screen in a permeanent window. The user sees a large box with a refresh button next to it, and a small space below both where I would like to have a little progress monitor (just a TextView) which displays "Refreshing..." as soon as the refresh button is clicked, and then "Refresh successful!" once the string of text has been successfully pulled from the internet and displayed. This is just to reassure the user that something is actually happening when they press the button.
After a lot of research I've come to the conclusion that the way to update the TextView is to use a handler to execute a runnable which will update the text in the TextView. So my code looks like this:
refreshbutton.setOnClickListener(new View.OnClickListener()
{
public void onClick(View abc)
{
//display "Refreshing..."
refreshhandler.post(refreshingmsg);
/*Code to pull the string of text from the internet goes here (not shown)*/
//display "Refresh successul!"
successfulhandler.post(successfulmsg);
//clear the "Refresh successful" message after 2 seconds
clearhandler.postDelayed(clearmsg, 2000);
}
});
I hope that sort of makes sense. My issue is that these things all happen at once: I want the "Refreshing..." message to display, THEN for the phone to connect to the internet to find the string of text for the update, and THEN for the "Refresh Successful" message to display once the internet string has been successfully displayed. But what actually happens is that none of the commands in the onClick method actually happen until the phone has already pulled the message from the internet so the "Refreshing..." message isn't displayed at all. So what actually happens when the user clicks the button is that nothing happens for a second or two (presumably because the phone is busy pulling the string from the internet), then the string from the internet is suddenly displayed along with the message saying "Refresh Successful!".
So why is this happening? Is onClick supposed to work this way? If so, is there a workaround? I haven't posted my runnable and handler statements because I'm not sure it'll make any difference... but if it would help then I can post them!
Also, you can probably tell from this that I haven't been doing android (or indeed java) for very long, but I'm trying my best so please be gentle! Thanks :)
What is happening is that your code is locking on the network communication and doesn't let your app update itself with the handler value.
You should create an AsyncTask to that work for you.
On the OnPreExecute method, change your UI to show the user that you are refreshing your content. Reading your code it appears that you are updating an image, do that here.
On the doInBackground method do your actual network communication, an optional step is to use onProgressUpdate(Progress...) to update your UI to notify the user that the operation is progressing.
On the onPostExecute method you them update your UI to reflect the new content.
For more info check the documentation in AsyncTask, you can also search StackOverflow, there's a lot of good questions about it here.
As a side note, I don't recommend that you use handlers at all, AsyncTasks are easier to use and the code looks better.