My application allows users to select an image to upload. When users select an image from a picasa album my data intent comes back with dat=content://com.sec.android.gallery3d.provider/picasa/item/....
Apparently when selecting an image from a picasa folder, I must handle getting the image differently as noted in this answer.
But before I implement a fix, I want to be able to reproduce the crash so I can verify my fix actually works. So how can I get a Picasa folder on my new (marshmallow) Android test device since Picasa has been killed by Google?
The most guaranteed way of getting a file send inside an intent, is to open a stream to it and copy it over to a private folder on your app.
This way works for local file, content uri, picasa, all of it.
Something like that:
private File getSharedFile() {
Uri uri = intent.getExtras().getParcelable(Intent.EXTRA_STREAM);
// or using the new compat lib
Uri uri = ShareCompat.IntentReader(this).getStream();
InputStream is = null;
OutputStream os = null;
try {
File f = ... define here a temp file // maybe getCacheDir();
is = getContentResolver().openInputStream(uri);
os = new BufferedOutputStream(new FileOutputStream(f));
int read;
byte[] bytes = new byte[2048];
while ((read = is.read(bytes)) != -1) {
os.write(bytes, 0, read);
}
return f;
} catch (Exception e) {
... handle exceptions, buffer underflow, NPE, etc
} finally {
try { is.close(); } catch (Exception e) { /* u never know */ }
try {
os.flush();
os.close();
} catch (Exception e) { /* seriously can happen */ }
}
return null;
}
Related
I am developing an app where time-cost of an algorithm matters a lot.
In the algorithm, I need to get path string to a file in assets folder. And I got answer from this question.
The file is a configuration file, which is ~400 bytes in size. The library I used requires path, but not some Java string.
My code is like:
public static File getCacheFile(String path, Context context) throws IOException {
File cacheFile = new File(context.getCacheDir(), path);
try {
InputStream inputStream = context.getAssets().open(path);
try {
FileOutputStream outputStream = new FileOutputStream(cacheFile);
try {
byte[] buf = new byte[1024];
int len;
while ((len = inputStream.read(buf)) > 0) {
outputStream.write(buf, 0, len);
}
} finally {
outputStream.close();
}
} finally {
inputStream.close();
}
} catch (IOException e) {
throw new IOException("Could not open file", e);
}
return cacheFile;
}
If I run the algorithm for the first time since I start my app, it will cost ~900ms.
If I run the algorithm again without restarting the app, it will cost ~400ms.
So I guess the time difference is that this function attempts to load the file into cache and read path from the cache? Maybe the file is already in the cache and that is why it is faster.
Is there any way to make it faster? E.g. preload this file to cache in onCreate(), maybe?
Edit: I tried to preload this file in onCreate() and it does not work.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
try {
getCacheFile("a.properties", getApplicationContext());
} catch (IOException e) {
e.printStackTrace();
}
}
Edit2: Not sure whether it matters, but my algorithm is posted here.
My Android app (Java 8, min SDK 24, target SDK 27) downloads images from an FTP server, using Apache's FTPClient (version 3.6).
There are a couple of smaller .jpg images (around 12kb each) that always download completely but all of the bigger .png images (8+ mb each) show up black in my app. The files on the server look fine, so I downloaded the images from the emulator:
Windows' "Photo" app displays a black, horizontal bar at the bottom of all the affected images and they are all missing a couple of bytes (according to Windows' file explorer).
Reading the downloaded image with BitmapFactory.decodeFile(....) on a real device returns a null Bitmap, even though the file exists.
My code (runs on a background thread):
public void downloadFiles(String remoteFolder, String localFolder, ArrayList<String> filenames) {
//login here
ftpClient.setConnectTimeout(10000);
ftpClient.setDefaultTimeout(10000);
OutputStream out = null;
try {
ftpClient.enterLocalPassiveMode();
ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
if(ftpClient.changeWorkingDirectory(remoteFolder)) {
for(String filename : filenames) {
FTPFile[] singleFile = ftpClient.listFiles(filename);
if(singleFile != null && singleFile.length > 0) { //check if file exists
String localPath = localFolder + File.separator + filename;
out = new FileOutputStream(localPath);
if(!ftpClient.retrieveFile(filename, out)) {
//Set error message here
out.close();
break;
}
out.close();
} else {
//Another error message here
break;
}
}
}
} catch (IOException e) {
//And another error message here
} finally {
try {
if(out!=null) { out.close(); }
} catch(IOException e) {
//Doesn't matter
}
}
//logout here
}
I tried out.flush() before closing the stream, a longer timeout (ftpClient.setDefaultTimeout(30000)) and even retrieveFileStream:
InputStream is = ftpClient.retrieveFileStream(filename);
BufferedInputStream bis = new BufferedInputStream(is);
byte[] buf = new byte[10000000];
int len;
while ((len = bis.read(buf)) > 0) {
out.write(buf, 0, len);
}
if(ftpClient.completePendingCommand()) {
Log.d(TAG,"Done"); //This is always printed, even though the files aren't complete
} else {
Log.d(TAG,"Not done");
}
out.close();
bis.close();
The problem persists. I added up len and and even ran bis.read a couple more times (if(fullLength<singleFile[0].getSize())) but no matter what, most of the time less than 10 bytes are missing in the downloaded file. Only rarely one of the images is actually downloaded completely but it seems to be pretty random.
How do I fix this, so it downloads all of the images completely?
I'm having issues using the Cards from the recently released GDK. Basically, Card.addImage() can only take two arguments, a resource id or a URI.
For my use case, I need to open an image that exists as a file not directly as a resource. So for testing purposes I'm including the images in the assets folder. Trying to access them directly from the asset folder fails, so I'm copying them from there to internal storage. Once they're copied, I generate a URI from the file and assign it to the card. The resulting card shows a grey block where the image should be.
String fileName = step.attachment; //of the form, "folder1/images/image1.jpg"
File outFile = new File(getFilesDir()+File.separator+fileName);
FileChannel inputChannel = null;
FileChannel outputChannel = null;
try {
//check to see if the file has already been cached in internal storage before copying
if(!outFile.exists()) {
FileInputStream inputStream = new FileInputStream(getAssets().openFd(fileName).getFileDescriptor());
FileOutputStream outputStream = openFileOutput(fileName, Context.MODE_PRIVATE);
inputChannel = inputStream.getChannel();
outputChannel = outputStream.getChannel();
outputChannel.transferFrom(inputChannel, 0, inputChannel.size());
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
finally{
try {
if(inputChannel!=null)
inputChannel.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
if(outputChannel!=null)
outputChannel.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
card.addImage(Uri.fromFile(outFile));
It's hard to diagnose because I have no clue what the Card is doing internally.
Instead of writing
new FileInputStream(getAssets().openFd(fileName).getFileDescriptor());
can you try
getAssets().openFd(fileName).createInputStream();
and see if it works?
To answer your original question, the addImage method supports resource: and file: URIs.
This is very strange, but I managed to solve my problem. I replaced the file copy code with the following and it appears to have solved my issues
InputStream in = null;
OutputStream out = null;
try {
in = getAssets().open(step.attachment);
out = new FileOutputStream(outFile);
byte[] buffer = new byte[1024];
int read;
while((read = in.read(buffer)) != -1){
out.write(buffer, 0, read);
}
in.close();
in = null;
out.flush();
out.close();
out = null;
} catch(IOException e) {
Log.e("tag", "Failed to copy asset file: " + step.attachment, e);
}
It's not clear to me why/how I was copying my entire apk, but I'm guessing it's the call to
getAssets().openFd(fileName).getFileDescriptor()
Perhaps it was returning the file descriptor of the apk. It's odd because I've seen some claim that the previous method works.
I'm trying to figure how to store my application's data for the long term. Basically I get a list of data from from a web service, and I don't want to go back to the web service the next time the app runs. I'd prefer to just store it locally. How do I do this?
I don't mind serialising the data to any particular format. I don't see this on the Xamarin site for Android. There's a tutorial for iOS, but I'm not interested in that.
I personally copy the data from webservice in raw format in a text file on the memory.
So, I have just to open the inputStream from the file the same I did from the webservice and my code remains clean.
But I guess there are indeed thousands of ways to copy this data.
I just wanted to share the one I found more convenient.
The code just for information:
InputStream source = getStreamFromWebservice();// <= YOUR CODE HERE
File dir = context.getDir("CACHE", Context.MODE_PRIVATE);
dir.mkdirs();
File file = new File(dir, fileName);
// Write to Memory
try {
FileOutputStream f = new FileOutputStream(file);
byte[] buffer = new byte[32768];
int read;
try {
while ((read = source.read(buffer, 0, buffer.length)) > 0) {
f.write(buffer, 0, read);
}
} catch (IOException e) {
e.printStackTrace();
}
f.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
}
Greetings and a happy new year to all my fellow programmers.
My code downloads an apk file from a remote server. I need to initiate the installation procedure through code, without user having to explicitly install it. The catch is that i cannot use an SD card download the apk file.
I can navigate to the data/data/files folder and can see my file downloaded. The only problem is that i cannot get it installed. This is what i get
'/data/data/org.obs.testinstall.main/files/app.apk': Permission denied
I understand that Android does not give permission to access the data directory.
My question is how can i download and install an application(apk) without using a SD card. This application is not intended to be published in the market. I have tried using both the Internal Storage using
FileOutputStream fos = openFileOutput("app.apk", Context.MODE_PRIVATE);
and the cache directory
File file = getCacheDir();
File outputFile = new File(file, "app.apk");
Both give the same result .. "Permission denied"
When i change the code to incorporate an SD card the application works perfectly, but using an SD card is not an option.
Surely there must be a way to do this. It is hard to believe that such a handicap exist in the Android O/S.
Has anybody done this? Any workarounds? Any pointers would be helpful.
It it caused by android application can not read from
another application file if it is written using PRIVATE mode.
You can do this:
String fileName = "tmp.apk";
FileOutputStream fos = openFileOutput(fileName,
MODE_WORLD_READABLE | MODE_WORLD_WRITEABLE);
// write the .apk content here ... flush() and close()
// Now start the standard instalation window
File fileLocation = new File(context.getFilesDir(), fileName);
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(fileLocation),
"application/vnd.android.package-archive");
context.startActivity(intent);
Be careful though, because that file is now world-visible,
and can be seen by any application in the same device,
if they know the file location.
No need to root.
You can just use linux command chmod to do it.
public static String exec(String[] args) {
String result = "";
ProcessBuilder processBuilder = new ProcessBuilder(args);
Process process = null;
InputStream errIs = null;
InputStream inIs = null;
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int read = -1;
process = processBuilder.start();
errIs = process.getErrorStream();
while ((read = errIs.read()) != -1) {
baos.write(read);
}
baos.write('\n');
inIs = process.getInputStream();
while ((read = inIs.read()) != -1) {
baos.write(read);
}
byte[] data = baos.toByteArray();
result = new String(data);
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (errIs != null) {
errIs.close();
}
if (inIs != null) {
inIs.close();
}
} catch (IOException e) {
e.printStackTrace();
}
if (process != null) {
process.destroy();
}
}
return result;
}
in your program,it can be invoked like this:
String[] args1 = { "chmod", "705", "/data/data/org.obs.testinstall.main/files/" };
exec(args1);
String[] args2 = { "chmod", "604", "/data/data/org.obs.testinstall.main/files/app.apk" };
exec(args2);
Then you can install the app.apk as wished.
Also you can use
downloadedFile.setReadable(true, false);
with
fileOutputStream = openFileOutput(fileName, Context.MODE_PRIVATE);
There are two setReadable method. The first has one parameter and the second one has two parameters.
setReadable(boolean readable)
setReadable(boolean readable, boolean ownerOnly)
Try rooting your device and then running the program from the device, instead of using an emulator.
For me I deleted the apk file right after the startActivity, which is asynchronous.
Too bad there is no better description of the parsing error (file not found, access denied, corrupted file in package,...)
when you send intent to install apk, you can use this function to change mode for apk directory.
private static boolean changeMode(String filePath, String prefixPath) {
if (TextUtils.isEmpty(prefixPath) || !filePath.startsWith(prefixPath)) {
return true;
}
try {
String[] args1 = { "chmod", "705", prefixPath};
Runtime.getRuntime().exec(args1);
} catch (IOException e) {
e.printStackTrace();
return false;
}
String subPath = filePath.split(prefixPath)[1];
String[] subArr = subPath.split(File.separator);
for (String path : subArr) {
if (!TextUtils.isEmpty(path)) {
prefixPath = prefixPath + File.separator + path;
try {
if (!prefixPath.endsWith(".apk")) {
String[] progArray1 = {"chmod", "705", prefixPath};
Runtime.getRuntime().exec(progArray1);
} else {
String[] progArray2 = {"chmod", "604", prefixPath};
Runtime.getRuntime().exec(progArray2);
}
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
}
return true;
}
And before you send intent, check chmod is it alreay success.
boolean chmodRes = changeMode(filePath, context.getCacheDir().getAbsolutePath())
&& changeMode(filePath, context.getFilesDir().getAbsolutePath());
if (!chmodRes) {
return false;
}