Fixed 'Move' action
[pub/Android/ownCloud.git] / src / com / owncloud / android / ui / activity / CopyActivity.java
1 /* ownCloud Android client application
2 * Copyright (C) 2012-2014 ownCloud Inc.
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2,
6 * as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 */
17
18 package com.owncloud.android.ui.activity;
19
20 import android.accounts.Account;
21 import android.accounts.AccountManager;
22 import android.accounts.AuthenticatorException;
23 import android.accounts.OperationCanceledException;
24 import android.content.BroadcastReceiver;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.IntentFilter;
28 import android.content.res.Resources.NotFoundException;
29 import android.os.Bundle;
30 import android.support.v4.app.Fragment;
31 import android.support.v4.app.FragmentTransaction;
32 import android.support.v4.widget.SwipeRefreshLayout;
33 import android.support.v7.app.ActionBar;
34 import android.util.Log;
35 import android.view.Menu;
36 import android.view.MenuInflater;
37 import android.view.MenuItem;
38 import android.view.View;
39 import android.view.View.OnClickListener;
40 import android.view.Window;
41 import android.widget.Button;
42 import android.widget.Toast;
43
44 import com.owncloud.android.R;
45 import com.owncloud.android.datamodel.OCFile;
46 import com.owncloud.android.lib.common.OwnCloudAccount;
47 import com.owncloud.android.lib.common.OwnCloudClient;
48 import com.owncloud.android.lib.common.OwnCloudClientManagerFactory;
49 import com.owncloud.android.lib.common.OwnCloudCredentials;
50 import com.owncloud.android.lib.common.accounts.AccountUtils.AccountNotFoundException;
51 import com.owncloud.android.lib.common.operations.RemoteOperation;
52 import com.owncloud.android.lib.common.operations.RemoteOperationResult;
53 import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
54 import com.owncloud.android.lib.common.utils.Log_OC;
55 import com.owncloud.android.operations.CreateFolderOperation;
56 import com.owncloud.android.operations.RefreshFolderOperation;
57 import com.owncloud.android.operations.SynchronizeFolderOperation;
58 import com.owncloud.android.syncadapter.FileSyncAdapter;
59 import com.owncloud.android.ui.dialog.CreateFolderDialogFragment;
60 import com.owncloud.android.ui.fragment.FileFragment;
61 import com.owncloud.android.ui.fragment.OCFileListFragment;
62 import com.owncloud.android.utils.DisplayUtils;
63 import com.owncloud.android.utils.ErrorMessageAdapter;
64
65 import java.io.IOException;
66
67 public class CopyActivity extends HookActivity implements FileFragment.ContainerActivity,
68 OnClickListener, SwipeRefreshLayout.OnRefreshListener {
69
70 public static final String EXTRA_CURRENT_FOLDER = UploadFilesActivity.class.getCanonicalName() + ".EXTRA_CURRENT_FOLDER";
71 public static final String EXTRA_TARGET_FILE = UploadFilesActivity.class.getCanonicalName() + "EXTRA_TARGET_FILE";
72
73 public static final int RESULT_OK_AND_MOVE = 1;
74
75 private SyncBroadcastReceiver mSyncBroadcastReceiver;
76
77 private static final String TAG = CopyActivity.class.getSimpleName();
78
79 private static final String TAG_LIST_OF_FOLDERS = "LIST_OF_FOLDERS";
80
81 private boolean mSyncInProgress = false;
82
83 private Button mCancelBtn;
84 private Button mChooseBtn;
85
86
87 @Override
88 protected void onCreate(Bundle savedInstanceState) {
89 Log_OC.d(TAG, "onCreate() start");
90 requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
91
92 super.onCreate(savedInstanceState);
93
94 setContentView(R.layout.files_folder_picker);
95
96 if (savedInstanceState == null) {
97 createFragments();
98 }
99
100 // sets callback listeners for UI elements
101 initControls();
102
103 // Action bar setup
104 ActionBar actionBar = getSupportActionBar();
105 actionBar.setDisplayShowTitleEnabled(true);
106 actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
107 setSupportProgressBarIndeterminateVisibility(mSyncInProgress);
108 // always AFTER setContentView(...) ; to work around bug in its implementation
109
110 // sets message for empty list of folders
111 setBackgroundText();
112
113 Log_OC.d(TAG, "onCreate() end");
114
115 }
116
117 @Override
118 protected void onStart() {
119 super.onStart();
120 getSupportActionBar().setIcon(DisplayUtils.getSeasonalIconId());
121 }
122
123 @Override
124 protected void onDestroy() {
125 super.onDestroy();
126 }
127
128 /**
129 * Called when the ownCloud {@link Account} associated to the Activity was just updated.
130 */
131 @Override
132 protected void onAccountSet(boolean stateWasRecovered) {
133 super.onAccountSet(stateWasRecovered);
134 if (getAccount() != null) {
135
136 updateFileFromDB();
137
138 OCFile folder = getFile();
139 if (folder == null || !folder.isFolder()) {
140 // fall back to root folder
141 setFile(getStorageManager().getFileByPath(OCFile.ROOT_PATH));
142 folder = getFile();
143 }
144
145 if (!stateWasRecovered) {
146 OCFileListFragment listOfFolders = getListOfFilesFragment();
147 listOfFolders.listDirectory(folder);
148
149 startSyncFolderOperation(folder, false);
150 }
151
152 updateNavigationElementsInActionBar();
153 }
154 }
155
156 private void createFragments() {
157 OCFileListFragment listOfFiles = new OCFileListFragment();
158 Bundle args = new Bundle();
159 args.putBoolean(OCFileListFragment.ARG_JUST_FOLDERS, true);
160 args.putBoolean(OCFileListFragment.ARG_ALLOW_CONTEXTUAL_ACTIONS, false);
161 listOfFiles.setArguments(args);
162 FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
163 transaction.add(R.id.fragment_container, listOfFiles, TAG_LIST_OF_FOLDERS);
164 transaction.commit();
165 }
166
167 /**
168 * Show a text message on screen view for notifying user if content is
169 * loading or folder is empty
170 */
171 private void setBackgroundText() {
172 OCFileListFragment listFragment = getListOfFilesFragment();
173 if (listFragment != null) {
174 int message = R.string.file_list_loading;
175 if (!mSyncInProgress) {
176 // In case folder list is empty
177 message = R.string.file_list_empty_moving;
178 }
179 listFragment.setMessageForEmptyList(getString(message));
180 } else {
181 Log.e(TAG, "OCFileListFragment is null");
182 }
183 }
184
185 private OCFileListFragment getListOfFilesFragment() {
186 Fragment listOfFiles = getSupportFragmentManager().findFragmentByTag(CopyActivity.TAG_LIST_OF_FOLDERS);
187 if (listOfFiles != null) {
188 return (OCFileListFragment) listOfFiles;
189 }
190 Log_OC.wtf(TAG, "Access to unexisting list of files fragment!!");
191 return null;
192 }
193
194
195 /**
196 * {@inheritDoc}
197 * <p/>
198 * Updates action bar and second fragment, if in dual pane mode.
199 */
200 @Override
201 public void onBrowsedDownTo(OCFile directory) {
202 setFile(directory);
203 updateNavigationElementsInActionBar();
204 // Sync Folder
205 startSyncFolderOperation(directory, false);
206
207 }
208
209
210 public void startSyncFolderOperation(OCFile folder, boolean ignoreETag) {
211 long currentSyncTime = System.currentTimeMillis();
212
213 mSyncInProgress = true;
214
215 // perform folder synchronization
216 RemoteOperation synchFolderOp = new RefreshFolderOperation(folder,
217 currentSyncTime,
218 false,
219 getFileOperationsHelper().isSharedSupported(),
220 ignoreETag,
221 getStorageManager(),
222 getAccount(),
223 getApplicationContext()
224 );
225 synchFolderOp.execute(getAccount(), this, null, null);
226
227 setSupportProgressBarIndeterminateVisibility(true);
228
229 setBackgroundText();
230 }
231
232 @Override
233 protected void onResume() {
234 super.onResume();
235 Log_OC.e(TAG, "onResume() start");
236
237 // refresh list of files
238 refreshListOfFilesFragment();
239
240 // Listen for sync messages
241 IntentFilter syncIntentFilter = new IntentFilter(FileSyncAdapter.EVENT_FULL_SYNC_START);
242 syncIntentFilter.addAction(FileSyncAdapter.EVENT_FULL_SYNC_END);
243 syncIntentFilter.addAction(FileSyncAdapter.EVENT_FULL_SYNC_FOLDER_CONTENTS_SYNCED);
244 syncIntentFilter.addAction(RefreshFolderOperation.EVENT_SINGLE_FOLDER_CONTENTS_SYNCED);
245 syncIntentFilter.addAction(RefreshFolderOperation.EVENT_SINGLE_FOLDER_SHARES_SYNCED);
246 mSyncBroadcastReceiver = new SyncBroadcastReceiver();
247 registerReceiver(mSyncBroadcastReceiver, syncIntentFilter);
248
249 Log_OC.d(TAG, "onResume() end");
250 }
251
252 @Override
253 protected void onPause() {
254 Log_OC.e(TAG, "onPause() start");
255 if (mSyncBroadcastReceiver != null) {
256 unregisterReceiver(mSyncBroadcastReceiver);
257 //LocalBroadcastManager.getInstance(this).unregisterReceiver(mSyncBroadcastReceiver);
258 mSyncBroadcastReceiver = null;
259 }
260
261 Log_OC.d(TAG, "onPause() end");
262 super.onPause();
263 }
264
265 @Override
266 public boolean onCreateOptionsMenu(Menu menu) {
267 MenuInflater inflater = getMenuInflater();
268 inflater.inflate(R.menu.main_menu, menu);
269 menu.findItem(R.id.action_upload).setVisible(false);
270 menu.findItem(R.id.action_sync_account).setVisible(false);
271 return true;
272 }
273
274 @Override
275 public boolean onOptionsItemSelected(MenuItem item) {
276 boolean retval = true;
277 switch (item.getItemId()) {
278 case R.id.action_create_dir: {
279 CreateFolderDialogFragment dialog =
280 CreateFolderDialogFragment.newInstance(getCurrentFolder());
281 dialog.show(
282 getSupportFragmentManager(),
283 CreateFolderDialogFragment.CREATE_FOLDER_FRAGMENT
284 );
285 break;
286 }
287 case android.R.id.home: {
288 OCFile currentDir = getCurrentFolder();
289 if (currentDir != null && currentDir.getParentId() != 0) {
290 onBackPressed();
291 }
292 break;
293 }
294 default:
295 retval = super.onOptionsItemSelected(item);
296 }
297 return retval;
298 }
299
300 private OCFile getCurrentFolder() {
301 OCFile file = getFile();
302 if (file != null) {
303 if (file.isFolder()) {
304 return file;
305 } else if (getStorageManager() != null) {
306 String parentPath = file.getRemotePath().substring(0, file.getRemotePath().lastIndexOf(file.getFileName()));
307 return getStorageManager().getFileByPath(parentPath);
308 }
309 }
310 return null;
311 }
312
313 protected void refreshListOfFilesFragment() {
314 OCFileListFragment fileListFragment = getListOfFilesFragment();
315 if (fileListFragment != null) {
316 fileListFragment.listDirectory();
317 }
318 }
319
320 public void browseToRoot() {
321 OCFileListFragment listOfFiles = getListOfFilesFragment();
322 if (listOfFiles != null) { // should never be null, indeed
323 OCFile root = getStorageManager().getFileByPath(OCFile.ROOT_PATH);
324 listOfFiles.listDirectory(root);
325 setFile(listOfFiles.getCurrentFile());
326 updateNavigationElementsInActionBar();
327 startSyncFolderOperation(root, false);
328 }
329 }
330
331 @Override
332 public void onBackPressed() {
333 OCFileListFragment listOfFiles = getListOfFilesFragment();
334 if (listOfFiles != null) { // should never be null, indeed
335 int levelsUp = listOfFiles.onBrowseUp();
336 if (levelsUp == 0) {
337 finish();
338 return;
339 }
340 setFile(listOfFiles.getCurrentFile());
341 updateNavigationElementsInActionBar();
342 }
343 }
344
345 private void updateNavigationElementsInActionBar() {
346 ActionBar actionBar = getSupportActionBar();
347 OCFile currentDir = getCurrentFolder();
348 boolean atRoot = (currentDir == null || currentDir.getParentId() == 0);
349 actionBar.setDisplayHomeAsUpEnabled(!atRoot);
350 actionBar.setHomeButtonEnabled(!atRoot);
351 actionBar.setTitle(
352 atRoot
353 ? getString(R.string.default_display_name_for_root_folder)
354 : currentDir.getFileName()
355 );
356 }
357
358 /**
359 * Set per-view controllers
360 */
361 private void initControls() {
362 mCancelBtn = (Button) findViewById(R.id.folder_picker_btn_cancel);
363 mCancelBtn.setOnClickListener(this);
364 mChooseBtn = (Button) findViewById(R.id.folder_picker_btn_choose);
365 mChooseBtn.setOnClickListener(this);
366 }
367
368 @Override
369 public void onClick(View v) {
370 if (v == mCancelBtn) {
371 finish();
372 } else if (v == mChooseBtn) {
373 Intent i = getIntent();
374 OCFile targetFile = i.getParcelableExtra(FolderPickerActivity.EXTRA_FILE);
375
376 Intent data = new Intent();
377 data.putExtra(EXTRA_CURRENT_FOLDER, getCurrentFolder());
378 data.putExtra(EXTRA_TARGET_FILE, targetFile);
379 setResult(RESULT_OK_AND_MOVE, data);
380 finish();
381 }
382 }
383
384
385 @Override
386 public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationResult result) {
387 super.onRemoteOperationFinish(operation, result);
388
389 if (operation instanceof CreateFolderOperation) {
390 onCreateFolderOperationFinish((CreateFolderOperation) operation, result);
391
392 }
393 }
394
395
396 /**
397 * Updates the view associated to the activity after the finish of an operation trying
398 * to create a new folder.
399 *
400 * @param operation Creation operation performed.
401 * @param result Result of the creation.
402 */
403 private void onCreateFolderOperationFinish(
404 CreateFolderOperation operation, RemoteOperationResult result
405 ) {
406
407 if (result.isSuccess()) {
408 dismissLoadingDialog();
409 refreshListOfFilesFragment();
410 } else {
411 dismissLoadingDialog();
412 try {
413 Toast msg = Toast.makeText(CopyActivity.this,
414 ErrorMessageAdapter.getErrorCauseMessage(result, operation, getResources()),
415 Toast.LENGTH_LONG);
416 msg.show();
417
418 } catch (NotFoundException e) {
419 Log_OC.e(TAG, "Error while trying to show fail message ", e);
420 }
421 }
422 }
423
424
425 private class SyncBroadcastReceiver extends BroadcastReceiver {
426
427 /**
428 * {@link BroadcastReceiver} to enable syncing feedback in UI
429 */
430 @Override
431 public void onReceive(Context context, Intent intent) {
432 try {
433 String event = intent.getAction();
434 Log_OC.d(TAG, "Received broadcast " + event);
435 String accountName = intent.getStringExtra(FileSyncAdapter.EXTRA_ACCOUNT_NAME);
436 String synchFolderRemotePath = intent.getStringExtra(FileSyncAdapter.EXTRA_FOLDER_PATH);
437 RemoteOperationResult synchResult = (RemoteOperationResult) intent.getSerializableExtra(FileSyncAdapter.EXTRA_RESULT);
438 boolean sameAccount = (getAccount() != null && accountName.equals(getAccount().name) && getStorageManager() != null);
439
440 if (sameAccount) {
441
442 if (FileSyncAdapter.EVENT_FULL_SYNC_START.equals(event)) {
443 mSyncInProgress = true;
444
445 } else {
446 OCFile currentFile = (getFile() == null) ? null : getStorageManager().getFileByPath(getFile().getRemotePath());
447 OCFile currentDir = (getCurrentFolder() == null) ? null : getStorageManager().getFileByPath(getCurrentFolder().getRemotePath());
448
449 if (currentDir == null) {
450 // current folder was removed from the server
451 Toast.makeText(CopyActivity.this,
452 String.format(getString(R.string.sync_current_folder_was_removed), getCurrentFolder().getFileName()),
453 Toast.LENGTH_LONG)
454 .show();
455 browseToRoot();
456
457 } else {
458 if (currentFile == null && !getFile().isFolder()) {
459 // currently selected file was removed in the server, and now we know it
460 currentFile = currentDir;
461 }
462
463 if (synchFolderRemotePath != null && currentDir.getRemotePath().equals(synchFolderRemotePath)) {
464 OCFileListFragment fileListFragment = getListOfFilesFragment();
465 if (fileListFragment != null) {
466 fileListFragment.listDirectory(currentDir);
467 }
468 }
469 setFile(currentFile);
470 }
471
472 mSyncInProgress = (!FileSyncAdapter.EVENT_FULL_SYNC_END.equals(event) && !RefreshFolderOperation.EVENT_SINGLE_FOLDER_SHARES_SYNCED.equals(event));
473
474 if (RefreshFolderOperation.EVENT_SINGLE_FOLDER_CONTENTS_SYNCED.
475 equals(event) &&
476 /// TODO refactor and make common
477 synchResult != null && !synchResult.isSuccess() &&
478 (synchResult.getCode() == ResultCode.UNAUTHORIZED ||
479 synchResult.isIdPRedirection() ||
480 (synchResult.isException() && synchResult.getException()
481 instanceof AuthenticatorException))) {
482
483 try {
484 OwnCloudClient client;
485 OwnCloudAccount ocAccount =
486 new OwnCloudAccount(getAccount(), context);
487 client = (OwnCloudClientManagerFactory.getDefaultSingleton().
488 removeClientFor(ocAccount));
489
490 if (client != null) {
491 OwnCloudCredentials cred = client.getCredentials();
492 if (cred != null) {
493 AccountManager am = AccountManager.get(context);
494 if (cred.authTokenExpires()) {
495 am.invalidateAuthToken(
496 getAccount().type,
497 cred.getAuthToken()
498 );
499 } else {
500 am.clearPassword(getAccount());
501 }
502 }
503 }
504 requestCredentialsUpdate();
505
506 } catch (AccountNotFoundException e) {
507 Log_OC.e(TAG, "Account " + getAccount() + " was removed!", e);
508 }
509
510 }
511 }
512 removeStickyBroadcast(intent);
513 Log_OC.d(TAG, "Setting progress visibility to " + mSyncInProgress);
514 setSupportProgressBarIndeterminateVisibility(mSyncInProgress /*|| mRefreshSharesInProgress*/);
515
516 setBackgroundText();
517
518 }
519
520 } catch (RuntimeException e) {
521 // avoid app crashes after changing the serial id of RemoteOperationResult
522 // in owncloud library with broadcast notifications pending to process
523 removeStickyBroadcast(intent);
524 }
525 }
526 }
527
528
529 /**
530 * Shows the information of the {@link OCFile} received as a
531 * parameter in the second fragment.
532 *
533 * @param file {@link OCFile} whose details will be shown
534 */
535 @Override
536 public void showDetails(OCFile file) {
537
538 }
539
540 /**
541 * {@inheritDoc}
542 */
543 @Override
544 public void onTransferStateChanged(OCFile file, boolean downloading, boolean uploading) {
545
546 }
547
548
549 @Override
550 public void onRefresh() {
551 OCFileListFragment listOfFiles = getListOfFilesFragment();
552 if (listOfFiles != null) {
553 OCFile folder = listOfFiles.getCurrentFile();
554 if (folder != null) {
555 startSyncFolderOperation(folder, true);
556 }
557 }
558 }
559
560 }