1 package eu
.alefzero
.owncloud
;
5 import java
.sql
.Timestamp
;
6 import java
.util
.ArrayList
;
7 import java
.util
.Stack
;
9 import org
.apache
.http
.HttpHost
;
10 import org
.apache
.http
.auth
.AuthScope
;
11 import org
.apache
.http
.auth
.UsernamePasswordCredentials
;
12 import org
.apache
.http
.client
.methods
.HttpPut
;
13 import org
.apache
.http
.entity
.FileEntity
;
14 import org
.apache
.http
.entity
.mime
.MultipartEntity
;
15 import org
.apache
.http
.entity
.mime
.content
.FileBody
;
16 import org
.apache
.http
.impl
.auth
.BasicScheme
;
17 import org
.apache
.http
.impl
.client
.DefaultHttpClient
;
18 import org
.apache
.http
.protocol
.BasicHttpContext
;
20 import android
.accounts
.Account
;
21 import android
.accounts
.AccountManager
;
22 import android
.app
.AlertDialog
;
23 import android
.app
.Dialog
;
24 import android
.app
.ListActivity
;
25 import android
.app
.ProgressDialog
;
26 import android
.app
.AlertDialog
.Builder
;
27 import android
.content
.ContentResolver
;
28 import android
.content
.ContentValues
;
29 import android
.content
.Context
;
30 import android
.content
.DialogInterface
;
31 import android
.content
.Intent
;
32 import android
.content
.DialogInterface
.OnCancelListener
;
33 import android
.content
.DialogInterface
.OnClickListener
;
34 import android
.database
.Cursor
;
35 import android
.net
.Uri
;
36 import android
.os
.Bundle
;
37 import android
.os
.Handler
;
38 import android
.os
.Parcelable
;
39 import android
.provider
.MediaStore
.Images
.Media
;
40 import android
.util
.Log
;
41 import android
.view
.View
;
42 import android
.view
.Window
;
43 import android
.view
.ViewGroup
.LayoutParams
;
44 import android
.widget
.AdapterView
;
45 import android
.widget
.Button
;
46 import android
.widget
.EditText
;
47 import android
.widget
.LinearLayout
;
48 import android
.widget
.ListView
;
49 import android
.widget
.SimpleCursorAdapter
;
50 import android
.widget
.Toast
;
51 import android
.widget
.AdapterView
.OnItemClickListener
;
52 import eu
.alefzero
.owncloud
.authenticator
.AccountAuthenticator
;
53 import eu
.alefzero
.owncloud
.db
.ProviderMeta
;
54 import eu
.alefzero
.owncloud
.db
.ProviderMeta
.ProviderTableMeta
;
55 import eu
.alefzero
.webdav
.HttpMkCol
;
56 import eu
.alefzero
.webdav
.WebdavUtils
;
58 public class OwnCloudUploader
extends ListActivity
implements OnItemClickListener
, android
.view
.View
.OnClickListener
{
59 private static final String TAG
= "ownCloudUploader";
61 private Account mAccount
;
62 private AccountManager mAccountManager
;
63 private String mUsername
, mPassword
;
64 private Cursor mCursor
;
65 private Stack
<String
> mParents
;
66 private Thread mUploadThread
;
67 private Handler mHandler
;
68 private ArrayList
<Parcelable
> mStreamsToUpload
;
70 private final static int DIALOG_NO_ACCOUNT
= 0;
71 private final static int DIALOG_WAITING
= 1;
72 private final static int DIALOG_NO_STREAM
= 2;
73 private final static int DIALOG_MULTIPLE_ACCOUNT
= 3;
74 private final static int DIALOG_GET_DIRNAME
= 4;
76 private final static int REQUEST_CODE_SETUP_ACCOUNT
= 0;
79 protected void onCreate(Bundle savedInstanceState
) {
80 super.onCreate(savedInstanceState
);
81 getWindow().requestFeature(Window
.FEATURE_NO_TITLE
);
82 mParents
= new Stack
<String
>();
83 mHandler
= new Handler();
84 if (getIntent().hasExtra(Intent
.EXTRA_STREAM
)) {
85 prepareStreamsToUpload();
86 mAccountManager
= (AccountManager
)getSystemService(Context
.ACCOUNT_SERVICE
);
87 Account
[] accounts
= mAccountManager
.getAccountsByType(AccountAuthenticator
.ACCOUNT_TYPE
);
88 if (accounts
.length
== 0) {
89 Log
.i(TAG
, "No ownCloud account is available");
90 showDialog(DIALOG_NO_ACCOUNT
);
91 } else if (accounts
.length
> 1) {
92 Log
.i(TAG
, "More then one ownCloud is available");
93 showDialog(DIALOG_MULTIPLE_ACCOUNT
);
95 mAccount
= accounts
[0];
96 setContentView(R
.layout
.uploader_layout
);
97 populateDirectoryList();
100 showDialog(DIALOG_NO_STREAM
);
105 protected Dialog
onCreateDialog(final int id
) {
106 final AlertDialog
.Builder builder
= new Builder(this);
109 ProgressDialog pDialog
= new ProgressDialog(this);
110 pDialog
.setIndeterminate(false
);
111 pDialog
.setCancelable(false
);
112 pDialog
.setMessage(getResources().getString(R
.string
.uploader_info_uploading
));
114 case DIALOG_NO_ACCOUNT
:
115 builder
.setIcon(android
.R
.drawable
.ic_dialog_alert
);
116 builder
.setTitle(R
.string
.uploader_wrn_no_account_title
);
117 builder
.setMessage(R
.string
.uploader_wrn_no_account_text
);
118 builder
.setCancelable(false
);
119 builder
.setPositiveButton(R
.string
.uploader_wrn_no_account_setup_btn_text
, new OnClickListener() {
120 public void onClick(DialogInterface dialog
, int which
) {
121 if (android
.os
.Build
.VERSION
.SDK_INT
> android
.os
.Build
.VERSION_CODES
.ECLAIR_MR1
) {
122 // using string value since in API7 this constatn is not defined
123 // in API7 < this constatant is defined in Settings.ADD_ACCOUNT_SETTINGS
124 // and Settings.EXTRA_AUTHORITIES
125 Intent intent
= new Intent("android.settings.ADD_ACCOUNT_SETTINGS");
126 intent
.putExtra("authorities", new String
[] {AccountAuthenticator
.AUTH_TOKEN_TYPE
});
127 startActivityForResult(intent
, REQUEST_CODE_SETUP_ACCOUNT
);
129 // since in API7 there is no direct call for account setup, so we need to
130 // show our own AccountSetupAcricity, get desired results and setup
131 // everything for ourself
132 Intent intent
= new Intent(getBaseContext(), AccountAuthenticator
.class);
133 startActivityForResult(intent
, REQUEST_CODE_SETUP_ACCOUNT
);
137 builder
.setNegativeButton(R
.string
.uploader_wrn_no_account_quit_btn_text
, new OnClickListener() {
138 public void onClick(DialogInterface dialog
, int which
) {
142 return builder
.create();
143 case DIALOG_GET_DIRNAME
:
144 final EditText dirName
= new EditText(getBaseContext());
145 builder
.setView(dirName
);
146 builder
.setTitle(R
.string
.uploader_info_dirname
);
148 if (mParents
.empty()) {
151 mCursor
= managedQuery(Uri
.withAppendedPath(ProviderTableMeta
.CONTENT_URI_FILE
, mParents
.peek()),
156 mCursor
.moveToFirst();
157 pathToUpload
= mCursor
.getString(mCursor
.getColumnIndex(ProviderTableMeta
.FILE_PATH
)) +
158 mCursor
.getString(mCursor
.getColumnIndex(ProviderTableMeta
.FILE_NAME
)).replace(" ", "%20");
160 a a
= new a(pathToUpload
, dirName
);
161 builder
.setPositiveButton(R
.string
.common_ok
, a
);
162 builder
.setNegativeButton(R
.string
.common_cancel
, new OnClickListener() {
163 public void onClick(DialogInterface dialog
, int which
) {
167 return builder
.create();
168 case DIALOG_MULTIPLE_ACCOUNT
:
169 CharSequence ac
[] = new CharSequence
[mAccountManager
.getAccountsByType(AccountAuthenticator
.ACCOUNT_TYPE
).length
];
170 for (int i
= 0; i
< ac
.length
; ++i
) {
171 ac
[i
] = mAccountManager
.getAccountsByType(AccountAuthenticator
.ACCOUNT_TYPE
)[i
].name
;
173 builder
.setTitle(R
.string
.common_choose_account
);
174 builder
.setItems(ac
, new OnClickListener() {
175 public void onClick(DialogInterface dialog
, int which
) {
176 mAccount
= mAccountManager
.getAccountsByType(AccountAuthenticator
.ACCOUNT_TYPE
)[which
];
177 populateDirectoryList();
180 builder
.setCancelable(true
);
181 builder
.setOnCancelListener(new OnCancelListener() {
182 public void onCancel(DialogInterface dialog
) {
187 return builder
.create();
189 throw new IllegalArgumentException("Unknown dialog id: " + id
);
193 class a
implements OnClickListener
{
196 public a(String path
, EditText dirname
) {
197 mPath
= path
; mDirname
= dirname
;
199 public void onClick(DialogInterface dialog
, int which
) {
200 showDialog(DIALOG_WAITING
);
201 mUploadThread
= new Thread(new BackgroundUploader(mPath
+mDirname
.getText().toString(), mStreamsToUpload
, mHandler
, true
));
202 mUploadThread
.start();
207 public void onBackPressed() {
209 if (mParents
.size()==0) {
210 super.onBackPressed();
212 } else if (mParents
.size() == 1) {
214 mCursor
= managedQuery(ProviderTableMeta
.CONTENT_URI
,
216 ProviderTableMeta
.FILE_CONTENT_TYPE
+"=?",
221 mCursor
= managedQuery(Uri
.withAppendedPath(ProviderTableMeta
.CONTENT_URI_DIR
, mParents
.peek()),
223 ProviderTableMeta
.FILE_CONTENT_TYPE
+"=?",
228 SimpleCursorAdapter sca
= new SimpleCursorAdapter(this, R
.layout
.uploader_list_item_layout
,
230 new String
[]{ProviderTableMeta
.FILE_NAME
},
231 new int[]{R
.id
.textView1
});
235 public void onItemClick(AdapterView
<?
> parent
, View view
, int position
, long id
) {
236 if (!mCursor
.moveToPosition(position
)) {
237 throw new IndexOutOfBoundsException("Incorrect item selected");
239 String _id
= mCursor
.getString(mCursor
.getColumnIndex(ProviderTableMeta
._ID
));
243 mCursor
= managedQuery(Uri
.withAppendedPath(ProviderTableMeta
.CONTENT_URI_DIR
, _id
),
245 ProviderTableMeta
.FILE_CONTENT_TYPE
+"=?",
248 SimpleCursorAdapter sca
= new SimpleCursorAdapter(this, R
.layout
.uploader_list_item_layout
,
250 new String
[]{ProviderTableMeta
.FILE_NAME
},
251 new int[]{R
.id
.textView1
});
253 getListView().invalidate();
256 public void onClick(View v
) {
258 case R
.id
.uploader_choose_folder
:
259 String pathToUpload
= null
;
260 if (mParents
.empty()) {
263 mCursor
= managedQuery(Uri
.withAppendedPath(ProviderTableMeta
.CONTENT_URI_FILE
, mParents
.peek()),
268 mCursor
.moveToFirst();
269 pathToUpload
= mCursor
.getString(mCursor
.getColumnIndex(ProviderTableMeta
.FILE_PATH
)) +
270 mCursor
.getString(mCursor
.getColumnIndex(ProviderTableMeta
.FILE_NAME
)).replace(" ", "%20");
273 showDialog(DIALOG_WAITING
);
274 mUploadThread
= new Thread(new BackgroundUploader(pathToUpload
, mStreamsToUpload
, mHandler
));
275 mUploadThread
.start();
278 case android
.R
.id
.button1
: // dynamic action for create aditional dir
279 showDialog(DIALOG_GET_DIRNAME
);
282 throw new IllegalArgumentException("Wrong element clicked");
286 public void onUploadComplete(boolean uploadSucc
, String message
) {
287 dismissDialog(DIALOG_WAITING
);
288 Log
.i(TAG
, "UploadSucc: " + uploadSucc
+ " message: " + message
);
290 Toast
.makeText(this, getResources().getString(R
.string
.uploader_upload_succeed
), Toast
.LENGTH_SHORT
).show();
292 Toast
.makeText(this, getResources().getString(R
.string
.uploader_upload_failed
) + message
, Toast
.LENGTH_LONG
).show();
298 protected void onActivityResult(int requestCode
, int resultCode
, Intent data
) {
299 super.onActivityResult(requestCode
, resultCode
, data
);
300 Log
.i(TAG
, "result received. req: " + requestCode
+ " res: " + resultCode
);
301 if (requestCode
== REQUEST_CODE_SETUP_ACCOUNT
) {
302 dismissDialog(DIALOG_NO_ACCOUNT
);
303 if (resultCode
== RESULT_CANCELED
) {
306 Account
[] accounts
= mAccountManager
.getAccountsByType(AccountAuthenticator
.AUTH_TOKEN_TYPE
);
307 if (accounts
.length
== 0) {
308 showDialog(DIALOG_NO_ACCOUNT
);
310 // there is no need for checking for is there more then one account at this point
311 // since account setup can set only one account at time
312 mAccount
= accounts
[0];
313 populateDirectoryList();
318 private void populateDirectoryList() {
319 mUsername
= mAccount
.name
.substring(0, mAccount
.name
.indexOf('@'));
320 mPassword
= mAccountManager
.getPassword(mAccount
);
321 setContentView(R
.layout
.uploader_layout
);
322 mCursor
= managedQuery(ProviderMeta
.ProviderTableMeta
.CONTENT_URI
,
324 ProviderTableMeta
.FILE_CONTENT_TYPE
+"=? AND " + ProviderTableMeta
.FILE_ACCOUNT_OWNER
+ "=?",
325 new String
[]{"DIR", mAccount
.name
},
328 ListView lv
= getListView();
329 lv
.setOnItemClickListener(this);
330 SimpleCursorAdapter sca
= new SimpleCursorAdapter(this,
331 R
.layout
.uploader_list_item_layout
,
333 new String
[]{ProviderTableMeta
.FILE_NAME
},
334 new int[]{R
.id
.textView1
});
336 Button btn
= (Button
) findViewById(R
.id
.uploader_choose_folder
);
337 btn
.setOnClickListener(this);
338 // insert create new directory for multiple items uploading
339 if (getIntent().getAction().equals(Intent
.ACTION_SEND_MULTIPLE
)) {
340 Button createDirBtn
= new Button(this);
341 createDirBtn
.setId(android
.R
.id
.button1
);
342 createDirBtn
.setText(R
.string
.uploader_btn_create_dir_text
);
343 createDirBtn
.setOnClickListener(this);
344 ((LinearLayout
)findViewById(R
.id
.linearLayout1
)).addView(createDirBtn
, LayoutParams
.FILL_PARENT
, LayoutParams
.WRAP_CONTENT
);
348 private void prepareStreamsToUpload() {
349 if (getIntent().getAction().equals(Intent
.ACTION_SEND
)) {
350 mStreamsToUpload
= new ArrayList
<Parcelable
>();
351 mStreamsToUpload
.add(getIntent().getParcelableExtra(Intent
.EXTRA_STREAM
));
352 } else if (getIntent().getAction().equals(Intent
.ACTION_SEND_MULTIPLE
)) {
353 mStreamsToUpload
= getIntent().getParcelableArrayListExtra(Intent
.EXTRA_STREAM
);
355 // unknow action inserted
356 throw new IllegalArgumentException("Unknown action given: " + getIntent().getAction());
360 public void PartialupdateUpload(String fileLocalPath
, String filename
, String filepath
, String contentType
, String contentLength
) {
361 ContentValues cv
= new ContentValues();
362 cv
.put(ProviderTableMeta
.FILE_NAME
, filename
);
363 cv
.put(ProviderTableMeta
.FILE_PATH
, filepath
);
364 cv
.put(ProviderTableMeta
.FILE_STORAGE_PATH
, fileLocalPath
);
365 cv
.put(ProviderTableMeta
.FILE_MODIFIED
, WebdavUtils
.DISPLAY_DATE_FORMAT
.format(new java
.util
.Date()));
366 cv
.put(ProviderTableMeta
.FILE_CONTENT_TYPE
, contentType
);
367 cv
.put(ProviderTableMeta
.FILE_CONTENT_LENGTH
, contentLength
);
368 cv
.put(ProviderTableMeta
.FILE_ACCOUNT_OWNER
, mAccount
.name
);
369 Log
.d(TAG
, filename
+" ++ "+filepath
+" ++ " + contentLength
+ " ++ " + contentType
+ " ++ " + fileLocalPath
);
370 if (!mParents
.empty()) {
371 Cursor c
= managedQuery(Uri
.withAppendedPath(ProviderTableMeta
.CONTENT_URI_FILE
, mParents
.peek()),
377 cv
.put(ProviderTableMeta
.FILE_PARENT
, c
.getString(c
.getColumnIndex(ProviderTableMeta
._ID
)));
380 getContentResolver().insert(ProviderTableMeta
.CONTENT_URI_FILE
, cv
);
383 class BackgroundUploader
implements Runnable
{
384 private ArrayList
<Parcelable
> mUploadStreams
;
385 private Handler mHandler
;
386 private String mUploadPath
;
387 private boolean mCreateDir
;
389 public BackgroundUploader(String pathToUpload
, ArrayList
<Parcelable
> streamsToUpload
,
391 mUploadStreams
= streamsToUpload
;
393 mUploadPath
= pathToUpload
.replace(" ", "%20");
397 public BackgroundUploader(String pathToUpload
, ArrayList
<Parcelable
> streamsToUpload
,
398 Handler handler
, boolean createDir
) {
399 mUploadStreams
= streamsToUpload
;
401 mUploadPath
= pathToUpload
.replace(" ", "%20");
402 mCreateDir
= createDir
;
406 boolean any_failed
= false
;
407 DefaultHttpClient httpClient
= new DefaultHttpClient();
408 Uri uri
= Uri
.parse(mAccountManager
.getUserData(mAccount
,
409 AccountAuthenticator
.KEY_OC_URL
));
410 httpClient
.getCredentialsProvider().setCredentials(
411 new AuthScope(uri
.getHost(), (uri
.getPort() == -1) ?
80 : uri
413 new UsernamePasswordCredentials(mUsername
, mPassword
));
414 BasicHttpContext httpContext
= new BasicHttpContext();
415 BasicScheme basicAuth
= new BasicScheme();
416 httpContext
.setAttribute("preemptive-auth", basicAuth
);
417 HttpHost targetHost
= new HttpHost(uri
.getHost(), (uri
.getPort() == -1)
419 : uri
.getPort(), (uri
.getScheme() == "https") ?
"https" : "http");
421 // create last directory in path if nessesary
423 HttpMkCol method
= new HttpMkCol(uri
.toString() + mUploadPath
+ "/");
424 method
.setHeader("User-Agent", "Android-ownCloud");
426 httpClient
.execute(targetHost
, method
, httpContext
);
427 Log
.i(TAG
, "Creating dir completed");
428 } catch (final Exception e
) {
430 mHandler
.post(new Runnable() {
432 OwnCloudUploader
.this.onUploadComplete(false
, e
.getLocalizedMessage());
439 for (int i
= 0; i
< mUploadStreams
.size(); ++i
) {
440 final Cursor c
= getContentResolver().query((Uri
)mUploadStreams
.get(i
), null
, null
, null
, null
);
443 HttpPut method
= new HttpPut(uri
.toString() + mUploadPath
+ "/"
444 + c
.getString(c
.getColumnIndex(Media
.DISPLAY_NAME
)).replace(" ", "%20"));
445 method
.setHeader("Content-type", c
.getString(c
.getColumnIndex(Media
.MIME_TYPE
)));
446 method
.setHeader("User-Agent", "Android-ownCloud");
449 FileBody fb
= new FileBody(new File(c
.getString(c
.getColumnIndex(Media
.DATA
))), c
.getString(c
.getColumnIndex(Media
.MIME_TYPE
)));
450 MultipartEntity entity
= new MultipartEntity();
451 final FileEntity fileEntity
= new FileEntity(new File(c
.getString(c
.getColumnIndex(Media
.DATA
))),
452 c
.getString(c
.getColumnIndex(Media
.MIME_TYPE
)));
454 entity
.addPart(c
.getString(c
.getColumnIndex(Media
.DISPLAY_NAME
)).replace(" ", "%20"), fb
);
456 method
.setEntity(fileEntity
);
457 Log
.i(TAG
, "executing:" + method
.getRequestLine().toString());
459 httpClient
.execute(targetHost
, method
, httpContext
);
460 mHandler
.post(new Runnable() {
462 OwnCloudUploader
.this.PartialupdateUpload(c
.getString(c
.getColumnIndex(Media
.DATA
)),
463 c
.getString(c
.getColumnIndex(Media
.DISPLAY_NAME
)),
464 mUploadPath
+ (mUploadPath
.equals("/")?
"":"/"),
465 fileEntity
.getContentType().getValue(),
466 fileEntity
.getContentLength()+"");
469 Log
.i(TAG
, "Uploading, done");
471 } catch (final Exception e
) {
473 mHandler
.post(new Runnable() {
475 OwnCloudUploader
.this.onUploadComplete(false
, c
.getString(c
.getColumnIndex(Media
.DISPLAY_NAME
))+ " " + e
.getLocalizedMessage());
481 mHandler
.post(new Runnable() {
483 OwnCloudUploader
.this.onUploadComplete(true
, "Success");
487 Bundle bundle
= new Bundle();
488 bundle
.putBoolean(ContentResolver
.SYNC_EXTRAS_MANUAL
, true
);
489 //ContentResolver.requestSync(mAccount, AccountAuthenticator.AUTH_TOKEN_TYPE, bundle);