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