I spent the last 8 hours to find a way to upload a file from the internal memory of my android phone to a google drive account.
I'm very frustrated right now. The official examples hosted at GitHub, explain how to take a photo and upload it on the drive. My problem is different, I already have a file on my device and I'd like to upload it on the drive.
Here is my last try (this is inspired by various examples found on the web):
public class MainActivity extends ActionBarActivity {
private GoogleAccountCredential credential;
private static Drive service;
int ACCOUNT_REQUEST = 1;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
credential = GoogleAccountCredential.usingOAuth2(this, DriveScopes.DRIVE);
startActivityForResult(credential.newChooseAccountIntent(), ACCOUNT_REQUEST);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
protected void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
if (resultCode == RESULT_OK && data != null && data.getExtras() != null) {
String accountName = data.getStringExtra(AccountManager.KEY_ACCOUNT_NAME);
if (accountName != null) {
credential.setSelectedAccountName(accountName);
Drive.Builder builder = new Drive.Builder(AndroidHttp.newCompatibleTransport(), new GsonFactory(), credential);
builder.setApplicationName("Test Application");
service =builder.build();
uploadTheFile();
}
}
}
public void uploadTheFile() {
String filePath = "storage/sdcard0/file.txt";
String fileName = "File";
File body = new File();
body.setTitle(fileName);
body.setDescription(fileName);
body.setMimeType("text/plain");
java.io.File fileContent = new java.io.File(filePath);
FileContent mediaContent = new FileContent("text/plain", fileContent);
try {
File file = service.files().insert(body, mediaContent).execute();
} catch (IOException e) {
e.printStackTrace();
}
}
I can select the account where to upload the file (file.txt), but then it crash with:
java.lang.IllegalStateException: calling this from your main thread can lead to deadlock
I've tried to give as many info as I can, if you need further info, don't exitate to ask! Thanks !
EDIT: Thanks to greenapps the deadlock problem is fixed, but I still can't upload my file: where should I put my client id and client secret ?? Thanks !
String filePath = "storage/sdcard0/file.txt";
change that to
String filePath = "/storage/sdcard0/file.txt";
And before uploading check if that file exists.
File file = new File (filePath);
if ( ! file.exists ) )
{
message that file does not exist
return false;
}
Your IllegalStateException can be solved by putting uploadFile() in an AsyncTask
If you can get the photo upload "quick starter" demonstration code working (no small task in itself), try modifying that code to copy your file (instead of the photo) to Drive's outputStream as shown in the following code which worked for me. (I'm not a serious Java programmer so I wouldn't be surprised if there's a better way to copy a file to an outputStream.)
public void onResult(ContentsResult result) {
// Get an output stream for the contents.
OutputStream outputStream = result.getContents().getOutputStream();
// Write specified local file to Google Drive output stream.
byte[] buf = new byte[1024];
int nbyteWritten = 0;
int nbyte;
try {
DataInputStream dis = new DataInputStream(new FileInputStream(file));
DataOutputStream dos = new DataOutputStream(outputStream);
while ((nbyte = dis.read(buf)) > 0) {
dos.write(buf, 0, nbyte);
nbyteWritten += nbyte;
}
dis.close();
dos.close();
} catch (IOException e) {
Log.i(TAG, "Unable to write file contents.");
}
This should to a lot easier using the Android-specific API. See create files. It handles all the authorization and uploading for you. Just copy the contents from your local file on disk into the OutputStream provided by the new contents object.
Related
I am used to opening my files in my apps using the next code:
public void openFile(#NonNull String uri) {
checkNotNull(uri);
File file = new File(uri);
String dataType = null;
if (ContentTypeUtils.isPdf(uri)) dataType = "application/pdf";
else if (ContentTypeUtils.isImage(uri)) dataType = "image/*";
if (file.exists() && dataType != null) {
Intent target = new Intent(Intent.ACTION_VIEW);
target.setDataAndType(Uri.fromFile(file), dataType);
target.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
Intent intent = Intent.createChooser(target, "Open file");
try {
startActivity(intent);
} catch (ActivityNotFoundException e) {
e.printStackTrace();
Log.e(TAG, "There is a problem when opening the file :(");
}
} else {
Toast.makeText(getContext(), "Invalido", Toast.LENGTH_LONG).show();
}
}
I had always used static files so this was enough, but now I am using the Google Drive SDK for Android. I possess the driveId of the file I want to open but the problem is I cannot find a clean way to open the file contents I obtain by doing this:
Drive.DriveApi.fetchDriveId(mGoogleApiClient, documentFile.getDriveId())
.setResultCallback(driveIdResult -> {
PendingResult<DriveApi.DriveContentsResult> open =
driveIdResult.getDriveId().asDriveFile().open(
mGoogleApiClient,
DriveFile.MODE_READ_ONLY,
null);
open.setResultCallback(result -> {
DriveContents contents = result.getDriveContents();
InputStream inputStream = contents.getInputStream();
// I know I can get the input stream, and use it to write a new file.
});
});
So the only thing that comes to my mind is creating a static route to create a file every time I have to open it, and erasing it every time I have to open a new file.
What I have understood up until now is that the Google Drive API for Android already saves an instance of the file so what I have in mind sounds unnecessary, I would like to know if there is a better way to achieve this. Is there a way I can open the file and do something similar to what I do with the Intent.ACTION_VIEW in a cleaner way?
Thanks in advance.
Well since it seems this will not be answered I will post what I did. All I did was create a temp file where I put my contents to be read. I still don't know if it was the best choice so this question will still be opened for a better answer.
open.setResultCallback(result -> {
DriveContents contents = result.getDriveContents();
InputStream inputStream = contents.getInputStream();
writeTempFile(inputStream);
});
And here the implementation of the `writeTempFile`:
private synchronized File writeTempFile(#NonNull InputStream inputStream) {
checkNotNull(inputStream);
File filePath = new File(mActivity.getFilesDir(), "TempFiles");
if (!filePath.exists()) filePath.mkdirs();
File file = new File(filePath, TEMP_FILE);
try {
OutputStream outputStream = new FileOutputStream(file);
IOUtils.copyLarge(inputStream, outputStream);
IOUtils.closeQuietly(inputStream);
IOUtils.closeQuietly(outputStream);
} catch (IOException e) {
e.printStackTrace();
}
return file;
}
I'm able to upload database to Drive using the following post.
Drive API - Download/upload sql database
But I'm not able to access it directly offline without using app.
Aim: Use the db file further in different application so I want it to be in a usable format whenever I download the content directly from google drive.
I am using MODE_WRITE_ONLY to upload the file to drive from within app
mfile.open(api, DriveFile.MODE_WRITE_ONLY, new DriveFile.DownloadProgressListener()
And mime type as this String mimeType = MimeTypeMap.getSingleton().getExtensionFromMimeType("db");
My db size is 44kb when I access from external sd card on phone, however it shows 40kb when I see on drive. Please suggest what can I do to make it readable so that I can directly open it in an sqlite browser because when I open it shows "File not recognized".
Do I have to make changes in the WRITE only part or mime type for db file. Please suggest what could be the problem.
Since I've successfully tested an SQLite file upload to GooDrive, I can post a piece of code that does it:
Let's assume, there is a SQLite file on your android device:
java.io.File dbFile = Context.getDatabasePath([YOUR_DB_NAME])
Then you can call this method:
upload("temp.db", dbFile, "application/x-sqlite3")
com.google.android.gms.common.api.GoogleApiClient GAC;
///...
void upload(final String titl, final File file, final String mime) {
if (GAC != null && GAC.isConnected() && titl != null && file != null) try {
Drive.DriveApi.newDriveContents(GAC).setResultCallback(new ResultCallback<DriveContentsResult>() {
#Override
public void onResult(#NonNull DriveContentsResult contRslt) {
if (contRslt.getStatus().isSuccess()){
DriveContents cont = contRslt.getDriveContents();
if (cont != null && file2Os(cont.getOutputStream(), file)) {
MetadataChangeSet meta = new Builder().setTitle(titl).setMimeType(mime).build();
Drive.DriveApi.getRootFolder(GAC).createFile(GAC, meta, cont).setResultCallback(
new ResultCallback<DriveFileResult>() {
#Override
public void onResult(#NonNull DriveFileResult fileRslt) {
if (fileRslt.getStatus().isSuccess()) {
// fileRslt.getDriveFile(); BINGO !!!
}
}
}
);
}
}
}
});
} catch (Exception e) { e.printStackTrace(); }
}
static boolean file2Os(OutputStream os, File file) {
boolean bOK = false;
InputStream is = null;
if (file != null && os != null) try {
byte[] buf = new byte[4096];
is = new FileInputStream(file);
int c;
while ((c = is.read(buf, 0, buf.length)) > 0)
os.write(buf, 0, c);
bOK = true;
} catch (Exception e) {e.printStackTrace();}
finally {
try {
os.flush(); os.close();
if (is != null )is.close();
} catch (Exception e) {e.printStackTrace();}
}
return bOK;
}
To create a "temp.db" SQLite file in the root of your GooDrive.
You can certainly supply a different parent folder (instead of Drive.DriveApi.getRootFolder(GAC)) if you need to place your file in a different location.
In my app, I need to upload multiple files (1 sqlite db file and multiple image files) for backup purpose to user's google drive.
I am using android google drive api, but not sure, how to do back to back file uploads and then later on downloads like this.
The db file obviously comes from /data/data//databases kind of directory whereas images are stored in pictures directory. I need to grab all of these one by one and upload to drive.
Also, I have seen that if a given file (with the same title) already exists, even then, a new file with the same title is created on drive (obviously has diff DriveId but same title). I would like to check, if the file exists and only upload if it doesn't, else skip that file.
Please help.. I have been trying to refer the android demos on github by google, but have been only able to do bits and pieces using that.
File title is not unique in Drive API. However you can save the IDs of your newly created files in your app's local storage, so you can check against the IDs in the Drive side when you want to upload the file again.
You can use the CreateFileActvity.java from the Google Drive Demo GitHub page. It will return a file ID after you create a file successfully, so you can store the ID in your local storage.
Sample code from CreateFileActivity.java:
final private ResultCallback<DriveFileResult> fileCallback = new
ResultCallback<DriveFileResult>() {
#Override
public void onResult(DriveFileResult result) {
if (!result.getStatus().isSuccess()) {
showMessage("Error while trying to create the file");
return;
}
showMessage("Created a file with content: " + result.getDriveFile().getDriveId());
}
};
Just in case somebody is looking how to upload multiple files to Drive, here is solution that worked for me:
for(String fileName: fileNameArrayList){backupImage(fileName);}
private void backupImage(String fileName) {
Drive.DriveApi.newDriveContents(mGoogleApiClient).setResultCallback(
new BackupImagesContentsCallback(mContext, mGoogleApiClient, fileName));
}
Backup callback:
public class BackupImagesContentsCallback implements ResultCallback<DriveApi.DriveContentsResult> {
#Override
public void onResult(#NonNull DriveApi.DriveContentsResult driveContentsResult) {
if (!driveContentsResult.getStatus().isSuccess()) {
Log.v(TAG, "Error while trying to backup images");
return;
}
MetadataChangeSet changeSet = new MetadataChangeSet.Builder()
.setTitle(mFileName) // Google Drive File name
.setMimeType("image/jpeg")
.setStarred(true).build();
Drive.DriveApi.getAppFolder(mGoogleApiClient)
.createFile(mGoogleApiClient, changeSet, driveContentsResult.getDriveContents())
.setResultCallback(backupImageFileCallback);
}
final private ResultCallback<DriveFolder.DriveFileResult> backupImageFileCallback = new ResultCallback<DriveFolder.DriveFileResult>() {
#Override
public void onResult(#NonNull DriveFolder.DriveFileResult result) {
if (!result.getStatus().isSuccess()) {
Log.v(TAG, "Error while trying to backup images");
return;
}
DriveFile mImageFile;
mImageFile = result.getDriveFile();
mImageId = result.getDriveFile().getDriveId();
mImageFile.open(mGoogleApiClient, DriveFile.MODE_WRITE_ONLY, (bytesDownloaded, bytesExpected) -> {
}).setResultCallback(backupImagesContentsOpenedCallback);
}
};
final private ResultCallback<DriveApi.DriveContentsResult> backupImagesContentsOpenedCallback =
new ResultCallback<DriveApi.DriveContentsResult>() {
#Override
public void onResult(#NonNull DriveApi.DriveContentsResult result) {
if (!result.getStatus().isSuccess()) {
return;
}
DriveContents contents = result.getDriveContents();
BufferedOutputStream bos = new BufferedOutputStream(contents.getOutputStream());
byte[] buffer = new byte[1024];
int n;
File imageDirectory = new File(mContext.getFilesDir(),
Constants.IMAGE_DIRECTORY_NAME);
try {
FileInputStream is = new FileInputStream(new File(imageDirectory,
mFileName));
BufferedInputStream bis = new BufferedInputStream(is);
while ((n = bis.read(buffer)) > 0) {
bos.write(buffer, 0, n);
}
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
contents.commit(mGoogleApiClient, null);
}
};
}
This is not perfect solution, just a working code.
i am breaking my brain trying to attach an arm pre-compiled binary to my private android app.
if you don't mind, i tell you about my app. it just needs to modify iptables rules because of my gprs communication with my robot. it is a remote control for android, and i get better results if i deny all traffic instead my robot's one.
so i compiled an iptables with my cyanogenmod's version 11 iptables, but with -save -restore support, since i want to restore the rules after finishing the control of my robot..
the thing is that i've been searching in google for a lot of while, and i just found droidwall which seems to only create a 'raw' directory into the 'res' top dir, and once installed, i can see on adb shell the folder 'app_bin' inside /data/data's path.
but when i do install my app, with those dirs createds i can not see even the binary in some strange path... really, is this a very rare case? I don't find any documentation on the network...
thanks a lot,
hope you find it helpful.
abel.
EDIT (possible sollution)
I've downloaded the code from android_firewall project, where it seems to be copying from the apk resource, to the bin directory:
./src/com/jtschohl/androidfirewall/Api.java: final String app_iptables = dir + "/iptables_armv5";
./src/com/jtschohl/androidfirewall/Api.java: // Check iptables_armv5
./src/com/jtschohl/androidfirewall/Api.java: File file = new File(ctx.getDir("bin", 0), "iptables_armv5");
./src/com/jtschohl/androidfirewall/Api.java: copyRawFile(ctx, R.raw.iptables_armv5, file, "755");
I am going to try. Keep on news...
Yes, this is the sollution. The folder name "app_%{TYPE}" is the one returned by the calling of the function getDir(%{TYPE}, MODE_PRIVATE);
So the following code, does the desired functionality:
private static void copyRawFile(Context ctx, int resid, File file, String mode) throws IOException, InterruptedException {
final String abspath = file.getAbsolutePath();
// Write the iptables binary
final FileOutputStream out = new FileOutputStream(file);
final InputStream is = ctx.getResources().openRawResource(resid);
byte buf[] = new byte[1024];
int len;
while ((len = is.read(buf)) > 0) {
out.write(buf, 0, len);
}
out.close();
is.close();
// Change the permissions
Runtime.getRuntime().exec("chmod " + mode + " " + abspath).waitFor();
}
private boolean assertBinaries(Context ctx, boolean showErrors) {
try {
File file = new File(ctx.getDir("bin", MODE_PRIVATE), "xtables_multi");
if (!file.exists()) {
copyRawFile(ctx, R.raw.xtables_multi, file, "755");
}
file = new File(ctx.getDir("bin", MODE_PRIVATE), "iptables_new.sh");
if (!file.exists()) {
copyRawFile(ctx, R.raw.iptables_new, file, "755");
}
} catch(Exception e) {
if (showErrors) {
alert(ctx, R.string.error_assertbinaries + " " + e);
}
return false;
}
return true;
}
public static void alert(Context ctx, CharSequence msg) {
if (ctx != null) {
Toast.makeText(ctx, msg, Toast.LENGTH_SHORT).show();
}
}
Hope that helps,
cheers; Abel.
Obs: I know that here have many questions about this, but none of them do what i want, seriously, so don't downvote it due to Duplicate question, thanks.
Well, i have a application that have all the necessary Bluetooth classes for connection and managing and etc. My application is a Chat via Bluetooth, i can send string messages to another device and the other device can send to me too. Server and Client.
Here is my method to send Message (working)
public boolean sendMessageByBluetooth(String msg){
try {
if(dataOutputStream != null){
dataOutputStream.write(msg.getBytes());
dataOutputStream.flush();
return true;
}else{
sendHandler(ChatActivity.MSG_TOAST, context.getString(R.string.no_connection));
return false;
}
} catch (IOException e) {
LogUtil.e(e.getMessage());
sendHandler(ChatActivity.MSG_TOAST, context.getString(R.string.failed_to_send_message));
return false;
}
}
I want, if possible, a method that is similar to that method, that can Send Files so i can use the same source to implement the File Transfer.
But i need to have a functionality on my app that is a File Transfer, that have a Browse button to search a file on your Device and then select it and send to paired device, simple as that.
Too, i want to see a ProgressBar filling to the user see how long it will take to send the file :)
How i can do that?
--EDIT--
I am trying the File Transfer here is what i did:
FileActivity.java
onCreate event to connect:
verifyAndActivateBluetooth(); //this function is same used on ChatActivity it is working.
registerFilter(); //this function is same used on ChatActivity it is working.
chatBusinessLogic.startFoundDevices(); //this function is same used on ChatActivity and it is working.
Then i choose the visible another device and it connect successful.
After connected Then..
Button Browse -> Intent to show the file explorer:
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("file/*");
startActivityForResult(intent, FILE_FOUND);
FileActivity onActivityResult switch(requestCode):
case FILE_FOUND:
filename = data.getData().getPath(); //here was the problem of error 1. now fixed
if(filename.trim().length() > 0){
if(chatBusinessLogic.sendFile(filename)){
lblFilename.setText(filename);
toastUtil.showToast("Success");
}
}else{
toastUtil.showToast("fail");
}
The chatBusinessLogic.sendFile(filename) :
public boolean sendFile(String file){
if(bluetoothComunication != null){
return bluetoothComunication.sendFileByBluetooth(file);
}else{
return false;
}
}
the bluetoothComunication.sendFileByBluetooth(file):
public boolean sendFileByBluetooth(String fpath){
File f = new File(fpath);
int n = 0;
byte b[] = new byte[(int) f.length()];
try
{
FileInputStream fileInputStream = new FileInputStream(new File(fpath)); //ERROR FIXED
DataOutputStream dataOut = new DataOutputStream(dataOutputStream);
while (-1 != (n = fileInputStream.read(b))){
dataOutputStream.write(b, 0, n);
}
dataOut.flush();
fileInputStream.close();
dataOut.close();
return true;
}catch(IOException e)
{
LogUtil.e("Error converting file");
LogUtil.e(e.getMessage());
return false;
}
i was getting this error(NOW IT IS FIXED):
If i choose android native method for physical path:
/content:/com.estrongs.files/mnt/sdcard/calc-history.txt: open failed: ENOENT (No such file or directory)
If i choose alternative method for physical path:
/file:/mnt/sdcard/calc-history.txt: open failed: ENOENT (No such file or directory)
The error above was fixed using filename = data.getData().getPath(); on the case FILE_FOUND instead of filename = data.getDataString();
Now i have another error on this line:
dataOutputStream.write(b, 0, n);
The error message is that: Transport endpoint is not connected, what i should do?
To browse your device use something like aFileChooser (there are a lot more than that. google it;). You can use the dataOutputStream/dataInputStream to send/receive the file as well like you already did with the message. Did you try the ACTION_SEND-Intent yet? It could work that way too.
The ProgressBar is an android widget which is ready to use, that should not be a problem.
If you need general info use the Android Developers Guide (you probably do already). Almost everything concerning android can be found there, you just have to look.
EDIT : (from comment)
FileInputStream fin = new FileInputStream(<filepath>);
DataOutputStream dOut = new DataOutputStream(out);
int n = 0;
byte[] buffer = new byte[1024 * 4];
while (-1 != (n = fin.read(buffer))) {
dOut.write(buffer, 0, n);
}
dOut.flush();
fin.colse();
dOut.close();
hope this is understandable.
To pick a file from gallery, I do this:
Intent i = new Intent(Intent.ACTION_PICK,android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
startActivityForResult(i, 1);
And to get the file path when you select it from gallery and share it on different apps(like bluetooth, gmail, whatsapp etc), I do this in onActivityResult:
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
try {
Uri selectedImage = data.getData();
Intent sendIntent = new Intent();
sendIntent.setType("file/*");
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_STREAM,
selectedImage);
// startActivity(Intent.createChooser(sendIntent));
startActivity(Intent.createChooser(sendIntent,
"Share file via:"));
}
catch(Exception e)
{
}
}