0a23cfa9f959d28520b2f1ffa8135871b914c493
[pub/Android/ownCloud.git] / src / com / owncloud / android / ui / activity / FolderPickerActivity.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 java.io.IOException;
21
22 import android.accounts.Account;
23 import android.accounts.AccountManager;
24 import android.accounts.AuthenticatorException;
25 import android.accounts.OperationCanceledException;
26 import android.content.BroadcastReceiver;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.IntentFilter;
30 import android.content.res.Resources.NotFoundException;
31 import android.os.Bundle;
32 import android.support.v4.app.Fragment;
33 import android.support.v4.app.FragmentTransaction;
34 import android.util.Log;
35 import android.view.View;
36 import android.view.View.OnClickListener;
37 import android.widget.Button;
38 import android.widget.Toast;
39
40 import com.actionbarsherlock.app.ActionBar;
41 import com.actionbarsherlock.view.Menu;
42 import com.actionbarsherlock.view.MenuInflater;
43 import com.actionbarsherlock.view.MenuItem;
44 import com.actionbarsherlock.view.Window;
45 import com.owncloud.android.R;
46 import com.owncloud.android.datamodel.OCFile;
47 import com.owncloud.android.lib.common.OwnCloudAccount;
48 import com.owncloud.android.lib.common.OwnCloudClient;
49 import com.owncloud.android.lib.common.OwnCloudClientManagerFactory;
50 import com.owncloud.android.lib.common.OwnCloudCredentials;
51 import com.owncloud.android.lib.common.accounts.AccountUtils.AccountNotFoundException;
52 import com.owncloud.android.lib.common.operations.RemoteOperation;
53 import com.owncloud.android.lib.common.operations.RemoteOperationResult;
54 import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
55 import com.owncloud.android.operations.CreateFolderOperation;
56 import com.owncloud.android.operations.SynchronizeFolderOperation;
57 import com.owncloud.android.syncadapter.FileSyncAdapter;
58 import com.owncloud.android.ui.dialog.CreateFolderDialogFragment;
59 import com.owncloud.android.ui.fragment.FileFragment;
60 import com.owncloud.android.ui.fragment.OCFileListFragment;
61 import com.owncloud.android.utils.DisplayUtils;
62 import com.owncloud.android.utils.ErrorMessageAdapter;
63 import com.owncloud.android.lib.common.utils.Log_OC;
64
65 public class FolderPickerActivity extends FileActivity implements FileFragment.ContainerActivity,
66 OnClickListener, OnEnforceableRefreshListener {
67
68 public static final String EXTRA_FOLDER = UploadFilesActivity.class.getCanonicalName()
69 + ".EXTRA_FOLDER";
70 public static final String EXTRA_FILE = UploadFilesActivity.class.getCanonicalName()
71 + ".EXTRA_FILE";
72 //TODO: Think something better
73
74 private SyncBroadcastReceiver mSyncBroadcastReceiver;
75
76 private static final String TAG = FolderPickerActivity.class.getSimpleName();
77
78 private static final String TAG_LIST_OF_FOLDERS = "LIST_OF_FOLDERS";
79
80 private boolean mSyncInProgress = false;
81
82 protected Button mCancelBtn;
83 protected Button mChooseBtn;
84
85
86 @Override
87 protected void onCreate(Bundle savedInstanceState) {
88 Log_OC.d(TAG, "onCreate() start");
89 requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
90
91 super.onCreate(savedInstanceState);
92
93 setContentView(R.layout.files_folder_picker);
94
95 if (savedInstanceState == null) {
96 createFragments();
97 }
98
99 // sets callback listeners for UI elements
100 initControls();
101
102 // Action bar setup
103 ActionBar actionBar = getSupportActionBar();
104 actionBar.setDisplayShowTitleEnabled(true);
105 actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
106 setSupportProgressBarIndeterminateVisibility(mSyncInProgress);
107 // always AFTER setContentView(...) ; to work around bug in its implementation
108
109 // sets message for empty list of folders
110 setBackgroundText();
111
112 Log_OC.d(TAG, "onCreate() end");
113
114 }
115
116 @Override
117 protected void onStart() {
118 super.onStart();
119 getSupportActionBar().setIcon(DisplayUtils.getSeasonalIconId());
120 }
121
122 /**
123 * Called when the ownCloud {@link Account} associated to the Activity was just updated.
124 */
125 @Override
126 protected void onAccountSet(boolean stateWasRecovered) {
127 super.onAccountSet(stateWasRecovered);
128 if (getAccount() != null) {
129
130 updateFileFromDB();
131
132 OCFile folder = getFile();
133 if (folder == null || !folder.isFolder()) {
134 // fall back to root folder
135 setFile(getStorageManager().getFileByPath(OCFile.ROOT_PATH));
136 folder = getFile();
137 }
138
139 if (!stateWasRecovered) {
140 OCFileListFragment listOfFolders = getListOfFilesFragment();
141 listOfFolders.listDirectory(folder);
142
143 startSyncFolderOperation(folder, false);
144 }
145
146 updateNavigationElementsInActionBar();
147 }
148 }
149
150 private void createFragments() {
151 OCFileListFragment listOfFiles = new OCFileListFragment();
152 Bundle args = new Bundle();
153 args.putBoolean(OCFileListFragment.ARG_JUST_FOLDERS, true);
154 args.putBoolean(OCFileListFragment.ARG_ALLOW_CONTEXTUAL_ACTIONS, false);
155 listOfFiles.setArguments(args);
156 FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
157 transaction.add(R.id.fragment_container, listOfFiles, TAG_LIST_OF_FOLDERS);
158 transaction.commit();
159 }
160
161 /**
162 * Show a text message on screen view for notifying user if content is
163 * loading or folder is empty
164 */
165 private void setBackgroundText() {
166 OCFileListFragment listFragment = getListOfFilesFragment();
167 if (listFragment != null) {
168 int message = R.string.file_list_loading;
169 if (!mSyncInProgress) {
170 // In case folder list is empty
171 message = R.string.file_list_empty_moving;
172 }
173 listFragment.setMessageForEmptyList(getString(message));
174 } else {
175 Log.e(TAG, "OCFileListFragment is null");
176 }
177 }
178
179 protected OCFileListFragment getListOfFilesFragment() {
180 Fragment listOfFiles = getSupportFragmentManager().findFragmentByTag(FolderPickerActivity.TAG_LIST_OF_FOLDERS);
181 if (listOfFiles != null) {
182 return (OCFileListFragment)listOfFiles;
183 }
184 Log_OC.wtf(TAG, "Access to unexisting list of files fragment!!");
185 return null;
186 }
187
188
189 /**
190 * {@inheritDoc}
191 *
192 * Updates action bar and second fragment, if in dual pane mode.
193 */
194 @Override
195 public void onBrowsedDownTo(OCFile directory) {
196 setFile(directory);
197 updateNavigationElementsInActionBar();
198 // Sync Folder
199 startSyncFolderOperation(directory, false);
200
201 }
202
203
204 public void startSyncFolderOperation(OCFile folder, boolean ignoreETag) {
205 long currentSyncTime = System.currentTimeMillis();
206
207 mSyncInProgress = true;
208
209 // perform folder synchronization
210 RemoteOperation synchFolderOp = new SynchronizeFolderOperation( folder,
211 currentSyncTime,
212 false,
213 getFileOperationsHelper().isSharedSupported(),
214 ignoreETag,
215 getStorageManager(),
216 getAccount(),
217 getApplicationContext()
218 );
219 synchFolderOp.execute(getAccount(), this, null, null);
220
221 setSupportProgressBarIndeterminateVisibility(true);
222
223 setBackgroundText();
224 }
225
226 @Override
227 protected void onResume() {
228 super.onResume();
229 Log_OC.e(TAG, "onResume() start");
230
231 // refresh list of files
232 refreshListOfFilesFragment();
233
234 // Listen for sync messages
235 IntentFilter syncIntentFilter = new IntentFilter(FileSyncAdapter.EVENT_FULL_SYNC_START);
236 syncIntentFilter.addAction(FileSyncAdapter.EVENT_FULL_SYNC_END);
237 syncIntentFilter.addAction(FileSyncAdapter.EVENT_FULL_SYNC_FOLDER_CONTENTS_SYNCED);
238 syncIntentFilter.addAction(SynchronizeFolderOperation.EVENT_SINGLE_FOLDER_CONTENTS_SYNCED);
239 syncIntentFilter.addAction(SynchronizeFolderOperation.EVENT_SINGLE_FOLDER_SHARES_SYNCED);
240 mSyncBroadcastReceiver = new SyncBroadcastReceiver();
241 registerReceiver(mSyncBroadcastReceiver, syncIntentFilter);
242
243 Log_OC.d(TAG, "onResume() end");
244 }
245
246 @Override
247 protected void onPause() {
248 Log_OC.e(TAG, "onPause() start");
249 if (mSyncBroadcastReceiver != null) {
250 unregisterReceiver(mSyncBroadcastReceiver);
251 //LocalBroadcastManager.getInstance(this).unregisterReceiver(mSyncBroadcastReceiver);
252 mSyncBroadcastReceiver = null;
253 }
254
255 Log_OC.d(TAG, "onPause() end");
256 super.onPause();
257 }
258
259 @Override
260 public boolean onCreateOptionsMenu(Menu menu) {
261 MenuInflater inflater = getSherlock().getMenuInflater();
262 inflater.inflate(R.menu.main_menu, menu);
263 menu.findItem(R.id.action_upload).setVisible(false);
264 menu.findItem(R.id.action_settings).setVisible(false);
265 menu.findItem(R.id.action_sync_account).setVisible(false);
266 menu.findItem(R.id.action_logger).setVisible(false);
267 menu.findItem(R.id.action_sort).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 protected 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 protected 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(FolderPickerActivity.EXTRA_FILE);
372
373 Intent data = new Intent();
374 data.putExtra(EXTRA_FOLDER, getCurrentFolder());
375 if (targetFile != null) {
376 data.putExtra(EXTRA_FILE, targetFile);
377 }
378 setResult(RESULT_OK, data);
379 finish();
380 }
381 }
382
383
384 @Override
385 public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationResult result) {
386 super.onRemoteOperationFinish(operation, result);
387
388 if (operation instanceof CreateFolderOperation) {
389 onCreateFolderOperationFinish((CreateFolderOperation)operation, result);
390
391 }
392 }
393
394
395 /**
396 * Updates the view associated to the activity after the finish of an operation trying
397 * to create a new folder.
398 *
399 * @param operation Creation operation performed.
400 * @param result Result of the creation.
401 */
402 private void onCreateFolderOperationFinish(
403 CreateFolderOperation operation, RemoteOperationResult result
404 ) {
405
406 if (result.isSuccess()) {
407 dismissLoadingDialog();
408 refreshListOfFilesFragment();
409 } else {
410 dismissLoadingDialog();
411 try {
412 Toast msg = Toast.makeText(FolderPickerActivity.this,
413 ErrorMessageAdapter.getErrorCauseMessage(result, operation, getResources()),
414 Toast.LENGTH_LONG);
415 msg.show();
416
417 } catch (NotFoundException e) {
418 Log_OC.e(TAG, "Error while trying to show fail message " , e);
419 }
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.
438 getSerializableExtra(FileSyncAdapter.EXTRA_RESULT);
439 boolean sameAccount = (getAccount() != null &&
440 accountName.equals(getAccount().name) && getStorageManager() != null);
441
442 if (sameAccount) {
443
444 if (FileSyncAdapter.EVENT_FULL_SYNC_START.equals(event)) {
445 mSyncInProgress = true;
446
447 } else {
448 OCFile currentFile = (getFile() == null) ? null :
449 getStorageManager().getFileByPath(getFile().getRemotePath());
450 OCFile currentDir = (getCurrentFolder() == null) ? null :
451 getStorageManager().getFileByPath(getCurrentFolder().getRemotePath());
452
453 if (currentDir == null) {
454 // current folder was removed from the server
455 Toast.makeText( FolderPickerActivity.this,
456 String.format(
457 getString(R.string.sync_current_folder_was_removed),
458 getCurrentFolder().getFileName()),
459 Toast.LENGTH_LONG)
460 .show();
461 browseToRoot();
462
463 } else {
464 if (currentFile == null && !getFile().isFolder()) {
465 // currently selected file was removed in the server, and now we know it
466 currentFile = currentDir;
467 }
468
469 if (synchFolderRemotePath != null && currentDir.getRemotePath().
470 equals(synchFolderRemotePath)) {
471 OCFileListFragment fileListFragment = getListOfFilesFragment();
472 if (fileListFragment != null) {
473 fileListFragment.listDirectory(currentDir);
474 }
475 }
476 setFile(currentFile);
477 }
478
479 mSyncInProgress = (!FileSyncAdapter.EVENT_FULL_SYNC_END.equals(event) &&
480 !SynchronizeFolderOperation.EVENT_SINGLE_FOLDER_SHARES_SYNCED.equals(event));
481
482 if (SynchronizeFolderOperation.EVENT_SINGLE_FOLDER_CONTENTS_SYNCED.
483 equals(event) &&
484 /// TODO refactor and make common
485 synchResult != null && !synchResult.isSuccess() &&
486 (synchResult.getCode() == ResultCode.UNAUTHORIZED ||
487 synchResult.isIdPRedirection() ||
488 (synchResult.isException() && synchResult.getException()
489 instanceof AuthenticatorException))) {
490
491 OwnCloudClient client = null;
492 try {
493 OwnCloudAccount ocAccount =
494 new OwnCloudAccount(getAccount(), context);
495 client = (OwnCloudClientManagerFactory.getDefaultSingleton().
496 removeClientFor(ocAccount));
497 // TODO get rid of these exceptions
498 } catch (AccountNotFoundException e) {
499 e.printStackTrace();
500 } catch (AuthenticatorException e) {
501 e.printStackTrace();
502 } catch (OperationCanceledException e) {
503 e.printStackTrace();
504 } catch (IOException e) {
505 e.printStackTrace();
506 }
507
508 if (client != null) {
509 OwnCloudCredentials cred = client.getCredentials();
510 if (cred != null) {
511 AccountManager am = AccountManager.get(context);
512 if (cred.authTokenExpires()) {
513 am.invalidateAuthToken(
514 getAccount().type,
515 cred.getAuthToken()
516 );
517 } else {
518 am.clearPassword(getAccount());
519 }
520 }
521 }
522
523 requestCredentialsUpdate();
524
525 }
526 }
527 removeStickyBroadcast(intent);
528 Log_OC.d(TAG, "Setting progress visibility to " + mSyncInProgress);
529 setSupportProgressBarIndeterminateVisibility(mSyncInProgress /*|| mRefreshSharesInProgress*/);
530
531 setBackgroundText();
532
533 }
534
535 } catch (RuntimeException e) {
536 // avoid app crashes after changing the serial id of RemoteOperationResult
537 // in owncloud library with broadcast notifications pending to process
538 removeStickyBroadcast(intent);
539 }
540 }
541 }
542
543
544
545 /**
546 * Shows the information of the {@link OCFile} received as a
547 * parameter in the second fragment.
548 *
549 * @param file {@link OCFile} whose details will be shown
550 */
551 @Override
552 public void showDetails(OCFile file) {
553
554 }
555
556 /**
557 * {@inheritDoc}
558 */
559 @Override
560 public void onTransferStateChanged(OCFile file, boolean downloading, boolean uploading) {
561
562 }
563
564 @Override
565 public void onRefresh() {
566 refreshList(true);
567 }
568
569 @Override
570 public void onRefresh(boolean enforced) {
571 refreshList(enforced);
572 }
573
574 private void refreshList(boolean ignoreETag) {
575 OCFileListFragment listOfFiles = getListOfFilesFragment();
576 if (listOfFiles != null) {
577 OCFile folder = listOfFiles.getCurrentFile();
578 if (folder != null) {
579 startSyncFolderOperation(folder, ignoreETag);
580 }
581 }
582 }
583 }