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
Related
My application is a portrait locked application. The application structure is based on one activity and multiple fragments. I am using support v4 fragments for this and fragments has nesting also. While I am trying to take pictures from the camera, for my profile update fragment. The camera app is open and I can capture and save the image. the image is getting in the onActivityResult() successfully.
But randomly the application orientation is getting distorted and its automatically change to landscape.Because of that, the current fragment state is missing. I locked the orientation from Manifest file as android: screenOrientation="portrait for my activity. This issue is mainly getting in Custom android phone (Samsung, HTC etc). I required a directional guideline, whether I need to create a custom camera or any alternate fix for this issue.
My Camera call method from my fragment is given below:
private void callCamera() {
try {
Intent cameraIntent = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
cameraIntent.putExtra(MediaStore.EXTRA_SCREEN_ORIENTATION, ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
startActivityForResult(cameraIntent, CAMERA_REQUEST);
}catch (Exception e){
e.printStackTrace();
}
}
And I am getting the result in onActivityResult() of my Fragment as like this
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if( requestCode == CAMERA_REQUEST && resultCode == mActivity.RESULT_OK){
/**
* For Default Camera callback
*
*/
Bitmap image = (Bitmap) data.getExtras().get("data");
if(image != null){
AppUtility.mCapturedImage = image;
mIvProfileImg.setImageBitmap(image);
}
}
}
Simple solution will be handle it in onSaveInstanceState method.you cannot control camera intent rotation.setting your rotation to portrait and take picture in landscape mode it rotates your activity forcefully it loses its state.
I'm creating a dynamic form activity and I get the instructions on how to make the forms from a db. The activity essentially just scrolls the list of questions and sees what type they are and adds them. So it's an activity that adds a view as a question. That all works fine by itself. I tried to keep the responses/questions inside the specific question classes which just subclass a base question.
The problem I'm having is when I try to add a camera "question" to prompt the user to take a picture, I can't get the result inside the view. I managed the launch the activity in the view, and it returns the result to the questionnaire activity. The activity doesn't know which question it's meant to add it to, since it's all done dynamically. So I tried passing through the questionId through as an extra in the camera intent and receive it in the questionnaire activity where it then scrolls through the questions it's added and if it's the same one, it adds the picture to the question it's associated to.
The way it adds questions is by having a viewgroup which just inserts at a part for each of them.
This the relevant part that launches the camera (I've tried using it without a bundle, too). This is inside a subclass of a BaseQuestion which is just a subclass of a linearlayout:
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
Bundle bundle = new Bundle();
bundle.putInt("questionId", getQuestionId());
intent.putExtras(bundle);
intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(new File(
Environment.getExternalStorageDirectory(), "image.tmp")));
((Activity)getContext()).startActivityForResult(intent, TAKE_PHOTO);
This is the relevant part that handles the result which is in an activity.
protected void onActivityResult(int requestCode, int resultCode,
Intent imageReturnedIntent) {
super.onActivityResult(requestCode, resultCode, imageReturnedIntent);
Bitmap image = null;
switch (requestCode)
{
case TAKE_PHOTO:
{
if (resultCode == RESULT_OK)
{
InputStream inputStream = null;
File file = new File(Environment.getExternalStorageDirectory(),
"image.tmp");
try {
inputStream = new FileInputStream(file);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
if (inputStream == null) {
Uri uri = imageReturnedIntent.getData();
try {
inputStream = getContentResolver().openInputStream(uri);
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
image = createScaledBitmapFromStream(inputStream, 200, 300);
Bundle bundle = imageReturnedIntent.getExtras();
if (bundle != null)
{
int questionId = bundle.getInt("questionId");
for (BaseQuestion questionView : questionViews)
{
if (questionId == questionView.getQuestionId())
{
questionView.setResponse(image);
}
}
}
}
else if (resultCode == Activity.RESULT_CANCELED)
{
Log.d("something", "something");
}
break;
}
}
}
EDIT: Solved, might not be clear from answer
I made questionIdForResult in my superclass which I set to -1. I then set it to the questionId when it runs startActivityForResult, and then in onActivityResult, I check each question if their questionIdForResult matches their questionId and if it does, use that one. I then set it back to -1 to make sure if you have two, it doesn't go to the other one.
Your approach will not work, as there is no requirement for any camera app to magically copy extras from the incoming Intent to the result Intent. In fact, I will be rather surprised if any camera activity does this.
You could store questionId() in a data member of the activity (e.g., questionIdForTheNextActivityResult), then use that value in onActivityResult(). Bear in mind, though, that taking a picture using a third-party app means that your process may be terminated before you get control again, so be sure to save that data member via onSaveInstanceState() and restore it via onRestoreInstanceState().
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" >
I am trying to use the built-in camera application to take a photo and view it through an ImageView.
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_photo);
addButtonListeners();
startCamera();
}
private void startCamera() {
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
startActivityForResult(intent, PHOTO_TAKEN);
}
protected void onActivityResult(int requestCode, int resultCode,
Intent intent) {
if (requestCode == PHOTO_TAKEN) {
Bundle extras = intent.getExtras();
photo = (Bitmap) extras.get("data");
if (photo != null) {
ImageView image = (ImageView) findViewById(R.id.image_background);
image.setImageBitmap(photo);
} else {
Toast.makeText(this, R.string.unable_to_read_photo, Toast.LENGTH_LONG)
.show();
}
}
}
When holding the phone in portrait position this code works just fine, however when I take the picture in landscape it breaks, any ideas why or how to solve this?
Question in not defined with enough details to answer it for sure, but my guess would be the same as Shani Goriwal .
It looks like problems with configuration changes event - which happens each time orientation is changed (from landscape to portrait).
Try to add to AndroidManifest of your app following lines:
android:configChanges="orientation|screenSize"
(more details: http://developer.android.com/guide/topics/resources/runtime-changes.html)
I found a tutorial that explains how to appropriately use the in built camera. Here is the link.
I am relativly new on android but from what I have read is the every time the display rotates android creates a new instance of some sort. So you have to save the instance of the rotation and this is done with the following code:
/**
* Here we store the file url as it will be null after returning from camera
* app
*/
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
// save file url in bundle as it will be null on scren orientation
// changes
outState.putParcelable("file_uri", fileUri);
}
/*
* Here we restore the fileUri again
*/
#Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
// get the file url
fileUri = savedInstanceState.getParcelable("file_uri");
}
If you click on the link you should go to bullet number 11. Avoiding NullPointerException after taking camera picture. The real hero here is Ravi Tamada who does an excellent tutorial on using the camera. I recommend reading the whole tutorial.
Again I am new at this so if there is any corrections on what I have wrote here please correct.
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.