Taking a picture from the camera fails 20% of the time - android

var camera = {
settings : {
quality : 50,
targetWidth : 1024,
targetHeight : 1024,
correctOrientation : true
}
};
var error = function(message) {
alert("Error happened while trying to get a picture", message);
};
document.addEventListener("deviceready", function() {
camera.toFile = function() {
this.settings.destinationType = navigator.camera.DestinationType.FILE_URI;
return this;
},
camera.toBase64 = function() {
this.settings.destinationType = navigator.camera.DestinationType.DATA_URL;
return this;
},
camera.fromCamera = function() {
this.settings.sourceType = navigator.camera.PictureSourceType.CAMERA;
return this;
};
camera.fromLibrary = function() {
this.settings.sourceType = navigator.camera.PictureSourceType.PHOTOLIBRARY;
return this;
};
camera.fromPhotoAlbum = function() {
this.settings.sourceType = navigator.camera.PictureSourceType.SAVEDPHOTOALBUM;
return this;
}
camera.get = function(callback) {
navigator.camera.getPicture(function(data) {
alert("taking a picture successful");
callback(data);
}, error, camera.settings);
};
}, false);
This is my small wrapper for the camera. And I call it like this:
camera.fromPhotoAlbum().toBase64().get(function(base64){});
About 20% of the time, the "alert("taking a picture successful");" is not called, while no error is shown. If I cancel taking a picture, an alert with the message "Error happened while trying to get a picture" is shown, so the error callback works.
Basically nothing happens. I've tested it on a Samsung Galaxy S2 on CM9 and a brand new HTC One X.

There was another question recently about this same problem that I answered. We ran into this at my company and solved it. It has more to do with the Android system than Phonegap.
What's happening is when you start the camera, your app goes into onStop(). While there, the Android system has the right to kill your app if it needs memory. It just so happens that memory usually gets low when the camera takes a picture and dumps it into memory, so there's a good chance your app will get killed while your user takes a picture.
Now that your app is dead, when the camera finishes, it restarts your app. That's why it's acting so weird; the camera comes back into your app, but not the same instance that it had before, so your callback never gets called, since it doesn't exist anymore.
You can reduce the frequency at which this occurs by reducing the quality of the picture and passing it by URI instead of data to your app, but the problem won't go away completely.
To work around the callback never happening, we made a Java callback that starts the camera and saves the picture to the same location every time it takes one. Then, when the app starts back up from getting killed, it looks in that location for the picture.
It's a weird solution to a stupid problem, but if your app gets killed, that camera callback simply won't happen. If you need more information on how to make the Java callback to do this, let me know and I'll put our code up here. Otherwise, take a look at this SO answer for more info.
EDIT: Here's the code we use in our main DroidGap activity:
private static final String folderPath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/appName";
private static final String filePath = "phonegapImage.jpg";
#Override
public void onCreate(Bundle savedState) {
//...The rest of onCreate, this makes the Java available in JavaScript
appView.addJavascriptInterface(this, "Camera");
}
public void takePhoto(final String callback) {
Log.v("Camera Plugin", "Starting takePhoto callback");
Intent intent = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
intent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, Uri.fromFile(new File(folderPath, filePath)));
startActivityForResult(intent, TAKE_PICTURE);
}
public String getPhotoUri() {
return Uri.fromFile(new File(folderPath, filePath)).toString();
}
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case TAKE_PICTURE:
if (resultCode == Activity.RESULT_OK) {
//Do whatever you need to do when the camera returns
//This is after the picture is already saved, we return to the page
}
break;
default:
Log.v("Camera", "Something strange happened...");
break;
}
}
Then, in your JavaScript, you can invoke the camera with:
Camera.takePhoto("onPhotoURISuccess");
//Then, to get the location of the photo after you take it and load the page again
var imgPath = Camera.getPhotoUri();
So, that's about it. Just make sure to change all of the path/file/page/etc names to what you want to use in your app. This will overwrite that image every time a picture is taken, but you can probably figure something out to dynamically name them if you don't want that. You can use that URI just as you would any other path in your JavaScript.

Related

Mediaprojection issues on Android 9+

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.

How to check if intent (activity) contains string in it's package name (Espresso)?

I test my app with UI testing and would like to check if the camera app opens.
I have did this with:
#Test
public void profileImageClickOpensCamera() {
mIntentsRule.getActivity().startActivity(new Intent(mIntentsRule.getActivity(), ProfileActivity.class));
onView(withId(R.id.circleProfileImage)).perform(click());
intended(toPackage("com.android.camera"));
}
It is working fine on most devices, however if I rain it on SAMSUNG Galaxy S8, which has "com.sec.android.app.camera" package of it's camera app, the test fails.
My question is, how could I check with espresso that the package contains the word "camera" ?
It's not the best solution because a device's camera app's package name could be anything, but even better then what I got know.
So I would like to do something like:
intended(StringContains(toPackage("com.android.camera")));
Any suggestions?
Thanks in advance.
You can test the intent action instead of package.
Something like intended(hasAction(MediaStore.ACTION_IMAGE_CAPTURE)) or intended(hasAction(equalTo(MediaStore.ACTION_IMAGE_CAPTURE))) should work.
I had the same situation and I've managed to solve it like this:
PackageManager packageManager = InstrumentationRegistry.getTargetContext().getPackageManager();
String pack = resultData.resolveActivity(packageManager).getPackageName();
intended(toPackage(pack));
In my situation I had an activity with a button which opens the camera, lets you take a picture and returns with it in your activity. The full code of this test would be:
#Test
public void testCameraIntent() {
Bitmap icon = BitmapFactory.decodeResource(
InstrumentationRegistry.getTargetContext().getResources(),
R.drawable.husky);
// Build a result to return from the Camera app
Intent resultData = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
resultData.putExtra("data", icon);
Instrumentation.ActivityResult result = new Instrumentation.ActivityResult(Activity.RESULT_OK, resultData);
PackageManager packageManager = InstrumentationRegistry.getTargetContext().getPackageManager();
String pack = resultData.resolveActivity(packageManager).getPackageName();
// Stub out the Camera. When an intent is sent to the Camera, this tells Espresso to respond
// with the ActivityResult we just created
intending(toPackage(pack)).respondWith(result);
// Now that we have the stub in place, click on the button in our app that launches into the Camera
onView(withId(R.id.btn_takePicture)).perform(click());
intended(toPackage(pack));
}
And this is the result :-) the image with Husky dog is a local image I've set to be sent in my custom ActivityResult:

Prevent MediaProjection object from being lost on killing the Application

I am using MediaProjection to take screenshot.This is what I am doing.I created an overlay icon using service.On clicking the overlay icon a screenshot is taken.The problem is that whenever the Application is killed either by pressing back button or manually by swiping it the MediaProjection object is lost.Is there a way to maintain the MediaProjection and avoiding requesting for MediaProjection each time application is killed. I have already seen this but I am still unable to do it.
In my Mainactivity Onclick contains startActivityForResult and the resulting onActivityResult is as follows:
#Override
protected void onActivityResult(final int requestCode,final int resultCode,final Intent data)
{
if (requestCode == requestcode)
{
if (resultCode == RESULT_OK)
{
Singelton.setScreenshotPermission((Intent) data.clone());
Singelton.putmanger(mediaProjectionManager);
}else if (resultCode==RESULT_CANCELED) {
Singelton.setScreenshotPermission(null);
}
}
}
The Singelton class is as follows:
protected static void setScreenshotPermission(final Intent permissionIntent)
{
screenshotPermission = permissionIntent;
//screenshotPermission becomes null once the application is killed
}
public static MediaProjection getData()
{
return (mediaProjection);
}
public static void getScreenshotPermission()
{
if (screenshotPermission != null)
{
Log.d("screenshotpermisson", "screenshotPermission != null ");
if(mediaProjection!=null)
{
Log.d("mediaprojection", "mediaprojection != null ");
mediaProjection.stop();
mediaProjection = null;
}
mediaProjection = mediaProjectionManager.getMediaProjection(Activity.RESULT_OK, (Intent) screenshotPermission.clone());
}
else
{
//Here I need to request for media projection again without starting activity
}
}
My Service class for overlay icon handles click as follows:
public void createDisplay()
{
if (mediaProjection == null)
{
Singelton.getScreenshotPermission();
mediaProjection = Singelton.getData();
}
mImageReader = ImageReader.newInstance(width, height, PixelFormat.RGBA_8888, 1);
vd = mediaProjection.createVirtualDisplay("screen-mirror", width, height, mDensity, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, mImageReader.getSurface(), null, null);
mImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener(){..}
}
I am new to android and having a hard time figuring this out.Any help will be appreciated.
Just re-create the media projection as fadden implied in his comment. Since every time your service is killed the associated overlay is also killed with it therefore you have to re-create the media projection anyway when restarting the service.
You already doing fine by not consuming the original intent and instead using an clone as answered here https://stackoverflow.com/a/33892817/3918978 .
However if the permission or the MediaProjection shouldn't be valid anymore just get a new one (start an activity asking for it).

Android startCamera gives me null Intent and ... does it destroy my global variable?

I have the next problem:
when I try to start my camera, I can take the picture, even save it on my sdcard, but when I'm going to get the path for showing it on my device I get errors.
My global variables are 2 (I used 1 but 2 are better for making sure it's a strange error):
private File photofile;
private Uri mMakePhotoUri;
and this is my start-camera function:
#SuppressLint("SimpleDateFormat")
public void farefoto(int num){
// For naming the picture
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd_HHmmss");
String n = sdf.format(new Date());
String fotoname = "Immagine-"+ n +".jpg";
//Going through files and folders
File photostorage = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
File photostorage2 = new File(photostorage, "Immagini");
System.out.println(photostorage+"\n"+photostorage2);
photostorage2.mkdirs();
// My file (global)
photofile = new File(photostorage2, fotoname);
Intent i = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); //intent to start camera
// My URI (global)
mMakePhotoUri = Uri.fromFile(photofile);
new Bundle(); // I took this code from internet, but if I remove this line, it's the same
i.putExtra(MediaStore.EXTRA_OUTPUT, mMakePhotoUri);
startActivityForResult(i, num); //num would be 1 on calling function
}
and my activityresult:
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
// TODO Auto-generated method stub
if (requestCode == 1){
try{ // tring my global URI
photo = f.decodeAndResizeFile(new File(mMakePhotoUri.getPath()));
}
catch(NullPointerException ex){
System.out.println("fail");
ex.printStackTrace();
try{ // Trying my global FILE
photo = BitmapFactory.decodeFile(photofile.getAbsolutePath());
} catch (Exception e){
e.printStackTrace();
Toast.makeText(this, "C'รจ stato un errore. Riprova a scattare la foto.", Toast.LENGTH_LONG).show();
}
......
......
.......
}
Always getting NullPointerException
But... if I take the picture again, IT WORKS!!.
I've read everything I could here... but it doesn't have logic when I modify a global variable and I cannot take it again...
Thanks in advance.
Cheers.
SOLUTION
As Alex Cohn said, my problem was that I was calling onCreate before onActivityResult because of a probably push out of memory (because sometimes doesn't do it), so I wanted to have my app "healthy" and I tried some try / catch and so I get the data even if it's calling onCreate or onActivityResult for the first call, and I wrote that data in a Bundle like explained in the link of restoring our state.
Thanks!.
It's possible that launching of ACTION_IMAGE_CAPTURE will push your activity out of memory. You should check (I'd simply a log, debugger may have its own side effects) that onCreate() of your activity is called before onActivityResult(). If this is the case, you should prepare your activity to reinitialize itself, probably using onSaveInstanceState(Bundle).
Note that the decision whether to shut down the activity, or keep it in background, depends on the overall system state that is beyond your control. It won't surprise me if the decision when you take the first picture, is "shut him down!", but when you take picture again, it is "keep him in background".
It doesnot destroy the variables. But after So many days of research LOL I have got a solution of my mistake.
When I put the debugger the method calls it like after taking a picture.
onCreate()
onActivityResult()
onCeate()
onResume()
Its fixed by just put these following lines in t the menifest. It happens due to camera config changes & window soft input mode.
<activity
android:name="packageName.Activity"
android:configChanges="orientation|keyboardHidden|screenSize"
android:label="#string/app_name"
android:screenOrientation="portrait"
android:windowSoftInputMode="adjustResize" >

handling device rotation with camera activity

I have an activity which the first thing it does is send a camera intent. I want the user to take a picture and then I will use it to do something. My problem is that If the user changes the rotation while taking the picture the app keeps on looping inside the camera until he finishes the entire process while staying in a single device orientation.
Here is the code:
protected override void OnCreate (Bundle bundle)
{
base.OnCreate (bundle);
_dateTextView = FindViewById<TextView> (Resource.Id.DateLabel);
ViewModel.RecomendDishViewModel.RecomendationSent += RecomendationSent;
if (IsThereAnAppToTakePictures())
{
CreateDirectoryForPictures();
var imageButton = FindViewById<ImageButton> (Resource.Id.TakeImageWaterMark);
_imageView = FindViewById<ImageView> (Resource.Id.UserDishImage);
imageButton.Click += TakePictureClicked;
if(_bitmap != null)
{
_imageView.RecycleBitmap ();
_imageView.SetImageBitmap(_bitmap);
}
}
_dateTextView.Click += DateLabelClicked;
TakePictureClicked ()
}
protected void TakePictureClicked ()
{
Intent intent = new Intent(MediaStore.ActionImageCapture);
_file = new Java.IO.File(_dir, String.Format("myPhoto_{0}.jpg", Guid.NewGuid()));
intent.PutExtra(MediaStore.ExtraOutput, Uri.FromFile(_file));
StartActivityForResult(intent, 0);
}
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
base.OnActivityResult(requestCode, resultCode, data);
// make it available in the gallery
Intent mediaScanIntent = new Intent(Intent.ActionMediaScannerScanFile);
Uri contentUri = Uri.FromFile(_file);
mediaScanIntent.SetData(contentUri);
SendBroadcast(mediaScanIntent);
// display in ImageView. We will resize the bitmap to fit the display
// Loading the full sized image will consume to much memory
// and cause the application to crash.
_bitmap = _file.Path.LoadAndResizeBitmap (518, 388);
if(_bitmap != null)
{
MemoryStream stream = new MemoryStream();
_bitmap.Compress(Bitmap.CompressFormat.Png, 0, stream);
byte[] bitmapData = stream.ToArray();
ViewModel.RecomendDishViewModel.ImageData = bitmapData;
if(_imageView == null)
{
return;
}
_imageView.RecycleBitmap ();
_imageView.SetImageBitmap(_bitmap);
}
_file.Dispose ();
_dir.Dispose ();
}
My problem is that the activity gets recreated and then launches the camera app again. I have tried many things (this is the clean version) but nothing worked perfectly...
Any ideas?
This might not be exactly the solution you were looking for, but you could try simply locking screen rotation during the activity.
You can set this in the Android Manifest like so:
<activity android:name="MyActivity"
android:screenOrientation="portrait">
...
</activity>
This will keep the activity from redrawing when the device is rotated. Obviously you can replace "portrait" with "landscape" if you feel that a landscape layout would be more appropriate for your activity.
I'd recommend saving the state of the Activity like a Question before that I gave advice on.
Sadly, I'd failed. : )
Save State
To add to Slack Shots answer your really need to understand what is going on within the activity life cycle you either need to lock the orientation of your app and prevent changes or the better way (save state) or handle the orientation changes directly.
http://developer.android.com/training/basics/activity-lifecycle/recreating.html

Categories

Resources