Before anything else, I have actually read through several threads regarding sending attachments on Android. That said, I haven't found a solution to my problem.
My app is relatively simple, user types numbers, they get saved to "values.csv" using openFileOutput(filename, Context.MODE_APPEND);.
Now, here's the code I'm using to attach the file to an email and send (I got it from one of the other file threads.)
private void sendEmail(String email) {
File file = getFileStreamPath(filename);
Uri path = Uri.fromFile(file);
Intent intent = new Intent(android.content.Intent.ACTION_SEND);
intent.setType("application/octet-stream");
intent.putExtra(android.content.Intent.EXTRA_SUBJECT, "Test");
String to[] = { email };
intent.putExtra(Intent.EXTRA_EMAIL, to);
intent.putExtra(Intent.EXTRA_TEXT, "Testing...");
intent.putExtra(Intent.EXTRA_STREAM, path);
startActivityForResult(Intent.createChooser(intent, "Send mail..."),
1222);
}
This opens my email client, which does everything right except attach the file, showing me a toast notification saying "file doesn't exist."
Am I missing something? I've already added the permissions for reading and writing to external storage, by the way.
Any and all help would be much appreciated.
EDIT: I can use the File Explorer module in DDMS and navigate to /data/data/com.example.myapp/files/, where my values.csv is located, and copy it to my computer, so the file DOES exist. Must be a problem with my code.
So another way to attach the email except using MODE_WORLD_READABLE is using ContentProvider.
There is a nice tutorial to do this in here
but I will explain it to you shortly too and then you can read that tutorial after that.
So first you need to create a ContentProvider that provides access to the files from the application’s internal cache.
public class CachedFileProvider extends ContentProvider {
public static final String AUTHORITY = "com.yourpackage.gmailattach.provider";
private UriMatcher uriMatcher;
#Override
public boolean onCreate() {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(AUTHORITY, "*", 1);
return true;
}
#Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
switch (uriMatcher.match(uri)) {
case 1:// If it returns 1 - then it matches the Uri defined in onCreate
String fileLocation = AppCore.context().getCacheDir() + File.separator + uri.getLastPathSegment();
ParcelFileDescriptor pfd = ParcelFileDescriptor.open(new File(fileLocation), ParcelFileDescriptor.MODE_READ_ONLY);
return pfd;
default:// Otherwise unrecognised Uri
throw new FileNotFoundException("Unsupported uri: " + uri.toString());
}
}
#Override public int update(Uri uri, ContentValues contentvalues, String s, String[] as) { return 0; }
#Override public int delete(Uri uri, String s, String[] as) { return 0; }
#Override public Uri insert(Uri uri, ContentValues contentvalues) { return null; }
#Override public String getType(Uri uri) { return null; }
#Override public Cursor query(Uri uri, String[] projection, String s, String[] as1, String s1) { return null; }
}
and then write a temporary file
File tempDir = getContext().getCacheDir();
File tempFile = File.createTempFile("values", ".csv", tempDir);
fout = new FileOutputStream(tempFile);
fout.write(bytes);
fout.close();
And then pass it to the email Intent
emailIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse("content://" +
CachedFileProvider.AUTHORITY + "/" + tempFile.getName()));
and don't forget to add this to the manifest
<provider android:name="CachedFileProvider" android:grantUriPermissions="true"
android:authorities="yourpackage.provider"></provider>
I see the answers and of course the question.
You asked how you can send a file from android, but in your code example you are talking about sending by email.
Those are two different things at all...
For sending by email you already have answers so I will not enter it at all.
For sending files from android is something else:
you can create a socket connection to your server and send it. you have of course to write both client and server side.
An example can be found here : Sending file over socket...
you can use an ftp server and upload it to there. you can use any server.
example : It's using apache ftp client library (open source)
you can use a http request and make it a post (this method is preferred only for small files)
example : Sending file with POST over HTTP
you can also use dropbox api or amazon s3 sdk's so you don't care about any connections issues and retries and so on and at the end you have a link to the file and pass over the link.
a. DropBox API : documentation
b. Amazon S3 SDK API : documentation
c. Google Drive API : documentation
The advantages in working with Google Drive.
Is working excelent on Android
Authentication is much easier.
The "used space" is on user account.
regards
Related
I have been working on getting my database backing up to work and I have reached a point where I am not sure what to do.
Basically at first the application opens a Login activity, the user logs in and their database file (if it exists) is downloaded from the Firebase Storage, and then the application navigates to the MainActivity.
In the MainActivity I call a method that sends the user's database file to Firebase Storage. I tried to manage the process by closing the database but since i couldn't fix an error of "E/ROOM: Invalidation tracker is initialized twice :/.", then I found an answer to use a checkpoint (Backup Room database). Now I implemented the forced checkpoint method.
(MarkerDao)
#RawQuery
int checkpoint(SupportSQLiteQuery supportSQLiteQuery);
(MarkerRepository)
public void checkPoint(){
Thread thread= new Thread(() -> markerDao.checkpoint(new SimpleSQLiteQuery("pragma wal_checkpoint(full)")));
thread.start();
}
(ViewModel)
public void setCheckpoint(){
repository.checkPoint();
}
(Database back-up method in the MainActivity)
private void copyDbToFirebase(){
String currentDBPath = "/data/data/"+ getPackageName() + "/databases/locations_table";
File dbBackupFile = new File(currentDBPath);
if (dbBackupFile.exists()){
markerViewModel.setCheckpoint();
// create file from the database path and convert it to a URI
Uri backupDB = Uri.fromFile(new File(currentDBPath));
// Create a StorageReference
StorageReference dbReference = storageRef.child("users").child(userId).child("user database").child("locations_table");
// Use the StorageReference to upload the file
if (userId != null){
dbReference.putFile(backupDB).addOnSuccessListener(new OnSuccessListener<UploadTask.TaskSnapshot>() {
#Override
public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) {
Log.d(TAG, "onSuccess: "+4 + taskSnapshot);
Toast.makeText(getApplicationContext(), "Database copied to Firebase 4", Toast.LENGTH_LONG).show();
}
}).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Log.d(TAG, "onFailure: "+ e.getMessage());
}
});
}
}
}
If the user logs out, then the files in the "/data/data/"+ getPackageName() + "/databases/" are deleted, which I have manually confirmed by looking at the databases folder of the application.
My issue is that after the databases are deleted and a new user logs in, then the previous database data remains but when I manually check the app's data folder, then the /databases/ folder shows that the files were deleted and a new file is created but it doesn't show any WAL or SHM files and also I get the data of another database which is created when the application first runs, but that file is also not shown in the databases/ folder.
Can anyone explain why the folder doesn't show the files that should be present, where is the application getting the data that was deleted and how to fix it.
Edit: My application has multiple Room databases and I just realized that all the data is still readable after the files were deleted.
The method to delete the database files
private boolean deleteDatabaseFiles(File path) {
if(path.exists() ) {
File[] files = path.listFiles();
for(int i=0; i<files.length; i++) {
if(files[i].isDirectory()) {
deleteDatabaseFiles(files[i]);
}
else {
files[i].delete();
}
}
}
return true;
}
If you are using the same exact RoomDatabase object simply building another one over the same object will prevent any hold over cached data from showing up. I've tested this using multiple database swaps large and small and there is no bleed over.
If you are using a new Instance of the RoomDatabase object for every login try closing the old one after the user logs out. Room will typically close when not needed but if you need it to happen immediately, manually closing it is your best bet.
roomDb.getOpenHelper().close();
I am building an app which has a feature to crop images using react-native-image-crop-picker. I am trying to implement the logic to store the cropped images locally in my react native app. I could successfully implement the logic for iOS, however, I am having trouble with the Android side.
My problem is that when I store the image using reactContext.getFilesDir(), the image is stored into the /data/user/0/com.myapp/files/ directory. And the images can be accessed via 'Google Photos' app or 'Files' app. I don't want to let the users access these images.
Here is the picture describing my problem.
The things I have tried so far:
1. Use getCurrentActivity() instead of reactContext
2. Use getReactApplicationContext() instead of context
Findings:
- After saving the image, it is stored into /data/user/0/com.myapp/files/, /data/data/0/com.myapp/files/ and storage/emulated/0/Pictures/.
FileStreamHandler.java
public class FileStreamHandler extends ReactContextBaseJavaModule {
private Context context;
// private Activity mActivity;
#Nonnull
#Override
public String getName() {
return "FileStreamHandler";
}
public FileStreamHandler(ReactApplicationContext reactContext) {
super(reactContext);
// mActivity = reactContext.getCurrentActivity();
this.context = reactContext;
}
#ReactMethod
private void saveImageData(String base64String, Callback callback) {
// Generate random image name
String fileName = UUID.randomUUID().toString() + ".png";
// File fileDirectory = mActivity.getFilesDir();
File fileDirectory = context.getFilesDir();
File imageFile = new File(fileDirectory, fileName);
String imageFilePath = imageFile.getAbsolutePath();
try {
OutputStream stream = new FileOutputStream(imageFile);
//decode base64 string to image
byte[] decodedBytes = Base64.decode(base64String, Base64.DEFAULT);
Bitmap decodedImage = BitmapFactory.decodeByteArray(decodedBytes, 0, decodedBytes.length);
decodedImage.compress(Bitmap.CompressFormat.PNG,100, stream);
stream.flush();
stream.close();
} catch (IOException e) {
e.printStackTrace();
}
callback.invoke(imageFilePath);
}
}
The image is stored successfully without any errors. However, it is stored into /data/user/ and can be accessed via other applications such as 'Photos' or 'Files'.
Although I am using exactly the same logic in my pure Android app, I have never had this problem. Therefore, I am suspecting that the react application context is causing the problem.
Any help would be highly appreciated.
Thank you.
It turns out that the cause of the problem is the react native library that I am using. I don't know why they implemented in this way, however, it seems like the react-native-image-crop-picker library saves images into the /storage/0/Pictures/ directory after cropping.
in my Android app I am using the system Download Manager to download a photo, once recieved I process it and then I need to move it to another location. if I just copy it it works fine, if I try to move it/delete it I get an exception about it can't be moved.
Failed to delete original file '/data/user/0/com.android.providers.downloads/cache/a3133p2930-9.jpg' after copy to '/data/data/com.XXXXX.testharness/app_appdata/images/one/two/three/6/a3133p2930.jpg'
also tried:
1) remove it from download manager before trying to move it
2) set the destination directly to the target dir, but got an error I can only use public external folders.
3) tried using JAVA renameTo and Apache commons moveFile
this is a code snippet that show the relevant parts of what I am doing.
any ideas?
appreciated
#Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(action)) {
String uriString = c.getString(
c.getColumnIndex(
DownloadManager.COLUMN_LOCAL_URI));
String uri2String = c.getString(
c.getColumnIndex(
DownloadManager.COLUMN_URI));
String localFileString = c.getString(
c.getColumnIndex(
DownloadManager.COLUMN_LOCAL_FILENAME));
File from = new File(localFileString);
File to = new File(appDataDir, localString);
File to2 = new File (appDataDir, localString + "/" + getFileNameFromUrl(uri2String));
if (!to.exists()) {
if (to.mkdirs()) {
Log.d(LCAT, "Directory Created");
}
}
dm.remove(downloadId);
//boolean a = from.renameTo(to2);
try {
FileUtils.copyFile(from, to2);
} catch (Exception e) {
Log.d(LCAT, "rename success? " + e.getMessage());
}
i think you can not delete the cache of downloadmanager, i think it clears itself automatically after you removed the downloadid from its database but if you like to have a control to your file you can set destination folder to your private external storage and then after download completed delete it.
File rootDirectory = new File(getActivity().getExternalFilesDir(null).getAbsoluteFile().toString());
if(!rootDirectory.exists()){
rootDirectory.mkdirs();
}
req.setDestinationInExternalFilesDir(getActivity(),null ,"/" + fileName);
and after your download completed you can delete it like:
from.delete();
and the doc says:
public int remove (long... ids)
Cancel downloads and remove them from
the download manager. Each download will be stopped if it was running,
and it will no longer be accessible through the download manager. If
there is a downloaded file, partial or complete, it is deleted.
http://developer.android.com/reference/android/app/DownloadManager.html#remove%28long...%29
In my app you can download some files. I used the Android DownloadManager class for downloading. After the download is completed, it should show me a message that the file was downloaded. The problem is, there could be 2,3 or 4 downloads at the same time. My BroadcastReceiver code looks like this:
receiver_complete = new BroadcastReceiver(){
#Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(DownloadManager.ACTION_DOWNLOAD_COMPLETE) ){
Toast.makeText(MainActivity.this, MainActivity.this.getString(R.string.download_finished, "Here should be the name", Toast.LENGTH_SHORT).show();
}
}
};
How can I get the current filename of the finished download?
Thank you very much.
I think you want to put something like this inside your if block. Replace YOUR_DM with your DownloadManager instance.
Bundle extras = intent.getExtras();
DownloadManager.Query q = new DownloadManager.Query();
q.setFilterById(extras.getLong(DownloadManager.EXTRA_DOWNLOAD_ID));
Cursor c = YOUR_DM.query(q);
if (c.moveToFirst()) {
int status = c.getInt(c.getColumnIndex(DownloadManager.COLUMN_STATUS));
if (status == DownloadManager.STATUS_SUCCESSFUL) {
// process download
title = c.getString(c.getColumnIndex(DownloadManager.COLUMN_TITLE));
// get other required data by changing the constant passed to getColumnIndex
}
}
Ian Shannon was totally right with his answer, but I sugest some improvement:
Remember to close that Cursor after using it, avoiding "Cursor Leaking". This Cursor consumes a lot of resources and must be released as soon as possible.
If you put some title for the download, such as:
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));
request.setTitle("Some title");
The value given by the DownloadManager.COLUMN_TITLE will be "Some title" instead of the file name. So I would recommend this instead:
String filePath = c.getString(c.getColumnIndex(DownloadManager.COLUMN_LOCAL_FILENAME));
title = filePath.substring( filePath.lastIndexOf('/')+1, filePath.length() );
The COLUMN_LOCAL_FILENAME returns the entire path (/storage/sdcard0/Android/data/.../filename.ext), but with this code, we will only get the file name.
Final code:
Bundle extras = intent.getExtras();
DownloadManager.Query q = new DownloadManager.Query();
q.setFilterById(extras.getLong(DownloadManager.EXTRA_DOWNLOAD_ID));
Cursor c = YOUR_DM.query(q);
if (c.moveToFirst()) {
int status = c.getInt(c.getColumnIndex(DownloadManager.COLUMN_STATUS));
if (status == DownloadManager.STATUS_SUCCESSFUL) {
String filePath = c.getString(c.getColumnIndex(DownloadManager.COLUMN_LOCAL_FILENAME));
filename = filePath.substring( filePath.lastIndexOf('/')+1, filePath.length() );
}
}
c.close();
Edit: Replace YOUR_DM with your DownloadManager instance.
I think what you are looking for is the DownloadManager.COLUMN_TITLE
Here is a link to the Android Documents: http://developer.android.com/reference/android/app/DownloadManager.html#COLUMN_TITLE
And here is a tutorial that will explain more than just getting the title. It is for downloading an image from a URL and then displaying it in an application. Mighty useful I think, overall speaking.
http://wptrafficanalyzer.in/blog/downloading-an-image-from-an-http-url-using-downloadmanager-and-displaying-in-imageview-by-dynamically-registered-broadcastreceiver/
There's a simpler way to retrieve the downloaded file URI, and that is with getUriForDownloadedFile:
download_receiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
Bundle b = intent.getExtras();
DownloadManager dm = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);
long file_id = b.getLong(DownloadManager.EXTRA_DOWNLOAD_ID);
Uri uri = dm.getUriForDownloadedFile(downloaded_file_id);
}
}
I found this method here in a full description of how to use a BroadcastReceiver for handling DOWNLOAD_COMPLETE events.
u try this code to get the file name
DownloadManager.COLUMN_LOCAL_FILENAME
i am not sure about it
I am writing an application wherein I want to detect if a download has started and retrieve the URI of the file being downloaded and then cancel the download from the Download Manager. I am doing this so that I can send this URI somewhere else.
The trouble is that I can detect when a download begins by querying the Download Manager, but is there a method or a constant variable in Download Manager from which I can also get the URL of the file being downloaded
Ok its weird answering your own question, but I finally figured out how to do this. There is a DownloadManager class in android.app, which stores a list of all http downloads initiated and their statuses. These can be filtered out based on whether the download is 'RUNNING', 'PENDING', 'PAUSED' and so on.
This list can be read into a cursor and one of the columns of the result is 'COLUMN_URI', which is the url from where the file is being downloaded. A sample code where I have used it is as given below:
public void readDownloadManager() {
DownloadManager.Query query = null;
DownloadManager downloadManager = null;
Cursor c = null;
try {
query = new DownloadManager.Query();
downloadManager = (DownloadManager)getSystemService(DOWNLOAD_SERVICE);
//Just for testing I initiated my own download from this url. When an http
// reuest for this url is made, since download is taking place, it gets saved in
// the download manager.
Request request = new Request(Uri.parse("http://ocw.mit.edu/courses" +
"/aeronautics-and-astronautics/16-100-aerodynamics-fall-2005" +
"/lecture-notes/16100lectre1_kvm.pdf"));
downloadManager.enqueue(request);
query.setFilterByStatus(DownloadManager.STATUS_PENDING);
c = downloadManager.query(query);
if(true){
int statusColumnIndex = c.getColumnIndex(DownloadManager.COLUMN_STATUS);
int urlColumnIndex = c.getColumnIndex(DownloadManager.COLUMN_URI);
long downloadProcessIdColumnNo = c.getColumnIndex(DownloadManager.COLUMN_ID);
Log.d("Column Count", ((Integer)c.getCount()).toString());
if(c.getCount() > 0){
String url="";
c.moveToLast();
if(c.isLast()){
url = c.getString(urlColumnIndex);
downloadManager.remove(downloadProcessIdColumnNo);
Log.d("Count after remove", ((Integer)c.getCount()).toString());
}
Log.d("After", "Stopped Working");
//Here I am sending the url to another activity, where I can work with it.
Intent intent = new Intent(EasyUploadMainMenu.this, EasyUploadActivity.class);
Bundle b = new Bundle();
b.putString("url", url);
intent.putExtras(b);
startActivity(intent);
Log.d("url:", url);
}
}
} catch (NullPointerException ex) {
ex.printStackTrace();
}
}