Serving a html file with FileProvider or own ContentProvider - android

Using ACTION_VIEW with a FileProvider uri for a html file lets browsers like Chrome and Edge display the html page.
Also HTML-viewer app displays. And editor apps can handle the file.
If using a ContentProvider extended uri the browsers only offer to download the file.
HTML-viewer shows an indefinite progressbar.
My apps and external editor apps will still read and edit the file. So providing works.
I see that Chrome, Edge and HTML-viewer only call openFile() and getType().
content://com.jmg.expas.fileprovider/external_files/Documents/index.html
content://com.jmg.expas.simpleprovider/storage/emulated/0/Documents/index.html
I have no idea why both content scheme uries are treated differently.
This is the simple content provider code:
public class SimpleContentProvider extends ContentProvider
{
static String TAG = "simplecontentprovider";
public static Uri getUriForFile ( Context context, File file)
{
return Uri.parse("content://" + context.getPackageName() + ".simpleprovider" + file.getAbsolutePath());
}
#Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException
{
Log.d(TAG, "open-File() mode: " + mode ); // "w"
Log.d(TAG, "uri.getEncodedPath(): " + uri.getEncodedPath() );
String path = uri.getEncodedPath();
File f = new File(path);
if ( ! f.exists() )
{
Log.d(TAG, "path does not exist" );
throw new FileNotFoundException(
"in SimpleContentProvider\n"
+
uri.getPath() );
}
if ( mode.equals("r") )
return (ParcelFileDescriptor.open(f,ParcelFileDescriptor.MODE_READ_ONLY));
return (ParcelFileDescriptor.open(f,ParcelFileDescriptor.MODE_READ_WRITE));
}
private String getExtension ( String fileName )
{
int index = fileName.lastIndexOf(".");
if ( index < 0 )
return "";
return fileName.substring(index+1);
}
#Override
public String getType(Uri uri)
{
Log.d(TAG, "get-Type() getPath(): " + uri.getPath() );
String path = uri.getPath();
String extension = getExtension(path);
MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton();
String mimeType = mimeTypeMap.getMimeTypeFromExtension(extension);
Log.d(TAG, "mimetype: " + mimeType + " for extension " + extension );
// O Oh Oh ..
//return extension;
return mimeType; // and problem solved ;-)
}
// Other member functions not used. They will be added by Android Studio
}
In manifest:
<provider
android:name=".SimpleContentProvider"
android:authorities="${applicationId}.simpleprovider"
android:enabled="true"
android:exported="true" />
Use:
String mimetype = "text/html";
String filePath = .... any filepath to a html file the app can read itself...
Uri uri = SimpleContentProvider.getUriForFile(context, new File(filePath));
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(uri, mimetype);
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
| Intent.FLAG_GRANT_WRITE_URI_PERMISSION
);
context.startActivity(intent);
The question is: What do i have to do to let browsers display the source like when FileProvider used? Or what makes them to offer a download instead?

Related

Android 10 write file to public DCIM directory, NON DEPRECATED method

We have an Android app where field workers take photographs which are stored on their phone and also uploaded via a web api.
If the uploads fail they do have retry mechanisms but sometimes they need to resort to pulling the images off their phone.
In order to bring the app up to Android 10 version without deprecation I was forced to write the images to an app internal directory.
The problem is that when they upgrade their app they lose their photos from the app.
(I do also copy the images to a backup directory but this is all looking a bit klutzy)
I would like to write the images to :
/storage/emulated/0/DCIM/GoTrialImages
Instead they are going to :
/storage/emulated/0/Android/data/au.come.aceware.ktr.ktr/files/DCIM/GoTrialImages/photoIntent
(where photoIntent is the activity that this is occurring in)
Here is the code I have copied and tweaked from an online article:
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
String fileName = "JPEG_" + timeStamp + ".jpg";
File mediaStorageDir = new File(getExternalFilesDir(Environment.DIRECTORY_DCIM + File.separator +"GoTrialPhotos"), TAG);
// Create the storage directory if it does not exist
if (!mediaStorageDir.exists() && !mediaStorageDir.mkdirs()){
Log.d(TAG, "failed to create directory");
}
// Return the file target for the photo based on filename
File file = new File(mediaStorageDir.getPath() + File.separator + fileName);
Uri bmpUri = FileProvider.getUriForFile(this, BuildConfig.APPLICATION_ID + ".provider", file);
Here is my file provider entry in the manifest:
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/provider_paths" />
</provider>```
and here is #xml/provider_paths:
```<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="external_files" path="."/>
</paths>```
1) Is it possible to do what I am seeking to do ?
2) How do I do it without using deprecated code
Many thanks in advance
Tony
Following the suggestion to use media store I kept most of the code for creating the app internal file name
(mainly because I wanted the randomised display name):
private File createImageFileV2() throws IOException
{
// Create an image file name
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
String imageFileName = "JPEG_" + timeStamp + "_";
imageFileNameToUseAtWebServerEnd = strTrial + "_" + timeStamp + "_" + strUserId + ".jpg";
File[] storageDir = getExternalMediaDirs();
File image = File.createTempFile(
imageFileName, /* prefix */
".jpg", /* suffix */
storageDir[0] /* directory */
);
return image;
}
I then passed the file object in to the following code:
public Uri testgetPhotoFileUri2(File f)
{
Uri uri = null;
String strDisplayName = f.getName();
final ContentValues values = new ContentValues();
values.put(MediaStore.MediaColumns.DISPLAY_NAME, strDisplayName);
values.put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg");
values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DCIM );
final ContentResolver resolver = thisContext.getContentResolver();
try
{
final Uri contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
uri = resolver.insert(contentUri, values);
if (uri == null)
throw new IOException("Failed to create new MediaStore record.");
return uri;
}
catch (IOException e)
{
if (uri != null) {
// Don't leave an orphan entry in the MediaStore
resolver.delete(uri, null, null);
}
}
return uri;
}
I then used the resulting uri as my camera uri:
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, cameraUri);
However, when the OnActivityResult calls HandleBigCameraPhoto and attempts to extract the bitmap using the CameraUri:
private void handleBigCameraPhoto() {
Bitmap bitmap = null;
if (cameraUri != null)
{
if (Build.VERSION.SDK_INT >= 29) {
ImageDecoder.Source source = ImageDecoder.createSource(getApplicationContext().getContentResolver(), cameraUri);
try {
bitmap = ImageDecoder.decodeBitmap(source);
} catch (IOException e) {
e.printStackTrace();
}
It error traps to "no such file or directory"
Does this mean that I need to most of my work (image resizing, rotation, etc) using my app private file only and then as a last step insert the bitmap in to media store (and then delete the app private file so the user does not see the file name twice under gallery, recents)?
You will not make use of a deprecated function:
File file = new File(getExternalFilesDir(null)
.getParentFile()
.getParentFile()
.getParentFile()
.getParentFile(), "DCIM");
;-).

how get a file path by uri which authority is "com.android.externalstorage.documents"

I want to get a file path through open a file choose by startActivityForResult which intent is Intent.ACTION_GET_CONTENT and setType(* / *), but when I choose open form the "Nexus 5X" item the return uri is "com.android.externalstorage.documents", how to handle this type uri.
There are some codes.
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.putExtra(Intent.ACTION_DEVICE_STORAGE_OK, true);
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
intent.setType("*/*");
startActivityForResult(intent, FILE_ADD_ACTION_REQUEST_CODE);
screenshot
External Storage URIs are of the following form:
content://com.android.externalstorage.documents/root%3Apath
where root is the root of the storage medium, %3A is simply an escaped colon and path is the filesystem path relative to the root (also escaped).
On devices with emulated primary storage (i.e. modern Android devices), the root for primary storage (i.e. /sdcard) is usually called primary. In other cases it seems to be the media ID (4+4 hex digits separated by a hyphen).
You can also try using this (requires API 21 for full functionality):
public static String getRealPathFromURI_API19(Context context, Uri uri) {
String filePath = "";
// ExternalStorageProvider
String docId = DocumentsContract.getDocumentId(uri);
String[] split = docId.split(':');
String type = split[0];
if ("primary".equalsIgnoreCase(type)) {
return Environment.getExternalStorageDirectory() + "/" + split[1];
} else {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) {
//getExternalMediaDirs() added in API 21
File[] external = context.getExternalMediaDirs();
if (external.length > 1) {
filePath = external[1].getAbsolutePath();
filePath = filePath.substring(0, filePath.indexOf("Android")) + split[1];
}
} else {
filePath = "/storage/" + type + "/" + split[1];
}
return filePath;
}
how to handle this type uri
Use ContentResolver and openInputStream() to get an InputStream on the content identified by the Uri.

Making Android filepaths uniform

I have three segments of code: one that sets the original filepath of the file, one that is used in renaming the file, and one that is used to match the file so that the file (audio recording) can be played.
My problem is that, to the best of my knowledge & what I have been able to find out online, I need "file://" before the rest of the filepath when I am renaming it...otherwise the MediaPlayer throws up exceptions when I try to do the playback. After much searching, I have not come up with a good way to make them uniform so that the "matcher" code can work on all the files. My best guess is that it would be ideal if I could find a way to not have to use "file://" before the rest of the filepath.
1) Code that sets original filepath:
public void setFileNameAndPath(){
int count = 0;
File f;
do{
count++;
mFileName = getString(R.string.default_file_name)
+ " #" + (mDatabase.getCount() + count) + ".mp4";
mFilePath = Environment.getExternalStorageDirectory().getAbsolutePath();
mFilePath += "/SoundRecorder/" + mFileName;
f = new File(mFilePath);
}while (f.exists() && !f.isDirectory());
}
2) Renaming the filepath:
public void rename(int position, String name) {
//rename a file
String mFilePath = "file://" + Environment.getExternalStorageDirectory().getAbsolutePath();
mFilePath += "/SoundRecorder/" + name;
File f = new File(mFilePath);
if (f.exists() && !f.isDirectory()) {
//file name is not unique, cannot rename file.
Toast.makeText(mContext,
String.format(mContext.getString(R.string.toast_file_exists), name),
Toast.LENGTH_SHORT).show();
} else {
//file name is unique, rename file
File oldFilePath = new File(getItem(position).getFilePath());
oldFilePath.renameTo(f);
mDatabase.renameItem(getItem(position), name);
notifyItemChanged(position);
}
}
3) Matching the file:
Intent iin = getIntent();
Bundle b = iin.getExtras();
newString = (String) b.get("filename");
mFilePath = Environment.getExternalStorageDirectory().getAbsolutePath();
mFilePath += "/SoundRecorder/" + newString;
I think file:// is the URI of the file and it's useful for example in a mediaplayer where the resource can exists on local storage (file://) or over internet (http://)
To "convert" string to URI use
Uri uri = Uri.parse("http://www.google.com");
And to "convert" URI to file use
File file = new File(uri.getPath());

Read file of any extension in android programmatically

Just want to confirm that is it possible that when i click on file of any extension will open up with its compatible software in android phone or display me the list of software’s present in mobile which can open the file and if it didn't found any software it will indicate user to first download the software to open that particular file (All this thing need to be done pro grammatically).
Thanks.
Any help will be appreciated.
In order to open the file you can use the following method, If there is no application that can handle given file, it simply shows a Toast saying no application found.
private void viewFile(String filePath, String title, int fileType) {
Uri uri = Uri.parse("file://" + filePath);
Intent intent = new Intent(Intent.ACTION_VIEW);
String dataAndType = getIntentDataAndType(filePath);
intent.setDataAndType(uri, dataAndType);
intent.putExtra(Intent.EXTRA_TITLE, title);
// Verify that the intent will resolve to an activity
if (intent.resolveActivity(getActivity().getPackageManager()) != null) {
startActivity(intent);
} else {
Toast.makeText(getActivity(), "No Application found", Toast.LENGTH_SHORT).show();
}
}
UPDATED :
For finding the mime type of the file.
private String getIntentDataAndType(String filePath) {
String exten = "";
int i = filePath.lastIndexOf('.');
// If the index position is greater than zero then get the substring.
if (i > 0) {
exten = filePath.substring(i + 1);
}
String mimeType = android.webkit.MimeTypeMap.getSingleton().getMimeTypeFromExtension(exten);
mimeType = (mimeType == null) ? "*/*" : mimeType;
return mimeType;
}

Android - Set a Local Image to an img element in WebView

I am passing a local file through Javascript to set an img source and I cannot get it working.
The javascript to set the path is
<img src='" + the_payload.image + "' width='" + (cellWidth()-20) + "' />
The String that I am sending is the complete file path:
file:///mnt/sdcard/Android/data/com.newvisioninteractive.android.myapp/files/c87eba4a-5349-4a55-baec-cc573a5f7571-thumb.png
I saw a post about ContentProviders and ParcelFileDescriptors but could not get that to work either.
This is for a Calendar View that displays images on certain days. It works fine when I pass in a static image, just not a local one.
Also, I have set
mWebView.getSettings().setAllowFileAccess(true);
mWebView.getSettings().setJavaScriptEnabled(true);
Do I need to set any other Permissions?
The solution is in extending the ContentProvider class and Overriding the openFile(Uri, String) method
package com.packagename.provider;
public class MyProvider extends ContentProvider {
#Override
public ParcelFileDescriptor openFile(Uri uri, String mode){
URI fileURI = URI.create( "file://" + uri.getPath() );
File file = new File( fileURI );
ParcelFileDescriptor parcel = null;
try {
parcel = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
} catch (FileNotFoundException e) {
Log.e( TAG, "Error finding: " + fileURI + "\n" + e.toString() );
}
return parcel;
}
}
Then in your AndroidManifest.xml file include
<provider
android:name=".provider.MyProvider"
android:authorities="com.packagename" />
You can then access your files which would normally be
file://sdcard/Android/data/com.packagename/image.jpg
by using
content://com.packagename/sdcard/Android/data/com.packagename/image.jpg
So essentially replace file:// with content://com.packagename
try using file:///android_assets/image.png and place images in the android assets

Categories

Resources