In the setup part of my app, I want to take a picture then display it. I want to display a Progress Dialog while saving the image to disk. My ProgressDialog code works except on a Droid Mini running KitKat.
I have tried a number of approaches, including this blog https://www.workreloaded.com/2011/06/how-to-use-the-android-camera/. I liked the approach but the ProgressDialog did not appear for me.
I think the root of the problem is some timing issue using the UI thread. But I can't precisely diagnose it and definitely can't solve it. My questions:
1. Does camera.takePicture() run on the UI thread?
2. What would prevent a ProgressDialog from appearing?
3. Does someone have a working example (besides the above post) that I could use?
I'm posting code that works but is bad practice and code that doesn't work but would be more maintainable.
The following code with a test for KitKat works, but is bad practice.
public class PreviewTestNextButtonListener implements Button.OnClickListener {
private static final String TAG = "PreviewTestButtonListener";
PreviewTest previewTest;
Camera camera;
public PreviewTestNextButtonListener(PreviewTest context, Camera camera) {
this.previewTest = context;
this.camera = camera;
}
public void onClick(View v) {
Log.v(TAG, "Taking picture");
try {
PhotoSaverPreviewTest photoSaver = new PhotoSaverPreviewTest(previewTest);
if (Build.VERSION.SDK_INT != Build.VERSION_CODES.KITKAT) {
// don't display the progress dialog in kit kat because my Droid Mini running KitKat has a problem with
// this. But the Mini is so fast that the save is instantaneous
previewTest.showProgressDialog();
}
// photoSaver will launch the StoredRotationTest
camera.takePicture(null, null, photoSaver);
} catch (Exception e) {
Log.e(TAG, "failed to start Stored Rotation Test because " + e, e);
}
}
}
In an alternative version, I tried simply calling the ProgressDialog from takePicture() but that doesn't work. I also tried an AsyncTask, but that did not present a ProgressDialog either.
public void onPictureTaken(final byte[] imageData, Camera camera) {
Log.e(TAG, "starting onPictureTaken");
// **** this does not present the ProgressDialog
activity.showProgressDialog();
if (imageData != null) {
Log.e(TAG, "got image data");
camera.startPreview();
Display display = ((android.view.WindowManager) activity.getSystemService(Context.WINDOW_SERVICE))
.getDefaultDisplay();
int displayRotation = display.getRotation();
try {
// showProgressDialog();
// save the image to disk in the background
RotateAndWriteImageAsync imageWrite = new RotateAndWriteImageAsync(activity, displayRotation, imageData);
imageWrite.mySpecialMove();
Intent intent;
// during set up, launch the stored image rotation test
intent = new Intent(activity, StoredRotationTest.class);
activity.startActivity(intent);
PreviewTest.dismissProgressDialog();
} catch (Exception e) {
Log.e(TAG, "error saving image or start rotation test: " + e);
}
} else {
Log.e(TAG, " ");
Log.e(TAG, "NO IMAGE DATA passed in parameter from camera");
Log.e(TAG, " ");
}
}
And finally, the method that presents the ProgressDialog from the PreviewTest Activity.
/** Called by Next Button which is a bit of kludge.*/
public void showProgressDialog() {
if (progress == null) {
progress = new ProgressDialog(this);
progress.setTitle("Saving picture");
}
progress.show();
}
/** Called by StoredRotationSetup which is a bit of a kludge.*/
public static void dismissProgressDialog() {
if (progress != null && progress.isShowing()) {
progress.dismiss();
}
}
Mark Murphy(aka Commonsware) gave me some guidance during his office hours. ProgressDialogs are bad form: https://ux.stackexchange.com/questions/12637/what-research-is-there-suggesting-modal-dialogs-are-disruptive. My ProgressBar only shows when the user is waiting, but it does not pop up.
ProgressBar solved my problem. Here is the .xml:
<ProgressBar
android:id="#+id/next_progress_bar"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_above="#+id/rotate_orientation_button"
style="#android:style/Widget.ProgressBar.Large"
android:indeterminate="true"
android:visibility="invisible" />
And the Java:
// progress bar
ProgressBar progressBar = (ProgressBar) findViewById(R.id.next_progress_bar);
progressBar.setVisibility(View.VISIBLE);
Related
I made an OCR application that makes a screenshot using Android mediaprojection and processes the text in this image. This is working fine, except on Android 9+. When mediaprojeciton is starting there is always a window popping up warning about sensitive data that could be recorded, and a button to cancel or start recording. How can I achieve that this window will only be showed once?
I tried preventing it from popping up by creating two extra private static variables to store intent and resultdata of mediaprojection, and reusing it if its not null. But it did not work (read about this method in another post).
// initializing MP
mProjectionManager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);
// Starting MediaProjection
private void startProjection() {
startActivityForResult(mProjectionManager.createScreenCaptureIntent(), REQUEST_CODE);
}
// OnActivityResult
protected void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
if (requestCode == 100) {
if(mProjectionManager == null) {
cancelEverything();
return;
}
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
if(mProjectionManager != null)
sMediaProjection = mProjectionManager.getMediaProjection(resultCode, data);
else
cancelEverything();
if (sMediaProjection != null) {
File externalFilesDir = getExternalFilesDir(null);
if (externalFilesDir != null) {
STORE_DIRECTORY = externalFilesDir.getAbsolutePath() + "/screenshots/";
File storeDirectory = new File(STORE_DIRECTORY);
if (!storeDirectory.exists()) {
boolean success = storeDirectory.mkdirs();
if (!success) {
Log.e(TAG, "failed to create file storage directory.");
return;
}
}
} else {
Log.e(TAG, "failed to create file storage directory, getExternalFilesDir is null.");
return;
}
// display metrics
DisplayMetrics metrics = getResources().getDisplayMetrics();
mDensity = metrics.densityDpi;
mDisplay = getWindowManager().getDefaultDisplay();
// create virtual display depending on device width / height
createVirtualDisplay();
// register orientation change callback
mOrientationChangeCallback = new OrientationChangeCallback(getApplicationContext());
if (mOrientationChangeCallback.canDetectOrientation()) {
mOrientationChangeCallback.enable();
}
// register media projection stop callback
sMediaProjection.registerCallback(new MediaProjectionStopCallback(), mHandler);
}
}
}, 2000);
}
}
My code is working fine on Android versions below Android 9. On older android versions I can choose to keep that decision to grant recording permission, and it will never show up again. So what can I do in Android 9?
Thanks in advance, I'm happy for every idea you have :)
Well the problem was that I was calling
startActivityForResult(mProjectionManager.createScreenCaptureIntent(), REQUEST_CODE);
every time, which is not necessary (createScreenCaptureIntent() leads to the dialog window which requests user interaction)
My solution makes the dialog appear only once (if application was closed it will ask for permission one time again).
All I had to do was making addiotional private static variables of type Intent and int.
private static Intent staticIntentData;
private static int staticResultCode;
On Activity result I assign those variables with the passed result code and intent:
if(staticResultCode == 0 && staticIntentData == null) {
sMediaProjection = mProjectionManager.getMediaProjection(resultCode, data);
staticIntentData = data;
staticResultCode = resultCode;
} else {
sMediaProjection = mProjectionManager.getMediaProjection(staticResultCode, staticIntentData)};
}
Every time I call my startprojection method, I will check if they are null:
if(staticIntentData == null)
startActivityForResult(mProjectionManager.createScreenCaptureIntent(), REQUEST_CODE);
else
captureScreen();
If null it will request permission, if not it will start the projection with the static intent data and static int resultcode, so it is not needed to ask for that permission again, just reuse what you get in activity result.
sMediaProjection = mProjectionManager.getMediaProjection(staticResultCode, staticIntentData);
Simple as that! Now it will only showing one single time each time you use the app. I guess thats what Google wants, because theres no keep decision checkbox in that dialog like in previous android versions.
I'm using fresco image viewer on my app for showing post photos when clicked make it full screen. It was working last week, i didn't changed anything in my code but now it doesn't work.
Here is my showFullScreen function
public void showFullScreenImage(final Context activity, final String imageURL) {
Handler handler = new Handler(activity.getMainLooper());
handler.post(new Runnable() {
public void run() {
try {
List<String> pictures = new ArrayList<>();
pictures.add(imageURL);
GenericDraweeHierarchyBuilder hierarchyBuilder = GenericDraweeHierarchyBuilder.newInstance(mContext.getResources())
.setFailureImage(R.drawable.error)
.setProgressBarImage(new ProgressBarDrawable());
new ImageViewer.Builder<>(activity, pictures).setCustomDraweeHierarchyBuilder(hierarchyBuilder).setStartPosition(0).show();
} catch (Exception ex) {
ex.printStackTrace();
}
}
});
}
It shows black screen, but it's not freezing, i can swipe to dismiss the view.
Okay I found the solution, upgrade all repos the final update from build.gradle fresco and frescoImageViwer than it'll be work.
I'm using parse.com Android SDK to manage some images in my app. Is cancel() the only way to stop a transaction with the parse.com server?
Minimal example:
final ParseFile file = ... ;
file.getDataInBackground(new GetDataCallback() {
//called when loading is done
#Override
public void done(byte[] bytes, ParseException e) {
Log.e(TAG, String.valueOf(bytes == null));
}
}, new ProgressCallback() {
//called to notify progress
#Override
public void done(Integer integer) {
Log.e(TAG, String.valueOf(integer));
if (integer > 50) {
file.cancel();
}
}
});
I would expect that loading stops after the 50% is reached, but it does not. My log in this situation would be:
1
2
3
....
49
50
51
....
98
99
100
true
The only difference from times you call cancel() and times you don't, is that if you canceled the byte[] result is null. But that is really secondary, the point here is that file keeps consuming bandwidth and, moreover, overlaps with future downloads, slowing things down.
Is there any way to really stop ParseFile loading? Do you see any workaround, like stopping its thread? Maybe something using the underlying bolts framework? Using my own async task?
Example: continueWhile() method might be useful, but I can't figure out how to use it.
I would like to know the reason for the downvote, maybe the common title? That is really what I am experiencing: ParseFile.cancel() is not working. And it should, according to the official docs.
Comments suggest that I should simply call break. While I don't think it would work, I might clarify that the code I posted was just a minimal, concise working example providing both context and the issue. I don't want to cancel() the transaction from inside the progress callback; I want to call parseFile.cancel() from everywhere. I put in the progress callback to show that, while it should stop, it doesn't.
Edit This is what I'm really trying to do. I have tried different ways but this is it.
ParseFile currentFile;
public void setFile(ParseFile file) {
if (currentFile != null) {
currentFile.cancel();
}
currentFile = file;
currentFile.getDataInBackground(new GetDataCallback() {
...
}, new ProgressCallback() {
... // logs
});
}
With such code and say, two big images to download, things go like:
//calling setFile(file1)
1
2
3
...
20
21
22
//calling setFile(file2), thus also calling file1.cancel()
1
2
23 //file1 going on!
3
24
4
25
... //things get slower and this screws up progress bars and such.
TL;DR;
The only conclusion I can draw at this point is that there is a difference in our implementations that is causing cancel to fail for you.
EDIT: this seems to be the case as seen in your own answer. The difference being SDK versions. https://stackoverflow.com/a/32034500/2680506
Full Answer:
The description for the cancel() method:
"Cancels the current network request and callbacks whether it's uploading or fetching data from the server."
I was curious about this so I did a little testing of my own. I took my app, made a ParseFile from the bytes of an image and attempted to save it in the background.
Test 1
Bitmap file = BitmapFactory.decodeResource(context.getResources(), R.drawable.background);
ByteArrayOutputStream stream = new ByteArrayOutputStream();
file.compress(Bitmap.CompressFormat.PNG, 100, stream);
byte[] byteArray = stream.toByteArray();
final ParseFile myTestFile = new ParseFile(byteArray);
myTestFile.saveInBackground(new SaveCallback(){
#Override
public void done(ParseException e) {
if(e == null)
{
Log.i(null, "Done saving.");
}
}
}, new ProgressCallback(){
#Override
public void done(Integer progress) {
Log.i(null, "Progress at " + progress + "%");
if(progress > 50)
{
myTestFile.cancel();
}
}});
//myTestFile.cancel();
Test 2
Bitmap file = BitmapFactory.decodeResource(context.getResources(), R.drawable.background);
ByteArrayOutputStream stream = new ByteArrayOutputStream();
file.compress(Bitmap.CompressFormat.PNG, 100, stream);
byte[] byteArray = stream.toByteArray();
ParseFile myTestFile = new ParseFile(byteArray);
myTestFile.saveInBackground(new SaveCallback(){
#Override
public void done(ParseException e) {
if(e == null)
{
Log.i(null, "Done saving.");
}
}
}, new ProgressCallback(){
#Override
public void done(Integer progress) {
Log.i(null, "Progress at " + progress + "%");
}});
myTestFile.cancel();
The results for Test 1 were similar to what you describe, because the file is very small I only got one progress callback at 100% but then it also invoked the SaveCallback.
In Test 2, however, the cancel() method appears to function as one would expect, resulting in no logs or callbacks.
It appears that cancel fails to work because you are calling it from the Callback. This is consistent with the fact that you continue to see ProgressCallbacks after you originally cancel in your own tests.
EDIT
I just uploaded an image and tested cancel for myself, in the onCreate() method of my activity I have this code:
ParseQuery<ParseObject> newQuery = ParseQuery.getQuery("TestObject");
newQuery.findInBackground(new FindCallback<ParseObject>(){
#Override
public void done(List<ParseObject> objects, ParseException e)
{
ParseFile myTestFile = objects.get(0).getParseFile("file");
myTestFile.getDataInBackground(new GetDataCallback()
{
#Override
public void done(byte[] data, ParseException e)
{
Log.i(null, "Download finished");
}
},
new ProgressCallback()
{
#Override
public void done(Integer percentDone)
{
Log.i(null, "Download at " + percentDone + "%");
}
});
//myTestFile.cancel();
}});
When cancel is commented, It will enter the GetDataCallback with a populated byte array. When cancel is not commented no call back will occur. Strangely enough the ProgressCallback is never called although it says it is guaranteed. However, it still appears that cancel is working for me.
Looks like this was a real bug, now fixed. I used to stay with the v1.9.2 release; today I updated to v1.10.0 and everything works fine.
public String newUser = "false";
public double lat = 0.0, lon = 0.0;
I have the following function in my android app (called when a Button is clicked) which starts a thread:
public void SignUpFunction(View view) {
assignValues();
String filledAll = checkIfFilled();
if (filledAll.equals("true")) {
Log.d("LIFECYCLE", "calling thread..");
//my thread
new validateThread().start();
Log.d("After thread start","This log call does not occur");
if (newUser.equals("true")) {
Toast.makeText(this, "Please wait as we obtain your location", Toast.LENGTH_SHORT).show();
getMyLocationFunction();
} else {
return;
}
}
}
validateThread:
class validateThread extends Thread {
public void run() {
Log.d("LIFECYCLE", "validateThread entered...");
try {
newUser = "true";
Log.d("validateThread", "Validated and found new user");
} catch (Exception e) {
Log.d("validateThread", "Exception in validateThread: " + e.getMessage());
}
}
}
The thread runs correctly...but after the last line, it does not go back to its point of start. I don't understand why this is happening because I've used threads before and they all work correctly.
I know I can just give the getMyLocation function inside the thread but I really need it this way.
I've searched for similar questions but none helped.. What am I doing wrong here?
Thanks in advance.
It's a race. SignUpFunction should wait until validateThread decides whether or not to set newUser = "true". Even with the race your code may work sometimes, but that is by accident.
I am struggling with one very strange bug in my app.
I have added TTS to it, and I am using the build one. The user can choose the language from the spinner which is filled in during AsyncTask started in onResume().
The AsyncTask looks like this:
private class AsyncTTSDownload extends AsyncTask<Void, Integer, String> {
#Override
protected String doInBackground(Void... params) {
try {
languagesTTS = tts.testLang();
} catch (Exception ex) {
if (D)
Log.e(TAG, ex.toString());
}
return null;
}
#Override
protected void onPostExecute(String result) {
ttsUpdate.dismiss();
TTSSpinnerAdapter adapterTTS = new TTSSpinnerAdapter(
MyTTS.this, android.R.layout.simple_spinner_item,
languagesTTS);
int savedLangTTS = ttsLang.getInt("savedTTS", -1);
langTTS.setAdapter(adapterTTS);
if (savedLangTTS == -1)
{
try {
int langObject = languagesTTS.indexOf(tts.getLanguage());
langTTS.setSelection(langObject);
} catch (IndexOutOfBoundsException ie) {
langTTS.setSelection(0);
}
} else {
langTTS.setSelection(savedLangTTS);
}
Locale langChoosen = (Locale) langTTS.getItemAtPosition(langTTS
.getSelectedItemPosition());
tts.setTTSLanguage(langChoosen);
}
#Override
protected void onPreExecute() {
ttsUpdate = ProgressDialog.show(MyTTS.this, "Wait",
"Loading TTS...");
ttsUpdate.setCancelable(false);
}
}
the thing is, that I am from time to time getting different number of languages supported. This is on this same device, during this same run. Just I open and close Activity with TTS. This bug is causing IndexOutOfBoundsException. This is my way of getting TTS languages:
public List<Locale> testLang() {
Locale[] AvalLoc = Locale.getAvailableLocales();
List<Locale> listaOK = new ArrayList<Locale>();
String tester = "";
for (Locale l : AvalLoc) {
if(tester.contains(l.getLanguage()))
{
continue;
}
int buf = tts.isLanguageAvailable(l);
if (buf == TextToSpeech.LANG_MISSING_DATA
|| buf == TextToSpeech.LANG_NOT_SUPPORTED) {
//TODO maybe
} else {
listaOK.add(l);
tester += l.getLanguage() + ";";
}
}
tts.setLanguage(Locale.ENGLISH);
return listaOK;
}
For now I've only find out a small hack for not showing this error, just save in shared preferences number of languages and compare it with what tts received, but it is not working well at all. Each time I am getting different number.
For me it seems, that something is not finished or started when I am starting again this same activity after return, because this is tts.isAvaliableLanguage(l) who is deciding whether language is supported or not and from time to time, one language is not supported and after reload it is.
EDIT:
As there appeared new comment about my question I need to add one important thing about TTS engine itself.
testLang() is a method inside my class Called TTSClass, that is implementing TextToSpeech.OnInitListener. tts object is created in onCreate of MyTTS activity and this constructor looks like this in TTSClass:
public TTSClass(Context context, Locale language) {
contextTTS = context;
languageTTS = language;
tts = new TextToSpeech(contextTTS, this);
}
and call in activity:
tts = new TTSClass(getApplicationContext(), Locale.ENGLISH);
Because TTSClass implements TextToSpeech.OnInitListener there is also onInit() method which looks like this:
#Override
public void onInit(int status) {
if (status == TextToSpeech.SUCCESS) {
int result = 0;
result = tts.setLanguage(languageTTS);
if (result == TextToSpeech.LANG_MISSING_DATA
|| result == TextToSpeech.LANG_NOT_SUPPORTED) {
if(D) Log.e(TAG, "This Language is not supported");
}
if(D) Log.d(TAG,"Initialized");
} else {
if(D) Log.e(TAG, "Initilization Failed!");
}
}
So, this is everything connecting to this class and problem I think. If anything is missing, let me now.
EDIT2:
Suggested by shoe rat comment I've run few more tests, and the outcome is just amazing, or extraordinary, I think it is better word.
So what I've done was adding 3 Log from different places in code informing me about list size on different stages.
First was added in onInit() in if status == TextToSpeech.SUCCESS. This one is just simple call of testLang().size(). The outcome is 5 languages - that is the correct number and it is always like this, no matter if there is or isn't an exception.
Second was added there:
protected String doInBackground(Void... params) {
try {
Log.w(TAG,"before: "+tts.testLang().size());
languagesTTS = tts.testLang();
}
and this one is starting to act quite weird. It is sometimes, or even quite often, showing number lower than 5. But this is not the strangest thing.
The third one is just at the beginning of onPostExecute checking the size of languagesTTS. And believe or not, the number is quite often totally different from the second log. However, it is never smaller. It can be equal or bigger.
Does anyone know, what is going one?
I've found solution. It came out that indeed it was initialization problem.
I'm not sure if documentation is saying anything about it, but it seem like the TTS engine initialization is done asynchronously, so it can finish at any time.
My solution was to change the doInBackground() method like this:
#Override
protected String doInBackground(Void... params) {
try {
while(!TTSClass.isInit){}
languagesTTS = tts.testLang();
} catch (Exception ex) {
if (D)
Log.e(TAG, ex.toString());
}
return null;
}
and in onInit() method I've added isInit public static boolean variable:
#Override
public void onInit(int status) {
if (status == TextToSpeech.SUCCESS) {
int result = 0;
result = tts.setLanguage(languageTTS);
if (result == TextToSpeech.LANG_MISSING_DATA
|| result == TextToSpeech.LANG_NOT_SUPPORTED) {
if(D) Log.e(TAG, "This Language is not supported");
}
if(D) Log.d(TAG,"initialized");
isInit = true;
} else {
if(D) Log.e(TAG, "Initilization Failed!");
}
}
Hope, that someone will find it helpful.