OC-2332: Isolate code to the upload a file to remove local dependences
[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 com.owncloud.android.MainApp;
30 import com.owncloud.android.R;
31 import com.owncloud.android.R.id;
32 import com.owncloud.android.R.layout;
33 import com.owncloud.android.R.string;
34 import com.owncloud.android.authentication.AccountAuthenticator;
35 import com.owncloud.android.datamodel.FileDataStorageManager;
36 import com.owncloud.android.datamodel.OCFile;
37 import com.owncloud.android.files.services.FileUploader;
38 import com.owncloud.android.utils.Log_OC;
39
40 import android.accounts.Account;
41 import android.accounts.AccountManager;
42 import android.app.AlertDialog;
43 import android.app.AlertDialog.Builder;
44 import android.app.Dialog;
45 import android.app.ListActivity;
46 import android.app.ProgressDialog;
47 import android.content.Context;
48 import android.content.DialogInterface;
49 import android.content.DialogInterface.OnCancelListener;
50 import android.content.DialogInterface.OnClickListener;
51 import android.content.Intent;
52 import android.database.Cursor;
53 import android.net.Uri;
54 import android.os.Bundle;
55 import android.os.Parcelable;
56 import android.provider.MediaStore.Audio;
57 import android.provider.MediaStore.Images;
58 import android.provider.MediaStore.Video;
59 import android.view.View;
60 import android.view.Window;
61 import android.widget.AdapterView;
62 import android.widget.AdapterView.OnItemClickListener;
63 import android.widget.Button;
64 import android.widget.EditText;
65 import android.widget.SimpleAdapter;
66 import android.widget.Toast;
67
68
69 /**
70 * This can be used to upload things to an ownCloud instance.
71 *
72 * @author Bartek Przybylski
73 *
74 */
75 public class Uploader extends ListActivity implements OnItemClickListener, android.view.View.OnClickListener {
76 private static final String TAG = "ownCloudUploader";
77
78 private Account mAccount;
79 private AccountManager mAccountManager;
80 private Stack<String> mParents;
81 private ArrayList<Parcelable> mStreamsToUpload;
82 private boolean mCreateDir;
83 private String mUploadPath;
84 private FileDataStorageManager mStorageManager;
85 private OCFile mFile;
86
87 private final static int DIALOG_NO_ACCOUNT = 0;
88 private final static int DIALOG_WAITING = 1;
89 private final static int DIALOG_NO_STREAM = 2;
90 private final static int DIALOG_MULTIPLE_ACCOUNT = 3;
91
92 private final static int REQUEST_CODE_SETUP_ACCOUNT = 0;
93
94 @Override
95 protected void onCreate(Bundle savedInstanceState) {
96 super.onCreate(savedInstanceState);
97 getWindow().requestFeature(Window.FEATURE_NO_TITLE);
98 mParents = new Stack<String>();
99 mParents.add("");
100 if (prepareStreamsToUpload()) {
101 mAccountManager = (AccountManager) getSystemService(Context.ACCOUNT_SERVICE);
102 Account[] accounts = mAccountManager.getAccountsByType(MainApp.getAccountType());
103 if (accounts.length == 0) {
104 Log_OC.i(TAG, "No ownCloud account is available");
105 showDialog(DIALOG_NO_ACCOUNT);
106 } else if (accounts.length > 1) {
107 Log_OC.i(TAG, "More then one ownCloud is available");
108 showDialog(DIALOG_MULTIPLE_ACCOUNT);
109 } else {
110 mAccount = accounts[0];
111 mStorageManager = new FileDataStorageManager(mAccount, getContentResolver());
112 populateDirectoryList();
113 }
114 } else {
115 showDialog(DIALOG_NO_STREAM);
116 }
117 }
118
119 @Override
120 protected Dialog onCreateDialog(final int id) {
121 final AlertDialog.Builder builder = new Builder(this);
122 switch (id) {
123 case DIALOG_WAITING:
124 ProgressDialog pDialog = new ProgressDialog(this);
125 pDialog.setIndeterminate(false);
126 pDialog.setCancelable(false);
127 pDialog.setMessage(getResources().getString(R.string.uploader_info_uploading));
128 return pDialog;
129 case DIALOG_NO_ACCOUNT:
130 builder.setIcon(android.R.drawable.ic_dialog_alert);
131 builder.setTitle(R.string.uploader_wrn_no_account_title);
132 builder.setMessage(String.format(getString(R.string.uploader_wrn_no_account_text), getString(R.string.app_name)));
133 builder.setCancelable(false);
134 builder.setPositiveButton(R.string.uploader_wrn_no_account_setup_btn_text, new OnClickListener() {
135 @Override
136 public void onClick(DialogInterface dialog, int which) {
137 if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.ECLAIR_MR1) {
138 // using string value since in API7 this
139 // constatn is not defined
140 // in API7 < this constatant is defined in
141 // Settings.ADD_ACCOUNT_SETTINGS
142 // and Settings.EXTRA_AUTHORITIES
143 Intent intent = new Intent(android.provider.Settings.ACTION_ADD_ACCOUNT);
144 intent.putExtra("authorities", new String[] { MainApp.getAuthTokenType() });
145 startActivityForResult(intent, REQUEST_CODE_SETUP_ACCOUNT);
146 } else {
147 // since in API7 there is no direct call for
148 // account setup, so we need to
149 // show our own AccountSetupAcricity, get
150 // desired results and setup
151 // everything for ourself
152 Intent intent = new Intent(getBaseContext(), AccountAuthenticator.class);
153 startActivityForResult(intent, REQUEST_CODE_SETUP_ACCOUNT);
154 }
155 }
156 });
157 builder.setNegativeButton(R.string.uploader_wrn_no_account_quit_btn_text, new OnClickListener() {
158 @Override
159 public void onClick(DialogInterface dialog, int which) {
160 finish();
161 }
162 });
163 return builder.create();
164 case DIALOG_MULTIPLE_ACCOUNT:
165 CharSequence ac[] = new CharSequence[mAccountManager.getAccountsByType(MainApp.getAccountType()).length];
166 for (int i = 0; i < ac.length; ++i) {
167 ac[i] = mAccountManager.getAccountsByType(MainApp.getAccountType())[i].name;
168 }
169 builder.setTitle(R.string.common_choose_account);
170 builder.setItems(ac, new OnClickListener() {
171 @Override
172 public void onClick(DialogInterface dialog, int which) {
173 mAccount = mAccountManager.getAccountsByType(MainApp.getAccountType())[which];
174 mStorageManager = new FileDataStorageManager(mAccount, getContentResolver());
175 populateDirectoryList();
176 }
177 });
178 builder.setCancelable(true);
179 builder.setOnCancelListener(new OnCancelListener() {
180 @Override
181 public void onCancel(DialogInterface dialog) {
182 dialog.cancel();
183 finish();
184 }
185 });
186 return builder.create();
187 case DIALOG_NO_STREAM:
188 builder.setIcon(android.R.drawable.ic_dialog_alert);
189 builder.setTitle(R.string.uploader_wrn_no_content_title);
190 builder.setMessage(R.string.uploader_wrn_no_content_text);
191 builder.setCancelable(false);
192 builder.setNegativeButton(R.string.common_cancel, new OnClickListener() {
193 @Override
194 public void onClick(DialogInterface dialog, int which) {
195 finish();
196 }
197 });
198 return builder.create();
199 default:
200 throw new IllegalArgumentException("Unknown dialog id: " + id);
201 }
202 }
203
204 class a implements OnClickListener {
205 String mPath;
206 EditText mDirname;
207
208 public a(String path, EditText dirname) {
209 mPath = path;
210 mDirname = dirname;
211 }
212
213 @Override
214 public void onClick(DialogInterface dialog, int which) {
215 Uploader.this.mUploadPath = mPath + mDirname.getText().toString();
216 Uploader.this.mCreateDir = true;
217 uploadFiles();
218 }
219 }
220
221 @Override
222 public void onBackPressed() {
223
224 if (mParents.size() <= 1) {
225 super.onBackPressed();
226 return;
227 } else {
228 mParents.pop();
229 populateDirectoryList();
230 }
231 }
232
233 @Override
234 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
235 // click on folder in the list
236 Log_OC.d(TAG, "on item click");
237 Vector<OCFile> tmpfiles = mStorageManager.getFolderContent(mFile);
238 if (tmpfiles.size() <= 0) return;
239 // filter on dirtype
240 Vector<OCFile> files = new Vector<OCFile>();
241 for (OCFile f : tmpfiles)
242 if (f.isFolder())
243 files.add(f);
244 if (files.size() < position) {
245 throw new IndexOutOfBoundsException("Incorrect item selected");
246 }
247 mParents.push(files.get(position).getFileName());
248 populateDirectoryList();
249 }
250
251 @Override
252 public void onClick(View v) {
253 // click on button
254 switch (v.getId()) {
255 case R.id.uploader_choose_folder:
256 mUploadPath = ""; // first element in mParents is root dir, represented by ""; init mUploadPath with "/" results in a "//" prefix
257 for (String p : mParents)
258 mUploadPath += p + OCFile.PATH_SEPARATOR;
259 Log_OC.d(TAG, "Uploading file to dir " + mUploadPath);
260
261 uploadFiles();
262
263 break;
264 default:
265 throw new IllegalArgumentException("Wrong element clicked");
266 }
267 }
268
269 @Override
270 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
271 super.onActivityResult(requestCode, resultCode, data);
272 Log_OC.i(TAG, "result received. req: " + requestCode + " res: " + resultCode);
273 if (requestCode == REQUEST_CODE_SETUP_ACCOUNT) {
274 dismissDialog(DIALOG_NO_ACCOUNT);
275 if (resultCode == RESULT_CANCELED) {
276 finish();
277 }
278 Account[] accounts = mAccountManager.getAccountsByType(MainApp.getAuthTokenType());
279 if (accounts.length == 0) {
280 showDialog(DIALOG_NO_ACCOUNT);
281 } else {
282 // there is no need for checking for is there more then one
283 // account at this point
284 // since account setup can set only one account at time
285 mAccount = accounts[0];
286 populateDirectoryList();
287 }
288 }
289 }
290
291 private void populateDirectoryList() {
292 setContentView(R.layout.uploader_layout);
293
294 String full_path = "";
295 for (String a : mParents)
296 full_path += a + "/";
297
298 Log_OC.d(TAG, "Populating view with content of : " + full_path);
299
300 mFile = mStorageManager.getFileByPath(full_path);
301 if (mFile != null) {
302 Vector<OCFile> files = mStorageManager.getFolderContent(mFile);
303 List<HashMap<String, Object>> data = new LinkedList<HashMap<String,Object>>();
304 for (OCFile f : files) {
305 HashMap<String, Object> h = new HashMap<String, Object>();
306 if (f.isFolder()) {
307 h.put("dirname", f.getFileName());
308 data.add(h);
309 }
310 }
311 SimpleAdapter sa = new SimpleAdapter(this,
312 data,
313 R.layout.uploader_list_item_layout,
314 new String[] {"dirname"},
315 new int[] {R.id.textView1});
316 setListAdapter(sa);
317 Button btn = (Button) findViewById(R.id.uploader_choose_folder);
318 btn.setOnClickListener(this);
319 getListView().setOnItemClickListener(this);
320 }
321 }
322
323 private boolean prepareStreamsToUpload() {
324 if (getIntent().getAction().equals(Intent.ACTION_SEND)) {
325 mStreamsToUpload = new ArrayList<Parcelable>();
326 mStreamsToUpload.add(getIntent().getParcelableExtra(Intent.EXTRA_STREAM));
327 } else if (getIntent().getAction().equals(Intent.ACTION_SEND_MULTIPLE)) {
328 mStreamsToUpload = getIntent().getParcelableArrayListExtra(Intent.EXTRA_STREAM);
329 }
330 return (mStreamsToUpload != null && mStreamsToUpload.get(0) != null);
331 }
332
333 public void uploadFiles() {
334 try {
335 //WebdavClient webdav = OwnCloudClientUtils.createOwnCloudClient(mAccount, getApplicationContext());
336
337 ArrayList<String> local = new ArrayList<String>();
338 ArrayList<String> remote = new ArrayList<String>();
339
340 /* TODO - mCreateDir can never be true at this moment; we will replace wdc.createDirectory by CreateFolderOperation when that is fixed
341 WebdavClient wdc = OwnCloudClientUtils.createOwnCloudClient(mAccount, getApplicationContext());
342 // create last directory in path if necessary
343 if (mCreateDir) {
344 wdc.createDirectory(mUploadPath);
345 }
346 */
347
348 // this checks the mimeType
349 for (Parcelable mStream : mStreamsToUpload) {
350
351 Uri uri = (Uri) mStream;
352 if (uri !=null) {
353 if (uri.getScheme().equals("content")) {
354
355 String mimeType = getContentResolver().getType(uri);
356
357 if (mimeType.contains("image")) {
358 String[] CONTENT_PROJECTION = { Images.Media.DATA, Images.Media.DISPLAY_NAME, Images.Media.MIME_TYPE, Images.Media.SIZE};
359 Cursor c = getContentResolver().query(uri, CONTENT_PROJECTION, null, null, null);
360 c.moveToFirst();
361 int index = c.getColumnIndex(Images.Media.DATA);
362 String data = c.getString(index);
363 local.add(data);
364 remote.add(mUploadPath + c.getString(c.getColumnIndex(Images.Media.DISPLAY_NAME)));
365
366 }
367 else if (mimeType.contains("video")) {
368 String[] CONTENT_PROJECTION = { Video.Media.DATA, Video.Media.DISPLAY_NAME, Video.Media.MIME_TYPE, Video.Media.SIZE, Video.Media.DATE_MODIFIED };
369 Cursor c = getContentResolver().query(uri, CONTENT_PROJECTION, null, null, null);
370 c.moveToFirst();
371 int index = c.getColumnIndex(Video.Media.DATA);
372 String data = c.getString(index);
373 local.add(data);
374 remote.add(mUploadPath + c.getString(c.getColumnIndex(Video.Media.DISPLAY_NAME)));
375
376 }
377 else if (mimeType.contains("audio")) {
378 String[] CONTENT_PROJECTION = { Audio.Media.DATA, Audio.Media.DISPLAY_NAME, Audio.Media.MIME_TYPE, Audio.Media.SIZE };
379 Cursor c = getContentResolver().query(uri, CONTENT_PROJECTION, null, null, null);
380 c.moveToFirst();
381 int index = c.getColumnIndex(Audio.Media.DATA);
382 String data = c.getString(index);
383 local.add(data);
384 remote.add(mUploadPath + c.getString(c.getColumnIndex(Audio.Media.DISPLAY_NAME)));
385
386 }
387 else {
388 String filePath = Uri.decode(uri.toString()).replace(uri.getScheme() + "://", "");
389 // cut everything whats before mnt. It occured to me that sometimes apps send their name into the URI
390 if (filePath.contains("mnt")) {
391 String splitedFilePath[] = filePath.split("/mnt");
392 filePath = splitedFilePath[1];
393 }
394 final File file = new File(filePath);
395 local.add(file.getAbsolutePath());
396 remote.add(mUploadPath + file.getName());
397 }
398
399 } else if (uri.getScheme().equals("file")) {
400 String filePath = Uri.decode(uri.toString()).replace(uri.getScheme() + "://", "");
401 if (filePath.contains("mnt")) {
402 String splitedFilePath[] = filePath.split("/mnt");
403 filePath = splitedFilePath[1];
404 }
405 final File file = new File(filePath);
406 local.add(file.getAbsolutePath());
407 remote.add(mUploadPath + file.getName());
408 }
409 else {
410 throw new SecurityException();
411 }
412 }
413 else {
414 throw new SecurityException();
415 }
416
417 Intent intent = new Intent(getApplicationContext(), FileUploader.class);
418 intent.putExtra(FileUploader.KEY_UPLOAD_TYPE, FileUploader.UPLOAD_MULTIPLE_FILES);
419 intent.putExtra(FileUploader.KEY_LOCAL_FILE, local.toArray(new String[local.size()]));
420 intent.putExtra(FileUploader.KEY_REMOTE_FILE, remote.toArray(new String[remote.size()]));
421 intent.putExtra(FileUploader.KEY_ACCOUNT, mAccount);
422 startService(intent);
423 finish();
424 }
425
426 } catch (SecurityException e) {
427 String message = String.format(getString(R.string.uploader_error_forbidden_content), getString(R.string.app_name));
428 Toast.makeText(this, message, Toast.LENGTH_LONG).show();
429 }
430 }
431
432 }