Understanding FileProvider in Android Studio - java

I am very noob in Android studio Java apps. so please bear with me on this.
So I would like to save some text data to a .txt file and then share this Text file on a click of a button as attachment for an Email.
I think there are two ways of doing this:
1- Send the .txt file as attachment per email in the background of the activity/App or whatever it is called.
for that way I have already found the SendGmail and I don't know the available stuff in Internet and so on and made Gmail account less secure and stuff but as a noob it just did not work for me so if you can provide me with step-by-step salutation it will be great (it would really really help me if you don't miss out any step even if small and understandable,... thanks in advance)
2- Promote the user to send the Email by using the Intent stuff in android studio java language.
for that according also to the search I have done we need to use the FileProvider service (if I am allowed to call it like that) from the Android Studion Java language.
for that I did the following:
I Added the Stuff need to be added in the AndroidManifest.xml as following:
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.example.XXXX.YYYY"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/file_paths" />
</provider>
and then I created the file_paths.xml file here please support me as I don't exactly know what and how to do it correctly:
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="/storage/emulated/0" path="."/>
</paths>
And now I think I am ready to Save The Text data to a .txt file using this function:
filepath = "AppData";
// in side the on create method i added this permission stuff
ActivityCompat.requestPermissions(MainActivity.this, new String[]{READ_EXTERNAL_STORAGE, WRITE_EXTERNAL_STORAGE},PackageManager.PERMISSION_GRANTED);
// of course in the Manifest i also added the user request for Eternal Storage saving stuff
private String SaveToExternal(String fileContent) {
if (!fileContent.equals("")) {
File myExternalFile = new File(getExternalFilesDir(filepath), "DataToSend.txt");
String Out = getExternalFilesDir(filepath) + File.separator + "DataToSeve.txt";
FileOutputStream fos = null;
try {
fos = new FileOutputStream(myExternalFile);
fos.write(fileContent.getBytes());
} catch (FileNotFoundException e) {
e.printStackTrace();
return "Unable to save";
} catch (IOException e) {
e.printStackTrace();
return "Unable to save";
}
Toast.makeText(MainActivity.this,"Data to send is prepared and ready",Toast.LENGTH_SHORT).show();
//return Out;
return myExternalFile.getPath();
} else {
Toast.makeText(MainActivity.this,"Data to send is not available",Toast.LENGTH_SHORT).show();
return "Unable to save";
}
}
here if you can provide me with more information on how really to select where to save what it will be great (sorry for asking too much)
now the .txt file is there I just need to attach it and send it per Email of the App user: I used the following Code for that:
private void sendEmail(String Body, String FilePath) {
// Toast.makeText(MainActivity.this, "Your Email Body is: " + Body,Toast.LENGTH_LONG).show();
try {
// First copy the File from internal to External storage:
// File dst = new File(StringPath);
// exportFile(new File(FilePath), dst);
// Then Share it now
// This one was working:
Uri Pathtosend = FileProvider.getUriForFile(MainActivity.this,"com.example.XXXX.YYYY",new File(FilePath));
Toast.makeText(MainActivity.this,"Your Uri is: " + Pathtosend,Toast.LENGTH_LONG).show();
TTba.setText(Pathtosend.toString());
Intent intent = new Intent(Intent.ACTION_SENDTO)
//.setType("message/rfc822")
//.setType("text/plain")
.setType("*/*")
.setData(new Uri.Builder().scheme("mailto").build())
.putExtra(Intent.EXTRA_EMAIL, new String[]{"XXXX YYYY <XYZ#gmail.com>"})
.putExtra(Intent.EXTRA_SUBJECT, "Email subject")
.putExtra(Intent.EXTRA_TEXT, "Dear Sir/Madem" + System.getProperty("line.separator") + System.getProperty("line.separator") + "I would like to ......")
.putExtra(Intent.EXTRA_STREAM, Pathtosend)
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
Intent chooser = Intent.createChooser(intent, "Send email with");
List<ResolveInfo> resInfoList = getPackageManager().queryIntentActivities(intent,PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo resolveInfo : resInfoList) {
String packageName = resolveInfo.activityInfo.packageName;
grantUriPermission(packageName, Pathtosend, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
try {
// Toast.makeText(MainActivity.this,"I am at the try of send Email",Toast.LENGTH_LONG).show();
startActivity(chooser);
} catch(android.content.ActivityNotFoundException ex) {
Toast.makeText(MainActivity.this, "I am at the Catch of send Email", Toast.LENGTH_LONG).show();
ComponentName emailApp = intent.resolveActivity(getPackageManager());
ComponentName unsupportedAction = ComponentName.unflattenFromString("com.android.fallback/.Fallback");
Toast.makeText(MainActivity.this, "Results of ComponentName: " + emailApp, Toast.LENGTH_LONG).show();
if (emailApp != null && !emailApp.equals(unsupportedAction))
try {
// Needed to customise the chooser dialog title since it might default to "Share with"
// Note that the chooser will still be skipped if only one app is matched
Intent chooser1 = Intent.createChooser(intent, "Send email with");
startActivity(chooser1);
return;
} catch (ActivityNotFoundException ignored) {
}
Toast
.makeText(this, "Couldn't find an email app and account", Toast.LENGTH_LONG)
.show();
}
}catch(android.content.ActivityNotFoundException ex){
Toast.makeText(MainActivity.this,"Unable to create Pathtosend",Toast.LENGTH_LONG).show();
}
}
So after I have those two I can call it when the button is clicked and so on like this:
rv = "Testing stuff that need to be saved to Text file and then attached";
DataPathFile = SaveToExternal(rv);
sendEmail(rv,DataPathFile);
so guy now comes the problem with this method I am using. it is working on the Emulator and SAMUSNG with Android version 8.0.0 but not working on my SAMSUNG with Android version 11.
in my build.gradle i have:
defaultConfig {
applicationId "com.example.XXXX"
minSdkVersion 16
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
I think the problem is with where the file is saved and how to know the exact path of the file and how to dynamically update the file path in xml or java or wherever it should be updated.
Thanks a lot for your help and support :)

Related

Copy database from my App "A" to my App "B" on Android 11

I have released App "A". Now I prepare to release App "B" that should load copy database from App "A" to enable users continue writing data.
This code from App "A" save db from internal storage to a file on scoped storage:
ContentValues values = new ContentValues();
values.put(MediaStore.MediaColumns.DISPLAY_NAME, "/databasename.db");
values.put(MediaStore.MediaColumns.MIME_TYPE, "application/vnd.sqlite3");
values.put(MediaStore.MediaColumns.RELATIVE_PATH, DIRECTORY_DOWNLOADS);
Uri uri = getContentResolver().insert(MediaStore.Files.getContentUri("external"), values);
if (uri != null) {
OutputStream outputStream = getContentResolver().openOutputStream(uri);
if (outputStream != null) {
outputStream.write("This is menu category data.".getBytes());
outputStream.close();
Toast.makeText(ActivitySettings.this, "File created successfully", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(ActivitySettings.this, "outputStream == null", Toast.LENGTH_SHORT).show();
}
} else {
Toast.makeText(ActivitySettings.this, "uri == null", Toast.LENGTH_SHORT).show();
}
} catch (IOException e) {
Toast.makeText(ActivitySettings.this, "Fail to create file", Toast.LENGTH_SHORT).show();
}
This code from App "B" should upload database file that was saved in App "A":
try {
File sd = Environment.getDataDirectory();
File data = Environment.getStorageDirectory();
try {
String currentDBPath = "emulated/0/" + DIRECTORY_DOWNLOADS + "/databasename.db";
String backupDBPath = "/data/com.example.mypackagename/databases/databasename.db";
File currentDB = new File(data, currentDBPath);
File backupDB = new File(sd, backupDBPath);
if (currentDB.exists()) {
FileChannel src = new FileInputStream(currentDB).getChannel();
FileChannel dst = new FileOutputStream(backupDB).getChannel();
dst.transferFrom(src, 0, src.size());
src.close();
dst.close();
Toast.makeText(this, R.string.toast_db_loaded_succesfully, Toast.LENGTH_LONG).show();
} else {
Toast.makeText(this, R.string.toast_db_not_exists, Toast.LENGTH_LONG).show();
}
} catch (Exception e) {
Toast.makeText(this, R.string.toast_not_access, Toast.LENGTH_LONG).show();
}
} catch (Exception e) {
Log.e("DBError", "exception", e);
Toast.makeText(this, R.string.toast_error_load_db, Toast.LENGTH_LONG).show();
}
It's working as well on Android 10 and below but it's not possible due to new access rules of scoped storage Android 11. With MANAGE_EXTERNAL_STORAGE permission it's possible but in my case Google may reject App "B" due to a policy violation.
SharedUserId is deprecated
Sharing files via FileProvider carried out by Activity with UI and additional user's operations.
But I need way without any additional operations, just load database with one click from App "B". It's possible on Android 11? Maybe there is an alternative to SharedUserId?
Any help/thoughts.
Thanks.
The new security requirements are a welcome change from a security standpoint.
The best solution is to send an upgrade to the existing app A to send the data an external endpoint accessible by app B.
Having App A modify App B data without app B's permission IS a violation of security protocols and should NEVER be allowed.
Unless you are the owner of both app A and app B, what you are asking is impossible and should never be allowed to happen.

Getting application link from Google Play before upload

I have an item in my navigation drawer called "share app" which users can use to share application link to other users.
My question is, can i get the link without first uploading to google play. I don't intend to re-upload my app in other to apply this functionality.
I have tried searching for this answer without any meaningful solution.
try {
Intent i = new Intent(Intent.ACTION_SEND);
i.setType("text/plain");
i.putExtra(Intent.EXTRA_SUBJECT, "My application name");
String sAux = "\nLet me recommend you this application\n\n";
sAux = sAux + "https://play.google.com/store/apps/details?id=com.app.ben.example \n\n";
i.putExtra(Intent.EXTRA_TEXT, sAux);
startActivity(Intent.createChooser(i, "choose one"));
} catch(Exception e) {
e.toString();
}
Play store links are deterministic. Your link should look like this :
https://play.google.com/store/apps/details?id=packagename
Edit
To know your package name, search for package in manifest tag : https://developer.android.com/guide/topics/manifest/manifest-element.html

File not found exception? (Voice recog)

Sorry for the long question, I have been stuck on this for a month, and I want to provide as much detail as possible...its just a file not found exception in a simple library... :)
I am getting a file not found exception on my variances file:
I do, however, have the variances file:
I am trying to simply implement voice recognition in my background service, so that I can detect when the user says the word hello (using pocketsphinx).
The problem happens in this method: createSphinxDir();
Here is my service:
#Override
public void onCreate() {
super.onCreate();
setupRecog();
}
private void setupRecog() {
String sphinxDir = createSphinxDir();
Log.v(TAG, "ABOUT TO CREATE SETUP");
if (sphinxDir != null) {
try {
Log.v(TAG, "SETTING UP! :)");
mSpeechRecognizer = defaultSetup()
.setAcousticModel(new File(sphinxDir, "en-us-ptm"))
.setDictionary(new File(sphinxDir, "hello.dict"))
.setBoolean("-allphone_ci", true) //WHAT IS THIS
.getRecognizer();
mSpeechRecognizer.addListener(this);
Log.v(TAG, "ADDED LISTENER");
if ((new File(sphinxDir + File.separator + "command.gram")).isFile()) {
mSpeechRecognizer.addKeywordSearch("hello",
new File(sphinxDir + File.separator + "command.gram"));
Log.v(TAG, "ADDED KEYWORD SEARCH! :)");
}
// Or wherever appropriate
mSpeechRecognizer.startListening("wakeup"); //Is this correct?
Log.v(TAG, "STARTED LISTENING");
} catch (IOException e) {
Log.v("ERROR", TAG);
}
}
}
String createSphinxDir() {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
String sphinxDir = prefs.getString("sphinx", null);
if (sphinxDir == null) {
Assets assets;
Log.v(TAG, "Assets are not synced, should sync now:");
try {
Log.v(TAG, "In try block!");
assets = new Assets(this);
File sphinxDirFile = assets.syncAssets();
Log.v(TAG, "Syncing assets...should set up listener");
if (sphinxDirFile != null) {
sphinxDir = sphinxDirFile.getAbsolutePath();
SharedPreferences.Editor editor = prefs.edit();
editor.putString("sphinx", sphinxDir);
editor.commit();
Log.v(TAG, "Set up listener");
}else{
Log.v(TAG, "sphinxDirFile is null!");
}
} catch (IOException e) { //THIS IS THE PLACE WHERE I AM GETTING THE ERROR!
e.printStackTrace();
Log.d(TAG, e.toString());
}
}
return sphinxDir;
}
I also have all the call back methods (onPartialResult, onResult, etc.) but they never get called.
Earlier I was getting an exception saying the variances .md5 file didn't exist, so I put a space in between the variances and the .md5, but now I am getting this error, and I don't know why...
Please let me know,
Ruchir
Earlier I was getting an exception saying the variances .md5 file didn't exist, so I put a space in between the variances and the .md5, but now I am getting this error, and I don't know why...
You should not do such things, it causes problems, instead you need to follow the documentation:
The standard way to ship resource files with your application in Android is to put them in assets/ directory of your project. But in order to make them available for pocketsphinx files should have physical path, as long as they are within .apk they don't have one. Assets class from pocketsphinx-android provides a method to automatically copy asset files to external storage of the target device. edu.cmu.pocketsphinx.Assets#syncAssets synchronizes resources reading items from assets.lst file located on the top assets/. Before copying it matches MD5 checksums of an asset and a file on external storage with the same name if such exists. It only does actualy copying if there is incomplete information (no file on external storage, no any of two .md5 files) or there is hash mismatch. PocketSphinxAndroidDemo contains ant script that generates assets.lst as well as .md5 files, look for assets.xml.
Please note that if ant build script doesn't run properly in your build process, assets might be out of sync. Make sure that script runs during the build.
To integrate assets sync in your application do the following
Include app/asset.xml build file from the demo application into your application. Edit build.gradle build file to run assets.xml:
ant.importBuild 'assets.xml'
preBuild.dependsOn(list, checksum)
clean.dependsOn(clean_assets)

Share image in Facebook programmatically from Android app via Intent

I have this code working well on Android 4.0.4.
// Create the new Intent using the 'Send' action.
Intent share = new Intent(Intent.ACTION_SEND);
// Set the MIME type
share.setType(type);
// Create the URI from the media
java.io.File media = new java.io.File(mediaPath);
Uri uri = Uri.fromFile(media);
// Add the URI and the caption to the Intent.
share.putExtra(Intent.EXTRA_STREAM, uri);
share.putExtra(Intent.EXTRA_TEXT, caption);
// Broadcast the Intent.
mActivity.startActivity(Intent.createChooser(share, "Share to"));
But on Android 4.4.2 it crashes the Facebook app. Facebook app opens, the image is not shown and the FB app is dead.
In log dump I've noticed this message:
E/JHEAD ( 5850): can't open '/data/data/cz.volten.brili.android.free/files/product_preview_shared.jpg'
V/ContextImpl( 5850): ----- packageName = com.facebook.katana is NOT LOCKED -----
Could the reason be some security restrictions, e.g. The FB app does not have rights to access the image in the application folder even though it is invoked from an intent?
If so, what would be a proper location for an image shared between the apps?
Shall I use something like this: how to share image to facebook via intent
Could the reason be some security restrictions, e.g. The FB app does not have rights to access the image in the application folder even though it is invoked from an intent?
Correct. That image is on internal storage for your app, which is private to your app.
If so, what would be a proper location for an image shared between the apps?
You can stick with internal storage, though you will need to use a FileProvider, perhaps with my LegacyCompatCursorWrapper, to serve the file. This sample app demonstrates this, albeit with a PDF rather than an image.
Or, put the file on external storage.
Shall I use something like this: how to share image to facebook via intent
You could, though that would seem to be overkill, compared to using FileProvider.
This is what I usually use
private void initShareIntent(String type) {
boolean found = false;
Intent share = new Intent(android.content.Intent.ACTION_SEND);
share.setType("image/jpeg");
// gets the list of intents that can be loaded.
List<ResolveInfo> resInfo = getPackageManager().queryIntentActivities(share, 0);
if (!resInfo.isEmpty()) {
for (ResolveInfo info : resInfo) {
if (info.activityInfo.packageName.toLowerCase().contains(type) ||
info.activityInfo.name.toLowerCase().contains(type)) {
share.putExtra(Intent.EXTRA_TEXT, "Elevator Express");
share.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(new File(imagePath))); // Optional, just if you wanna share an image.
share.setPackage(info.activityInfo.packageName);
found = true;
break;
}
}
if (!found) {
Toast.makeText(getApplicationContext(), "Facebook does not exist", Toast.LENGTH_SHORT).show();
return;
}
startActivity(Intent.createChooser(share, "Select"));
}
}
and call it like this :
iniShareIntent("face");
This code works for me.....here "updateImage" is my image location.
if (isFacebookExist()) {
if (hashClick.isChecked()) {
SharePhoto sharePhoto = new SharePhoto.Builder()
.setBitmap(updateImage)
.build();
if (ShareDialog.canShow(SharePhotoContent.class)) {
SharePhotoContent content = new SharePhotoContent.Builder()
.addPhoto(sharePhoto)
.setShareHashtag(new ShareHashtag.Builder()
.setHashtag("#HashTag")
.build())
.build();
shareDialog.show(content);
}
} else {
SharePhoto sharePhoto = new SharePhoto.Builder()
.setBitmap(updateImage)
.build();
if (ShareDialog.canShow(SharePhotoContent.class)) {
SharePhotoContent content = new SharePhotoContent.Builder()
.addPhoto(sharePhoto)
.build();
shareDialog.show(content);
}
}
} else {
showToast(" Facebook is not install.");
}
private boolean isFacebookExist() {
PackageManager pm = getPackageManager();
try {
PackageInfo info = pm.getPackageInfo("com.facebook.katana", PackageManager.GET_META_DATA);
} catch (PackageManager.NameNotFoundException e) {
return false;
}
return true;
}

Android Studio, Genymotion - sending email with txt file attachment: display in gmail, but doesn't send

Java, Android Studio, Genymotion.
Dear colleagues,
i'm sending email (Intent) with txt attach from android application. Txt file was created by application earlier.
In genymotion in gmail client this attachment (file around 1 Kb) is displaying, but real mail is coming without attachment.
Code snippets:
// file creating
...
final String FILENAME = "file";
...
try {
// отрываем поток для записи
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(openFileOutput(FILENAME, MODE_PRIVATE)));
// writing any data
bw.write ("\n");
...
Log.d(LOG_TAG, "file is created");
bw.close();
}
// sending email with intent
public void sendEmailwithMailClient (){
Intent emailIntent = new Intent(android.content.Intent.ACTION_SEND);
// sending email
emailIntent.setType("plain/text");
emailIntent.putExtra(android.content.Intent.EXTRA_EMAIL, new String[]{"example#rambler.ru"});
emailIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, getString(R.string.app_name));
emailIntent.putExtra(android.content.Intent.EXTRA_TEXT, "Hello!");
File file = new File(getFilesDir(), FILENAME);
// if (!file.exists() || !file.canRead()) {
// return;}
Uri uri = Uri.fromFile(file);
emailIntent.putExtra(Intent.EXTRA_STREAM, uri);
startActivity(Intent.createChooser(emailIntent, "Pick an Email provider"));
}
Do i correctly define Uri for attachment via getFilesDir() and FILENAME?
Why email is loosing attachment during sending? It's issue of Genymotion or in reality i'm not attaching anything to mail and attach displaying in Genymotion is just a fake?
Thank you in advance!
You cannot attach a file from the your apps private storage.
You need to save it to external storage and then attach.
File file = new File(getFilesDir(), FILENAME);
is creating your file in /data/data/package_name/files directory.
Which is not accessible form the other apps.
If you still want to share the file from the apps private storage you need to create your ContentProvider.

Categories

Resources