Parse error when trying toprogrammatically install APK from android app - android

To be as succinct as possible:
-APK file is not corrupt.
-I can browse to the APK in the phone's file system and manually install it from there without issue.
-I am using the following code to kick off the install process. File location is confirmed correct:
public void installfromlocal()
{
String downloadfilelocation = getsharedresourcestring("updatepackagelocation");
Log.e("installing from",downloadfilelocation);
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(new File(downloadfilelocation)), "application/vnd.android.package-archive");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
So far what I could gather from a couple hours on the internet is that apparently I can't make my app install an APK programmatically from external storage. I can also apparently not copy the file to internal storage and install from there.
So what now? Additionally, I get no messages from Logcat. I only get a popup alerting me that there was an error parsing the apk.

I found a solution for me (not so clear why have this issue, but i solve it).
It seems to me that when downloading with DownloadManager you cant access to the downloaded file via URI, and you get access denied (and various file not found exception error) that's why PackageInstaller cannot read at all the manifest (and that's the parse error).
This is what i did, i hope that resolve you problem as well, i know it's not elegant to say the least.
Because of DownloadManager.COLUMN_LOCAL_FILENAME is deprecated i tried with COLUMN_LOCAL_URI to access the file and access its content (q is Cursor)
String strUri = q.getString(q.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI));
Uri apkUri = Uri.parse(strUri);
with this uri i can access and copy the file to a temp file in getExternalCacheDir()
ParcelFileDescriptor pfd = context.getContentResolver().openFileDescriptor(apkUri, "r");
InputStream inFile = new FileInputStream(pfd.getFileDescriptor());
OutputStream outFile = new FileOutputStream(tmpFile);
//copy
byte[] buffer = new byte[1024];
int length;
while ((length = inFile.read(buffer)) > 0) {
outFile.write(buffer, 0, length);
}
outFile.flush();
inFile.close();
outFile.close();
Grab the file created and get its uri (that is accessible) and start the activity with that uri.
I hope it helps

You should use canonical path of the file. From the docs-
A canonical pathname is both absolute and unique. The precise definition of canonical form is system-dependent. This method first converts this pathname to absolute form if necessary, as if by invoking the getAbsolutePath() method, and then maps it to its unique form in a system-dependent way. This typically involves removing redundant names such as "." and ".." from the pathname, resolving symbolic links (on UNIX platforms), and converting drive letters to a standard case (on Microsoft Windows platforms).

Related

I don't understand why in Android 11 use of `storage\emulated...\downloads` folder can read/write some files and not others?

Problem: I cannot seem to understand why, targeting API 30 (Android 11), my app can still upload and download from the storage\emulated...\download folder for a variety of files all day long, but it fails every time on *.db files... and I was able to on API 29 without issue.
Being a new app, and my first, I don't really want to depend on legacy workarounds. I would rather this work correctly from the start. This app needs to be capable of allowing the user to import and export file attachments to this app's database.
So, back to my original issue... I don't understand why using new FileInputStream(filename) has no issue with using the same path for one file and not another? This is the only shared folder the app and user needs, it caches everything else.
Works with this file and same path
Doesn't with this file and same path
UPDATE: Added code to clarify intentions to copy.
InputStream source = new FileInputStream(sourcePath);
OutputStream destination = new FileOutputStream(destinationPath);
DatabaseManager.copyDatabase(dbSource, destination);
I can update and replace using the getContentResolver but the same issue still persists -- working for one device and not the other.
InputStream source = getContentResolver().openInputStream(uri);
With help from Mark in the comments above this solved my problem. Since the .db extension is not recognizable, I had to use intent.setType("*/*") and validate the file and extension at the resultLauncher (i.e., ActivityResultLauncher).
https://developer.android.com/training/data-storage/shared/documents-files#open-file
private void setupImportDBButton(){
mImportDB.setOnClickListener(v -> {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("*/*");
resultLauncher.launch(intent);
});
}
Included within the ActivityResultLauncher: Basics of the functionality that worked to resolve the multi-device use forced by Android 11 (API 30) requirements. The key was the use of the getContentResolver().
Uri uri = result.getData().getData();
String destinationPath = this.getDatabasePath(strDBName).getPath();
InputStream source = this.getContentResolver().openInputStream(uri);
OutputStream destination = this.getContentResolver().openOutputStream(Uri.fromFile(new File(destinationPath)));
DatabaseManager.copyDatabase(source, destination);

File operations in android Q beta

1)Target set to Android Q with android.permission.WRITE_EXTERNAL_STORAGE
2) use getExternalStorageDirectoryor getExternalStoragePublicDirectoryand FileOutputStream(file)saving file throws
java.io.FileNotFoundException: /storage/emulated/0/myfolder/mytext.txt open failed: ENOENT (No such file or directory)
3) use getExternalFilesDirapi and saving is success but wont show up even after MediaScannerConnection.scanFile.
/storage/emulated/0/Android/data/my.com.ui/files/Download/myfolder/mytext.txt
What is best way to copy file from internal memory to SDCARD in android Q and refresh.
In Android API 29 and above you can use the below code to store files, images and videos to external storage.
//First, if your picking your file using "android.media.action.IMAGE_CAPTURE"
then store that file in applications private Path(getExternalFilesDir()) as below.
File destination = new File(context.getExternalFilesDir(Environment.DIRECTORY_PICTURES), fileName);
//Then use content provider to get access to Media-store as below.
ContentValues values = new ContentValues();
values.put(MediaStore.Images.Media.TITLE, fileName);
values.put(MediaStore.Images.Media.DISPLAY_NAME, fileName);
//if you want specific mime type, specify your mime type here. otherwise leave it blank, it will take default file mime type
values.put(MediaStore.Images.Media.MIME_TYPE, "MimeType");
values.put(MediaStore.Images.Media.DATE_ADDED, System.currentTimeMillis() / 1000);
values.put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis());
values.put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/" + "path"); // specify
storage path
// insert to media-store using content provider, it will return URI.
Uri uri = cxt.getContentResolver().insert(MediaStore.Files.getContentUri("external"), values);
//Use that URI to open the file.
ParcelFileDescriptor descriptor = context.getContentResolver().openFileDescriptor(uri,"w"); //"w" specify's write mode
FileDescriptor fileDescriptor = descriptor.getFileDescriptor();
// read file from private path.
InputStream dataInputStream = cxt.openFileInput(privatePath_file_path);
//write file into out file-stream.
OutputStream output = new FileOutputStream(fileDescriptor);
byte[] buf = new byte[1024];
int bytesRead;
while ((bytesRead = dataInputStream.read(buf)) > 0)
{
output.write(buf, 0, bytesRead);
}
datanputStream.close();
output.close();
}
In Android Q direct File access is disabled by default for the apps outside their private folders. Here few strategies you can use:
Use the manifest option requestLegacyStorage to have the old behavior but it won't work anymore with Android R, so it's really a short term solution;
Save your files using getExternalFilesDir() method. It's your private folder, other apps could access these files only if they have READ_EXTERNAL_STORAGE permission. In this case it would be good to use FileProvider to grant access to other apps to your files.
Use MediaStore API to insert your media directly. This approach is good for videos, music and photos.
Use the method getPrimaryStorageVolume().createOpenDocumentTreeIntent() of class StorageManager to ask for access to the extenal primary volume. In this case you need user consent and you won't be able to use File api directly anyway, but using DocumentFile class you have a very similar interface, so this is the solution closer to the old behavior. It works if you need to perform operations in foreground and background, i.e. without user interaction with the exception the first interaction to request the permission.
Use ACTION_CREATE_DOCUMENT to create a file using SAF. This option is valid if you do this operation in foreground because you will need the folder selection by the user.
I link Flipper library for point 4, it helps to manage files like in older android versions.
Temporary possibility: you can use android:requestLegacyExternalStorage="true" in <application> tag in Android Manifest.
Add android:requestLegacyExternalStorage="true" in <application> tag in Android Manifest, which will allow you to use All File APIs, for now.
1)Use Document-provider https://developer.android.com/guide/topics/providers/document-provider#create
2)User will be asked to save(total number of files) and get stored in documents folder.
https://gist.github.com/neonankiti/05922cf0a44108a2e2732671ed9ef386
If you use preserveLegacyExternalStorage, the legacy storage model remains in effect only until the user uninstalls your app. If the user installs or reinstalls your app on a device that runs Android 11, then your app cannot opt out the scoped storage model, regardless of the value of preserveLegacyExternalStorage.
Something to remember when using this flag. know more

Copying resource file to external storage on android not working

I'm working on a mediaplayer application and am trying to have some default songs get put on the phone in case the user doesn't have any on their phone (I'm pulling the list of actual songs via mediastore).
So I have the songs put in the res/raw folder and fun the following code to copy them. It seems to be copying ok (since astro file browser and other apps see them fine) but the mediastore still can't find them.
I think it's something with the permissions on the file but I'm not sure what. Anybody know why?
InputStream song = getResources().openRawResource(R.raw.aquarel);
File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC), "aquarel.mp3");
Log.e(TAG, "File1: " + file.getPath());
OutputStream copySong = new FileOutputStream(file);
byte[] buffer = new byte[1024];
int readvalue = 0;
readvalue = song.read(buffer);
while (readvalue > 0) {
copySong.write(buffer, 0, readvalue);
readvalue = song.read(buffer);
}
copySong.close();
"When you add files to Android’s filesystem these files are not picked
up by the MedaScanner automatically.
...
If you want your files to be added to the media library, you can do so
either by using the MediaStore content provider, or by using the
MediaScanner."
You can add them by sending a broadcast:
Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
intent.setData(Uri.fromFile(file));
sendBroadcast(intent);
(Source)

android: avoid file download with MODE_WORLD_READABLE

Bonjour
I would like to create a simple market applications for android but I face a annoying issue.
The install flow is as below:
1- Download of apk file in the application context :
InputStream input = new BufferedInputStream(url.openStream()) ;
OutputStream output = openFileOutput("xx.apk", Activity.MODE_WORLD_READABLE);
byte data[] = new byte[1024];
while ((count = input.read(data)) != -1 && statusDownload ) {
output.write(data, 0, count);
}
output.flush();
output.close();
input.close();
2- When download is finished:
promptInstall = new Intent(Intent.ACTION_VIEW);
File fileApk = new File (getFilesDir() + "/xx.apk");
promptInstall.setDataAndType(Uri.fromFile(fileApk) , Consts.APK_FILE_INSTALL_TYPE);
startActivityForResult(promptInstall,654);
3- After installing (or if cancelled), apk file is deleted:
File fileApk = new File (a.getFilesDir() + "/xx.apk" );
fileApk.delete();
In order to install an apk file, it must be "World Readable", it means that everyone can , after downloading the file (and before installing), get the apk file.
Does anyone know how to set good permissions for avoid external downloads ?
Thank you for reading !
Does anyone know how to set good permissions for avoid external downloads ?
You already have the "good permissions". The installer runs in a separate process from yours, and therefore it must be able to read the file. Since you have no way of granting permissions only to the installer for this, you have to grant read permissions to all possible processes.
For most things, we would use a ContentProvider to get around this limitation. Alas, at least as of a year ago, the installer did not support content:// Uri values.

Convert HttpResponse to an .apk file

The problem is this:
I make an internet connection to some url and receive an HttpResponse with an app_example.apk.
Then I want to create a file (an .apk)
in the sdcard with this data so that this downloaded application
can be installed later.
How can I convert the HttpResponse to an .apk file?
Let's clear some details:
I have to get this apk file through an internet connection to my server
I don't want to install this applications I receive on the sdcard
All of this has to be done in my code, I cannot use android market
I am currently writing to that file.
What I'm doing is converting the HttpResponse to a byte[ ],
then that byte[ ] is written to a file (an .apk) using an ObjectOutputStream.
Like this:
// byte[] appByteArray - already has the internet response converted in bytes
try {
file = new File(Environment.getExternalStorageDirectory()+"/"+appName+".apk");
file.createNewFile();
FileOutputStream stream = null;
stream = new FileOutputStream(file, false);
ObjectOutputStream objectOut =
new ObjectOutputStream(new BufferedOutputStream(stream));
objectOut.writeObject(appByteArray);
objectOut.close();
} catch (Exception e) {
e.printStackTrace();
}
In the end, the file is created
and has the received content.
When I try to install it,
through a VIEW intent (using the default installer)
I get a parse error saying that it could not find the AndroidManifest.xml.
I think that in some step along the way, the received data is being corrupted.
Do you have another method to solve this?
Many thanks
Don't use an ObjectOutputStream, byte array is serialized as Object, not written as raw data.
Are you sure that you have SD card write permission? android.permission.WRITE_EXTERNAL_STORAGE
Don't write into SD card root directory. Number of files in root dir can be limited. Instead create you app subdirectory on SD CARD.
This code works for me:
try {
String filePath = Environment.getExternalStorageDirectory()
+ "/myappdir/" + appName + ".apk";
File file = new File(filePath);
file.getParentFile().mkdirs();
file.createNewFile();
BufferedOutputStream objectOut = new BufferedOutputStream(
new FileOutputStream(file));
objectOut.write(appByteArray);
objectOut.close();
} catch (Exception e) {
e.printStackTrace();
}
This may not be the core problem, but I don't think you want to wrap stream in an ObjectOutputStream, since that is used for object serialization. It could be that it is adding extra data to the file so it can be deserialized with ObjectInputStream.
I would try pulling the apk off of the emulator (or device) and check it's MD5 versus the file on the server to make sure that the bits are being written out correctly.
Take a look at Pavel P's answer.
Also, I would note that your idea of installing the APK using the VIEW intent action does work, as I have tested this technique in the past.
However, unless the user has explicitly gone into Settings → Applications and selected "Allow non-Market applications", your installation will fail and the user will just see a screen telling them that for security reasons the installation has been blocked.
Basically you really need to rely on having fairly tech-savvy users who are willing to overlook a scary security warning and go and disable that setting.

Categories

Resources