- adding possibility to create folder during upload
[pub/Android/ownCloud.git] / src / com / owncloud / android / ui / activity / Uploader.java
1 /* ownCloud Android client application
2 * Copyright (C) 2012 Bartek Przybylski
3 * Copyright (C) 2012-2013 ownCloud Inc.
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License version 2,
7 * as published by the Free Software Foundation.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 *
17 */
18
19 package com.owncloud.android.ui.activity;
20
21 import java.io.File;
22 import java.util.ArrayList;
23 import java.util.HashMap;
24 import java.util.LinkedList;
25 import java.util.List;
26 import java.util.Stack;
27 import java.util.Vector;
28
29 import android.accounts.Account;
30 import android.accounts.AccountManager;
31 import android.app.AlertDialog;
32 import android.app.AlertDialog.Builder;
33 import android.app.Dialog;
34 import android.app.ProgressDialog;
35 import android.content.Context;
36 import android.content.DialogInterface;
37 import android.content.DialogInterface.OnCancelListener;
38 import android.content.DialogInterface.OnClickListener;
39 import android.content.res.Resources.NotFoundException;
40 import android.content.Intent;
41 import android.database.Cursor;
42 import android.net.Uri;
43 import android.os.Bundle;
44 import android.os.Parcelable;
45 import android.provider.MediaStore.Audio;
46 import android.provider.MediaStore.Images;
47 import android.provider.MediaStore.Video;
48 import android.view.View;
49 import android.view.Window;
50 import android.widget.AdapterView;
51 import android.widget.AdapterView.OnItemClickListener;
52 import android.widget.Button;
53 import android.widget.EditText;
54 import android.widget.ListView;
55 import android.widget.SimpleAdapter;
56 import android.widget.Toast;
57
58 import com.owncloud.android.MainApp;
59 import com.owncloud.android.R;
60 import com.owncloud.android.authentication.AccountAuthenticator;
61 import com.owncloud.android.datamodel.FileDataStorageManager;
62 import com.owncloud.android.datamodel.OCFile;
63 import com.owncloud.android.files.services.FileUploader;
64 import com.owncloud.android.lib.common.operations.RemoteOperation;
65 import com.owncloud.android.lib.common.operations.RemoteOperationResult;
66 import com.owncloud.android.lib.common.utils.Log_OC;
67 import com.owncloud.android.operations.CreateFolderOperation;
68 import com.owncloud.android.operations.CreateShareOperation;
69 import com.owncloud.android.operations.MoveFileOperation;
70 import com.owncloud.android.operations.RemoveFileOperation;
71 import com.owncloud.android.operations.RenameFileOperation;
72 import com.owncloud.android.operations.SynchronizeFileOperation;
73 import com.owncloud.android.operations.UnshareLinkOperation;
74 import com.owncloud.android.ui.dialog.CreateFolderDialogFragment;
75 import com.owncloud.android.utils.ErrorMessageAdapter;
76
77
78 /**
79 * This can be used to upload things to an ownCloud instance.
80 *
81 * @author Bartek Przybylski
82 *
83 */
84 public class Uploader extends FileActivity implements OnItemClickListener, android.view.View.OnClickListener {
85 private static final String TAG = "ownCloudUploader";
86
87 private Account mAccount;
88 private AccountManager mAccountManager;
89 private Stack<String> mParents;
90 private ArrayList<Parcelable> mStreamsToUpload;
91 private boolean mCreateDir;
92 private String mUploadPath;
93 private FileDataStorageManager mStorageManager;
94 private OCFile mFile;
95
96 private final static int DIALOG_NO_ACCOUNT = 0;
97 private final static int DIALOG_WAITING = 1;
98 private final static int DIALOG_NO_STREAM = 2;
99 private final static int DIALOG_MULTIPLE_ACCOUNT = 3;
100
101 private final static int REQUEST_CODE_SETUP_ACCOUNT = 0;
102
103 @Override
104 protected void onCreate(Bundle savedInstanceState) {
105 super.onCreate(savedInstanceState);
106 getWindow().requestFeature(Window.FEATURE_NO_TITLE);
107 mParents = new Stack<String>();
108 mParents.add("");
109 if (prepareStreamsToUpload()) {
110 mAccountManager = (AccountManager) getSystemService(Context.ACCOUNT_SERVICE);
111 Account[] accounts = mAccountManager.getAccountsByType(MainApp.getAccountType());
112 if (accounts.length == 0) {
113 Log_OC.i(TAG, "No ownCloud account is available");
114 showDialog(DIALOG_NO_ACCOUNT);
115 } else if (accounts.length > 1) {
116 Log_OC.i(TAG, "More then one ownCloud is available");
117 showDialog(DIALOG_MULTIPLE_ACCOUNT);
118 } else {
119 mAccount = accounts[0];
120 mStorageManager = new FileDataStorageManager(mAccount, getContentResolver());
121 populateDirectoryList();
122 }
123 } else {
124 showDialog(DIALOG_NO_STREAM);
125 }
126 }
127
128 @Override
129 protected Dialog onCreateDialog(final int id) {
130 final AlertDialog.Builder builder = new Builder(this);
131 switch (id) {
132 case DIALOG_WAITING:
133 ProgressDialog pDialog = new ProgressDialog(this);
134 pDialog.setIndeterminate(false);
135 pDialog.setCancelable(false);
136 pDialog.setMessage(getResources().getString(R.string.uploader_info_uploading));
137 return pDialog;
138 case DIALOG_NO_ACCOUNT:
139 builder.setIcon(android.R.drawable.ic_dialog_alert);
140 builder.setTitle(R.string.uploader_wrn_no_account_title);
141 builder.setMessage(String.format(getString(R.string.uploader_wrn_no_account_text), getString(R.string.app_name)));
142 builder.setCancelable(false);
143 builder.setPositiveButton(R.string.uploader_wrn_no_account_setup_btn_text, new OnClickListener() {
144 @Override
145 public void onClick(DialogInterface dialog, int which) {
146 if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.ECLAIR_MR1) {
147 // using string value since in API7 this
148 // constatn is not defined
149 // in API7 < this constatant is defined in
150 // Settings.ADD_ACCOUNT_SETTINGS
151 // and Settings.EXTRA_AUTHORITIES
152 Intent intent = new Intent(android.provider.Settings.ACTION_ADD_ACCOUNT);
153 intent.putExtra("authorities", new String[] { MainApp.getAuthTokenType() });
154 startActivityForResult(intent, REQUEST_CODE_SETUP_ACCOUNT);
155 } else {
156 // since in API7 there is no direct call for
157 // account setup, so we need to
158 // show our own AccountSetupAcricity, get
159 // desired results and setup
160 // everything for ourself
161 Intent intent = new Intent(getBaseContext(), AccountAuthenticator.class);
162 startActivityForResult(intent, REQUEST_CODE_SETUP_ACCOUNT);
163 }
164 }
165 });
166 builder.setNegativeButton(R.string.uploader_wrn_no_account_quit_btn_text, new OnClickListener() {
167 @Override
168 public void onClick(DialogInterface dialog, int which) {
169 finish();
170 }
171 });
172 return builder.create();
173 case DIALOG_MULTIPLE_ACCOUNT:
174 CharSequence ac[] = new CharSequence[mAccountManager.getAccountsByType(MainApp.getAccountType()).length];
175 for (int i = 0; i < ac.length; ++i) {
176 ac[i] = mAccountManager.getAccountsByType(MainApp.getAccountType())[i].name;
177 }
178 builder.setTitle(R.string.common_choose_account);
179 builder.setItems(ac, new OnClickListener() {
180 @Override
181 public void onClick(DialogInterface dialog, int which) {
182 mAccount = mAccountManager.getAccountsByType(MainApp.getAccountType())[which];
183 mStorageManager = new FileDataStorageManager(mAccount, getContentResolver());
184 populateDirectoryList();
185 }
186 });
187 builder.setCancelable(true);
188 builder.setOnCancelListener(new OnCancelListener() {
189 @Override
190 public void onCancel(DialogInterface dialog) {
191 dialog.cancel();
192 finish();
193 }
194 });
195 return builder.create();
196 case DIALOG_NO_STREAM:
197 builder.setIcon(android.R.drawable.ic_dialog_alert);
198 builder.setTitle(R.string.uploader_wrn_no_content_title);
199 builder.setMessage(R.string.uploader_wrn_no_content_text);
200 builder.setCancelable(false);
201 builder.setNegativeButton(R.string.common_cancel, new OnClickListener() {
202 @Override
203 public void onClick(DialogInterface dialog, int which) {
204 finish();
205 }
206 });
207 return builder.create();
208 default:
209 throw new IllegalArgumentException("Unknown dialog id: " + id);
210 }
211 }
212
213 class a implements OnClickListener {
214 String mPath;
215 EditText mDirname;
216
217 public a(String path, EditText dirname) {
218 mPath = path;
219 mDirname = dirname;
220 }
221
222 @Override
223 public void onClick(DialogInterface dialog, int which) {
224 Uploader.this.mUploadPath = mPath + mDirname.getText().toString();
225 Uploader.this.mCreateDir = true;
226 uploadFiles();
227 }
228 }
229
230 @Override
231 public void onBackPressed() {
232
233 if (mParents.size() <= 1) {
234 super.onBackPressed();
235 return;
236 } else {
237 mParents.pop();
238 populateDirectoryList();
239 }
240 }
241
242 @Override
243 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
244 // click on folder in the list
245 Log_OC.d(TAG, "on item click");
246 Vector<OCFile> tmpfiles = mStorageManager.getFolderContent(mFile);
247 if (tmpfiles.size() <= 0) return;
248 // filter on dirtype
249 Vector<OCFile> files = new Vector<OCFile>();
250 for (OCFile f : tmpfiles)
251 if (f.isFolder())
252 files.add(f);
253 if (files.size() < position) {
254 throw new IndexOutOfBoundsException("Incorrect item selected");
255 }
256 mParents.push(files.get(position).getFileName());
257 populateDirectoryList();
258 }
259
260 @Override
261 public void onClick(View v) {
262 // click on button
263 switch (v.getId()) {
264 case R.id.uploader_choose_folder:
265 mUploadPath = ""; // first element in mParents is root dir, represented by ""; init mUploadPath with "/" results in a "//" prefix
266 for (String p : mParents)
267 mUploadPath += p + OCFile.PATH_SEPARATOR;
268 Log_OC.d(TAG, "Uploading file to dir " + mUploadPath);
269
270 uploadFiles();
271
272 break;
273
274 case R.id.uploader_new_folder:
275 CreateFolderDialogFragment dialog = CreateFolderDialogFragment.newInstance(mFile);
276 dialog.show(getSupportFragmentManager(), "createdirdialog");
277 break;
278
279
280 default:
281 throw new IllegalArgumentException("Wrong element clicked");
282 }
283 }
284
285 @Override
286 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
287 super.onActivityResult(requestCode, resultCode, data);
288 Log_OC.i(TAG, "result received. req: " + requestCode + " res: " + resultCode);
289 if (requestCode == REQUEST_CODE_SETUP_ACCOUNT) {
290 dismissDialog(DIALOG_NO_ACCOUNT);
291 if (resultCode == RESULT_CANCELED) {
292 finish();
293 }
294 Account[] accounts = mAccountManager.getAccountsByType(MainApp.getAuthTokenType());
295 if (accounts.length == 0) {
296 showDialog(DIALOG_NO_ACCOUNT);
297 } else {
298 // there is no need for checking for is there more then one
299 // account at this point
300 // since account setup can set only one account at time
301 mAccount = accounts[0];
302 populateDirectoryList();
303 }
304 }
305 }
306
307 private void populateDirectoryList() {
308 setContentView(R.layout.uploader_layout);
309
310 ListView mListView = (ListView) findViewById(android.R.id.list);
311
312 String full_path = "";
313 for (String a : mParents)
314 full_path += a + "/";
315
316 Log_OC.d(TAG, "Populating view with content of : " + full_path);
317
318 mFile = mStorageManager.getFileByPath(full_path);
319 if (mFile != null) {
320 Vector<OCFile> files = mStorageManager.getFolderContent(mFile);
321 List<HashMap<String, Object>> data = new LinkedList<HashMap<String,Object>>();
322 for (OCFile f : files) {
323 HashMap<String, Object> h = new HashMap<String, Object>();
324 if (f.isFolder()) {
325 h.put("dirname", f.getFileName());
326 data.add(h);
327 }
328 }
329 SimpleAdapter sa = new SimpleAdapter(this,
330 data,
331 R.layout.uploader_list_item_layout,
332 new String[] {"dirname"},
333 new int[] {R.id.textView1});
334
335 mListView.setAdapter(sa);
336 Button btnChooseFolder = (Button) findViewById(R.id.uploader_choose_folder);
337 btnChooseFolder.setOnClickListener(this);
338
339 Button btnNewFolder = (Button) findViewById(R.id.uploader_new_folder);
340 btnNewFolder.setOnClickListener(this);
341
342 mListView.setOnItemClickListener(this);
343 }
344 }
345
346 private boolean prepareStreamsToUpload() {
347 if (getIntent().getAction().equals(Intent.ACTION_SEND)) {
348 mStreamsToUpload = new ArrayList<Parcelable>();
349 mStreamsToUpload.add(getIntent().getParcelableExtra(Intent.EXTRA_STREAM));
350 } else if (getIntent().getAction().equals(Intent.ACTION_SEND_MULTIPLE)) {
351 mStreamsToUpload = getIntent().getParcelableArrayListExtra(Intent.EXTRA_STREAM);
352 }
353 return (mStreamsToUpload != null && mStreamsToUpload.get(0) != null);
354 }
355
356 public void uploadFiles() {
357 try {
358
359 ArrayList<String> local = new ArrayList<String>();
360 ArrayList<String> remote = new ArrayList<String>();
361
362 // this checks the mimeType
363 for (Parcelable mStream : mStreamsToUpload) {
364
365 Uri uri = (Uri) mStream;
366 if (uri !=null) {
367 if (uri.getScheme().equals("content")) {
368
369 String mimeType = getContentResolver().getType(uri);
370
371 if (mimeType.contains("image")) {
372 String[] CONTENT_PROJECTION = { Images.Media.DATA, Images.Media.DISPLAY_NAME, Images.Media.MIME_TYPE, Images.Media.SIZE};
373 Cursor c = getContentResolver().query(uri, CONTENT_PROJECTION, null, null, null);
374 c.moveToFirst();
375 int index = c.getColumnIndex(Images.Media.DATA);
376 String data = c.getString(index);
377 local.add(data);
378 remote.add(mUploadPath + c.getString(c.getColumnIndex(Images.Media.DISPLAY_NAME)));
379
380 }
381 else if (mimeType.contains("video")) {
382 String[] CONTENT_PROJECTION = { Video.Media.DATA, Video.Media.DISPLAY_NAME, Video.Media.MIME_TYPE, Video.Media.SIZE, Video.Media.DATE_MODIFIED };
383 Cursor c = getContentResolver().query(uri, CONTENT_PROJECTION, null, null, null);
384 c.moveToFirst();
385 int index = c.getColumnIndex(Video.Media.DATA);
386 String data = c.getString(index);
387 local.add(data);
388 remote.add(mUploadPath + c.getString(c.getColumnIndex(Video.Media.DISPLAY_NAME)));
389
390 }
391 else if (mimeType.contains("audio")) {
392 String[] CONTENT_PROJECTION = { Audio.Media.DATA, Audio.Media.DISPLAY_NAME, Audio.Media.MIME_TYPE, Audio.Media.SIZE };
393 Cursor c = getContentResolver().query(uri, CONTENT_PROJECTION, null, null, null);
394 c.moveToFirst();
395 int index = c.getColumnIndex(Audio.Media.DATA);
396 String data = c.getString(index);
397 local.add(data);
398 remote.add(mUploadPath + c.getString(c.getColumnIndex(Audio.Media.DISPLAY_NAME)));
399
400 }
401 else {
402 String filePath = Uri.decode(uri.toString()).replace(uri.getScheme() + "://", "");
403 // cut everything whats before mnt. It occured to me that sometimes apps send their name into the URI
404 if (filePath.contains("mnt")) {
405 String splitedFilePath[] = filePath.split("/mnt");
406 filePath = splitedFilePath[1];
407 }
408 final File file = new File(filePath);
409 local.add(file.getAbsolutePath());
410 remote.add(mUploadPath + file.getName());
411 }
412
413 } else if (uri.getScheme().equals("file")) {
414 String filePath = Uri.decode(uri.toString()).replace(uri.getScheme() + "://", "");
415 if (filePath.contains("mnt")) {
416 String splitedFilePath[] = filePath.split("/mnt");
417 filePath = splitedFilePath[1];
418 }
419 final File file = new File(filePath);
420 local.add(file.getAbsolutePath());
421 remote.add(mUploadPath + file.getName());
422 }
423 else {
424 throw new SecurityException();
425 }
426 }
427 else {
428 throw new SecurityException();
429 }
430
431 Intent intent = new Intent(getApplicationContext(), FileUploader.class);
432 intent.putExtra(FileUploader.KEY_UPLOAD_TYPE, FileUploader.UPLOAD_MULTIPLE_FILES);
433 intent.putExtra(FileUploader.KEY_LOCAL_FILE, local.toArray(new String[local.size()]));
434 intent.putExtra(FileUploader.KEY_REMOTE_FILE, remote.toArray(new String[remote.size()]));
435 intent.putExtra(FileUploader.KEY_ACCOUNT, mAccount);
436 startService(intent);
437 finish();
438 }
439
440 } catch (SecurityException e) {
441 String message = String.format(getString(R.string.uploader_error_forbidden_content), getString(R.string.app_name));
442 Toast.makeText(this, message, Toast.LENGTH_LONG).show();
443 }
444 }
445
446 @Override
447 public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationResult result) {
448 super.onRemoteOperationFinish(operation, result);
449
450
451 if (operation instanceof CreateFolderOperation) {
452 onCreateFolderOperationFinish((CreateFolderOperation)operation, result);
453 }
454
455 }
456
457 /**
458 * Updates the view associated to the activity after the finish of an operation trying create a new folder
459 *
460 * @param operation Creation operation performed.
461 * @param result Result of the creation.
462 */
463 private void onCreateFolderOperationFinish(CreateFolderOperation operation, RemoteOperationResult result) {
464 if (result.isSuccess()) {
465 dismissLoadingDialog();
466 populateDirectoryList();
467 } else {
468 dismissLoadingDialog();
469 try {
470 Toast msg = Toast.makeText(this,
471 ErrorMessageAdapter.getErrorCauseMessage(result, operation, getResources()),
472 Toast.LENGTH_LONG);
473 msg.show();
474
475 } catch (NotFoundException e) {
476 Log_OC.e(TAG, "Error while trying to show fail message " , e);
477 }
478 }
479 }
480 }