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