During developing application in Xamarin Android we encountered strange error. The pick image/video (whether its from camera or documents UI) application sometimes crashes with no error. Scenario is that we open application, make steps to the activity, where the images are chosen, and then open the camera or document UI by standard way:
public void ChooseMediaAfterTypeChose (bool photo, bool camera) {
try {
string title = "";
int id;
Intent iIntent;
if (camera) {
if (photo) {
iIntent = new Intent("android.media.action.IMAGE_CAPTURE");
id = Const.AND_pickImageID_camera;
App._file = new File (App._dir, "myPhoto.jpg");
iIntent.PutExtra (Android.Provider.MediaStore.ExtraOutput, Uri.FromFile (App._file));
} else {
iIntent = new Intent("android.media.action.VIDEO_CAPTURE");
id = Const.AND_pickVideoID_camera;
App._file = new File (App._dir, "myVideo.mp4");
iIntent.PutExtra (Android.Provider.MediaStore.ExtraOutput, Uri.FromFile (App._file));
}
} else {
iIntent = new Intent ();
iIntent.SetAction (Intent.ActionGetContent);
if (photo) {
iIntent.SetType ("image/jpg");
title = Static.mainData.currentTexts.ChoosePhoto;
id = Const.AND_pickImageID_galery;
} else {
iIntent.SetType ("video/mp4");
title = Static.mainData.currentTexts.ChooseVideo;
id = Const.AND_pickVideoID_galery;
}
}
if (camera) {
StartActivityForResult (iIntent, id);
} else {
StartActivityForResult (Intent.CreateChooser (iIntent, title), id);
}
} catch (Exception ex) {
ShowError (Static.mainData.currentTexts.MediaError);
W.L(ex.Message);
}
}
The chosen application crashes during image/video picking (E.g. during browsing images in gallery wthout picking any). When that happens we never get to any method in our code.
What we have:
When user don't pick photos, the application runs without any memory crash
Sometimes the picking proceedes without error.
We think that the issue is mainly on Android 4.4.2 (It happend to us only on devices with this android - on other android version devices we didn't encounter it)
What we tried:
We put all permissions to manifest (read/write external storage, hardware.camera etc.)
We delete almost all memory stored content to save up memory
All Activities are unload from memory when they leave the screen
Logcat says that every process connected (Application and the active application) just died with no trace to any error
Our question is if anyone stumbled to similar issue and how to handle it. The second question is if there is any way to find out whats happening.
EDIT:
App._dir code added.
App._dir = new Java.IO.File (
Environment.GetExternalStoragePublicDirectory (
Environment.DirectoryPictures), "Camera");
if (!App._dir.Exists ())
{
App._dir.Mkdirs( );
}
Related
I'm just trying to resize an image after the user launches the Image Picker from my app and chooses an image file on the local device (handling a remote image from Dropbox or something will be another battle) and while this has worked for me previously, now I'm getting this exception:
java.lang.RuntimeException: Failure delivering result ResultInfo{who=null, request=1105296364, result=-1, data=Intent { dat=content://com.android.externalstorage.documents/document/primary:Download/20170307_223207_cropped.jpg flg=0x1 }} to activity {my.app/MainActivity}: java.io.FileNotFoundException: No content provider: /document/primary:Download/20170307_223207_cropped.jpg
This occurs after the image is chosen in the Picker, because I'm running my "processing" code to locate the image, resize it, and copy it to a subfolder in the app's folder.
Like I said, this worked, but I'm not sure what's wrong now. I've tried this on the emulator as well as on my Galaxy S10 via USB debugging and it's the same result. The image is in the local storage "Download" folder on the emulator as well as my own device.
The URI looks weird (I mean the picture is just in the local storage "Download" folder) but I'm no URI expert so I assume it's fine, because that's what the Image Picker returns.
Here's the immediate code that's throwing the exception (specifically, the ImageDecoder.decodeBitmap call):
private fun copyFileToAppDataFolder(
context: Context,
imageTempPath: String
): String {
// ensure we are sent at least a non-empty path
if (imageTempPath.isEmpty()) {
return ""
}
val appDataFolder = "${context.dataDir.absolutePath}/images/firearms"
var filename = imageTempPath.substringAfterLast("/", "")
if (filename.isNullOrBlank()) {
filename = imageTempPath.substringAfterLast("%2F", "")
}
// couldn't parse filename from Uri; exit
if (filename.isNullOrBlank()) {
return ""
}
// get a bitmap of the selected image so it can be saved in an outputstream
var selectedImage: Bitmap? = null
selectedImage = if (Build.VERSION.SDK_INT <= 28) {
MediaStore.Images.Media.getBitmap(context.contentResolver, Uri.parse(imageTempPath))
} else {
ImageDecoder.decodeBitmap(ImageDecoder.createSource(context.contentResolver, Uri.parse(imageTempPath)))
}
if (selectedImage == null) {
return ""
}
val destinationImagePath: String = "$appDataFolder/$filename"
val destinationStream = FileOutputStream(destinationImagePath)
selectedImage.compress(Bitmap.CompressFormat.JPEG, 100, destinationStream)
destinationStream.close()
return destinationImagePath
}
That above function is called from my ViewModel (that processFirearmImage function is just calling the one above), where I send the result URI from the image Picker as well as the Application Context:
// this event is fired when the Image Picker returns
is AddEditFirearmEvent.AssignedPicture -> {
val resizedImagePath = ShotTrackerUtility.processFirearmImage(
event.applicationContext, // this is from LocalContext.current in Composable
event.value // result uri from image picker
)
_firearmImageUrl.value = resizedImagePath
}
I don't know, lol. I can't believe this is such a difficult thing but information for this sure seems sparse (for Compose especially, but even so) but I don't really consider launching an Image Picker and resizing the resulting image to be that weird. Any help would be great from you smart people.
Taking a step away from programming problems and coming back seems about the best bet sometimes, lol.
I came back tonight and within a couple minutes noticed that I was sending an improper Uri to the ImageDecoder.createSource method that was causing the exception. Basically this was happening:
val imageTempPath = theUriReturnedFromImagePicker.path ?: ""
ImageDecoder.decodeBitmap(ImageDecoder.createSource(context.contentResolver, Uri.parse(imageTempPath)))
And it should've been:
val imageUrl = theUriReturnedFromImagePicker
ImageDecoder.decodeBitmap(ImageDecoder.createSource(context.contentResolver, imageUri))
As I mentioned in the OP, this originally worked but I must've changed code around a bit (arguments I'm sending to various methods/classes, mostly). I'm also using that Uri.path part to get the filename of the image chosen so I overlooked and/or got confused to what I was sending to ImageDecoder.createSource.
Doh. Maybe someone else will do something dumb like me and this can help.
I was looking for a way to display a phone's gallery in a GridView. Came across the local_image_provider library. It does it's job pretty well. Not a lot of problems except the fact that using the "findLatest" method on the LocalImageProvider does not return the newest images from the gallery (it is the only way to show take images from the gallery and put them into a list as far as I know). Instead, the list I create from using this method on for example 25 newest images from the gallery skips quite a lot of images. It shows returns mostly screenshots and some downloaded pictures, along with some images taken with my camera. (I am testing this app on my phone). I simply can not find any info on this library so I have decided to ask myself. Here is some relevant code:
import 'package:local_image_provider/local_image_provider.dart' as lip;
Future<List<ImageProvider>> getLocalImage() async {
lip.LocalImageProvider imageProvider = lip.LocalImageProvider();
bool hasPermission = await imageProvider.initialize();
if ( hasPermission) {
List<lip.LocalImage> images = await imageProvider.findLatest(20);
if ( !images.isEmpty ) {
lip.LocalImage image = images.first;
lip.DeviceImage deviceImg = lip.DeviceImage( image );
List<ImageProvider> list = [];
images.forEach((element) {
list.add(lip.DeviceImage(element));
});
return list;
}
else {
print("No images found on the device.");
throw Exception();
}}
else {
print("The user has denied access to images on their device.");
throw Exception();
}
}
I then use this function as a future for a future builder which builds a gridview.
I'm trying to tell the google cloud printing app to print a document, be it a .png file or an pdf. After checking if the app is installed via
public static boolean isCloudPrintInstalled(Context ctx){
String packageName = "com.google.android.apps.cloudprint";
android.content.pm.PackageManager mPm = ctx.getPackageManager();
try{
PackageInfo info = mPm.getPackageInfo(packageName, 0);
boolean installed = (info != null);
return installed;
}catch(NameNotFoundException e){
return false;
}
}
i send the user to the PrintingDialogActivity if it is missing, as described here: https://developers.google.com/cloud-print/docs/android
But i would like to use the app, if it is installed. If i send the following intent:
File theFile = new File(filePath); //file is NOT missing
Intent printIntent = new Intent(Intent.ACTION_SEND);
printIntent.setType(Helper.getMimeType(filePath));
printIntent.putExtra(Intent.EXTRA_TITLE, titleOfFile);
printIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(theFile));
ctx.startActivity(printIntent);
The getMimeType method is this:
public static String getMimeType(String url) {
String method = "Helper.getMimeType";
String type = null;
String extension = MimeTypeMap.getFileExtensionFromUrl(url);
//Fix for Bug: https://code.google.com/p/android/issues/detail?id=5510
if(extension == null || extension.equals("")){
extension = url.substring(url.lastIndexOf('.'));
}
type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
//should i consider just not using the MimeTypeMap? -.-
if(type == null || type.equals("")){
if(extension.toLowerCase(Locale.getDefault()).equals(".txt") == true){
type = "text/plain";
}else{
Log.e(method, "Unknown extension");
}
}
return type;
}
it returns "application/pdf" for pdf files.
On my android 4.0.3 device i get the action chooser and can choose the google cloud print app, which then allows to do stuff like saving the file to google drive. It works.
But if i start this intent on my Android 5.1.1 device (Nexus 5), the action chooser also opens, but it doesn't have the google cloud printing app or anything else printing related in it. Cloud print is preinstalled and currently on version 1.17b. I didn't kill it's process with some form of energy saving app. The device is not routed. What am i missing?
I also tried entering "text/html" by hand as the mime type, because that was the solution to another stackoverflow thread - but it doesn't solve mine.
Setting the mimetype to */* also doesn't make the actionchooser offer me the printer
After a lot of googling and testing it is rather unclear to me, if printing via intent is still possible on android 5 with google cloud print. BUT what does work is to create a custom PrintDocumentAdapter, as described in this post: https://stackoverflow.com/a/20719729/1171328
Making an AS3 app for android that uses camera roll to load, select and then use an image.
The cameraroll browser works fine, but when the image is selected, the app crashes - almost every time - as it has worked on a handful of occasions (!?!) We assumed a memory issue and attempted to close all other windows / use smaller photos in the camera roll etc. and try different devices - but cannot recreate success consistently. Cannot find another ANE that works either
It fails at the point where the photo has been selected, and RESTARTS the app...
here's the relevant code, any help appreciated.
public function openGallery():void {
var cameraRoll:CameraRoll = new CameraRoll();
if(CameraRoll.supportsBrowseForImage) {
cameraRoll.addEventListener(MediaEvent.SELECT, imageSelected);
cameraRoll.addEventListener(flash.events.Event.CANCEL, browseCanceled);
cameraRoll.addEventListener(flash.events.ErrorEvent.ERROR, mediaError);
cameraRoll.browseForImage();
}
else { trace( "Image browsing is not supported on this device."); }
}
private function imageSelected(event:MediaEvent):void {
trace("Media selected...");
var imagePromise:MediaPromise = event.data as MediaPromise;
_dataSource = imagePromise.open();
if(imagePromise.isAsync) {
trace("Asynchronous media promise.");
var eventSource:IEventDispatcher = _dataSource as IEventDispatcher;
eventSource.addEventListener(flash.events.Event.COMPLETE, onMediaLoaded);
} else {
trace("Synchronous media promise.");
readMediaData();
}
}
private function onMediaLoaded(event:flash.events.Event):void{
trace("Media load complete");
_mediaBytes = new ByteArray();
_dataSource.readBytes(_mediaBytes);
_tempDir = File.createTempDirectory();
var now:Date = new Date();
var filename:String;
filename = now.fullYear + now.month + now.day+now.hours + now.minutes + now.seconds + ".JPG";
_file = _tempDir.resolvePath(filename);
//writing temporal file to display image
_stream = new FileStream();
_stream.open(_file,FileMode.WRITE);
_stream.writeBytes(_mediaBytes);
_stream.close();
if(_file.exists){
_imageLoader = new Loader();
_imageLoader.contentLoaderInfo.addEventListener(Event.COMPLETE,onMediaLoadedBitmapData);
_imageLoader.loadBytes(_mediaBytes);
}
}
private function onMediaLoadedBitmapData(event:Event):void{
trace("onMediaLoadedBitmapData");
var loaderInfo:LoaderInfo = LoaderInfo(event.target);
_bitmapData = new BitmapData(loaderInfo.width,loaderInfo.height,false,0xFFFFFF);
_bitmapData.draw(loaderInfo.loader);
addPictureToScreen();
}
I had a similar thing happen in Air for iOS but it was related to picking large images. I just checked the dimensions of the chosen image and then used a scalar matrix if they were past a certain point. Sounds like you've thought about this but maybe look further into that?
There's an exporting feature in my application. It's just a copy operation since all my settings are store in shared preference.
I just copy the xml file from /data/data/package.name/shared_prefs/settings.xml to SD card. It works fine on my HTC desire. However, it might not work on Samsung devices, and i got the following error while I try to copy the file.
I/System.out( 3166): /data/data/package.name/shared_prefs/settings.xml (No such file or directory)
in the directory.
Anyone know how to fix it, or is there another simple way to store the shared preference ?
Thanks.
Never never never never never never never never never hardwire paths.
Unfortunately, there's no getSharedPreferenceDir() anywhere that I can think of. The best solution I can think of will be:
new File(getFilesDir(), "../shared_prefs")
This way if a device manufacturer elects to change partition names, you are covered.
Try this and see if it helps.
CommonsWare's suggestion would a be clever hack, but unfortunately it won't work.
Samsung does not always put the shared_prefs directory in the same parent directory as the getFilesDir().
I'd recommend testing for the existence of (hardcode it, except for package name):
/dbdata/databases/<package_name>/shared_prefs/package.name_preferences.xml and if it exists use it, otherwise fall back to either CommonsWare's suggestion of new File(getFilesDir(), "../shared_prefs") or just /data/data/<package_name>/shared_prefs/package.name_preferences.xml.
A warning though that this method could potentially have problems if a user switched from a Samsung rom to a custom rom without wiping, as the /dbdata/databases file might be unused but still exist.
More details
On some Samsung devices, such as the Galaxy S series running froyo, the setup is this:
/data/data/<package_name>/(lib|files|databases)
Sometimes there's a shared_prefs there too, but it's just Samsung's attempt to confuse you! Don't trust it! (I think it can happen as a left over from a 2.1 upgrade to 2.2, but it might be a left over from users switching roms. I don't really know, I just have both included in my app's bug report interface and sometimes see both files).
And:
/dbdata/databases/<package_name>/shared_prefs
That's the real shared_prefs directory.
However on the Galaxy Tab on Froyo, it's weird. Generally you have: /data/data/<package_name>/(lib|shared_prefs|files|databases)
With no /dbdata/databases/<package_name> directory, but it seems the system apps do have:
/dbdata/databases/<package_name>/yourdatabase.db
And added bonus is that /dbdata/databases/<package_name> is not removed when your app is uninstalled. Good luck using SharedPreferences if the user ever reinstalls your app!
Try using
context.getFilesDir().getParentFile().getAbsolutePath()
Best way to get valid path on all devices - run method Context.getSharedPrefsFile defined as:
/**
* {#hide}
* Return the full path to the shared prefs file for the given prefs group name.
*
* <p>Note: this is not generally useful for applications, since they should
* not be directly accessing the file system.
*/
public abstract File getSharedPrefsFile(String name);
Because of it hidden need use reflection and use fallback on fail:
private File getSharedPrefsFile(String name) {
Context context = ...;
File file = null;
try {
if (Build.VERSION.SDK_INT >= 24) {
try {
Method m = context.getClass().getMethod("getSharedPreferencesPath", new Class[] {String.class});
file = (File)m.invoke(context, new Object[]{name});
} catch (Throwable e) {
Log.w("App TAG", "Failed call getSharedPreferencesPath", e);
}
}
if (file == null) {
Method m = context.getClass().getMethod("getSharedPrefsFile", new Class[] {String.class});
file = (File)m.invoke(context, new Object[]{name});
}
} catch (Throwable e) {
Log.w("App TAG", "Failed call getSharedPrefsFile", e);
file = new File(context.getFilesDir(), "../shared_prefs/" + name + ".xml");
}
return file;
}
On some Samsungs implements like this:
public File getSharedPrefsFile(String paramString) {
return makeFilename(getPreferencesDir(), paramString + ".xml");
}
private File getPreferencesDir() {
synchronized (this.mSync) {
if (this.mPreferencesDir == null) {
this.mPreferencesDir = new File("/dbdata/databases/" + getPackageName() + "/", "shared_prefs");
}
File localFile = this.mPreferencesDir;
return localFile;
}
}
On other Android like this:
public File getSharedPrefsFile(String name) {
return makeFilename(getPreferencesDir(), name + ".xml");
}
private File getPreferencesDir() {
synchronized (mSync) {
if (mPreferencesDir == null) {
mPreferencesDir = new File(getDataDirFile(), "shared_prefs");
}
return mPreferencesDir;
}
}
private File getDataDirFile() {
if (mPackageInfo != null) {
return mPackageInfo.getDataDirFile();
}
throw new RuntimeException("Not supported in system context");
}
After while Google change API for level 24 and later:
https://android.googlesource.com/platform/frameworks/base/+/6a6cdafaec56fcd793214678c7fcc52f0b860cfc%5E%21/core/java/android/app/ContextImpl.java
I've tested in Samsung P1010 with:
//I'm in a IntentService class
File file = this.getDir("shared_prefs", MODE_PRIVATE);
I got:
"/data/data/package.name/app_shared_prefs"
It works fine to me. I can run ffmpeg in this folder.
Look:
Context.getDir
You have to create the shared_prefs directory:
try{
String dir="/data/data/package.name/shared_prefs";
// Create one directory
boolean success = (new File(dir)).mkdirs();
if (success) {
// now copy the file
}
}catch (Exception e){//Catch exception if any
System.err.println("Error: " + e.getMessage());
}
Also... the package of your app is package.name? Make sure you are referring to the right package.