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