Merge branch 'develop' into navigation_drawer
[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.support.v7.app.ActionBar;
38 import android.util.Log;
39 import android.view.Menu;
40 import android.view.MenuInflater;
41 import android.view.MenuItem;
42 import android.view.View;
43 import android.view.View.OnClickListener;
44 import android.view.Window;
45 import android.widget.Button;
46 import android.widget.Toast;
47
48 import com.owncloud.android.R;
49 import com.owncloud.android.datamodel.OCFile;
50 import com.owncloud.android.lib.common.OwnCloudAccount;
51 import com.owncloud.android.lib.common.OwnCloudClient;
52 import com.owncloud.android.lib.common.OwnCloudClientManagerFactory;
53 import com.owncloud.android.lib.common.OwnCloudCredentials;
54 import com.owncloud.android.lib.common.accounts.AccountUtils.AccountNotFoundException;
55 import com.owncloud.android.lib.common.operations.RemoteOperation;
56 import com.owncloud.android.lib.common.operations.RemoteOperationResult;
57 import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
58 import com.owncloud.android.lib.common.utils.Log_OC;
59 import com.owncloud.android.operations.CreateFolderOperation;
60 import com.owncloud.android.operations.RefreshFolderOperation;
61 import com.owncloud.android.syncadapter.FileSyncAdapter;
62 import com.owncloud.android.ui.dialog.CreateFolderDialogFragment;
63 import com.owncloud.android.ui.fragment.FileFragment;
64 import com.owncloud.android.ui.fragment.OCFileListFragment;
65 import com.owncloud.android.utils.DisplayUtils;
66 import com.owncloud.android.utils.ErrorMessageAdapter;
67
68 public class FolderPickerActivity extends FileActivity implements FileFragment.ContainerActivity,
69 OnClickListener, OnEnforceableRefreshListener {
70
71 public static final String EXTRA_FOLDER = UploadFilesActivity.class.getCanonicalName()
72 + ".EXTRA_FOLDER";
73 public static final String EXTRA_FILE = UploadFilesActivity.class.getCanonicalName()
74 + ".EXTRA_FILE";
75 //TODO: Think something better
76
77 private SyncBroadcastReceiver mSyncBroadcastReceiver;
78
79 private static final String TAG = FolderPickerActivity.class.getSimpleName();
80
81 private static final String TAG_LIST_OF_FOLDERS = "LIST_OF_FOLDERS";
82
83 private boolean mSyncInProgress = false;
84
85 protected Button mCancelBtn;
86 protected Button mChooseBtn;
87
88
89 @Override
90 protected void onCreate(Bundle savedInstanceState) {
91 Log_OC.d(TAG, "onCreate() start");
92 requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
93
94 super.onCreate(savedInstanceState);
95
96 setContentView(R.layout.files_folder_picker);
97
98 if (savedInstanceState == null) {
99 createFragments();
100 }
101
102 // sets callback listeners for UI elements
103 initControls();
104
105 // Action bar setup
106 ActionBar actionBar = getSupportActionBar();
107 actionBar.setDisplayShowTitleEnabled(true);
108 actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
109 setSupportProgressBarIndeterminateVisibility(mSyncInProgress);
110 // always AFTER setContentView(...) ; to work around bug in its implementation
111
112 // sets message for empty list of folders
113 setBackgroundText();
114
115 Log_OC.d(TAG, "onCreate() end");
116
117 }
118
119 @Override
120 protected void onStart() {
121 super.onStart();
122 getSupportActionBar().setIcon(DisplayUtils.getSeasonalIconId());
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 protected OCFileListFragment getListOfFilesFragment() {
183 Fragment listOfFiles = getSupportFragmentManager().findFragmentByTag(FolderPickerActivity.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 *
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_settings).setVisible(false);
268 menu.findItem(R.id.action_sync_account).setVisible(false);
269 menu.findItem(R.id.action_logger).setVisible(false);
270 menu.findItem(R.id.action_sort).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 protected 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 protected 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 Parcelable targetFile = i.getParcelableExtra(FolderPickerActivity.EXTRA_FILE);
375
376 Intent data = new Intent();
377 data.putExtra(EXTRA_FOLDER, getCurrentFolder());
378 if (targetFile != null) {
379 data.putExtra(EXTRA_FILE, targetFile);
380 }
381 setResult(RESULT_OK, data);
382 finish();
383 }
384 }
385
386
387 @Override
388 public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationResult result) {
389 super.onRemoteOperationFinish(operation, result);
390
391 if (operation instanceof CreateFolderOperation) {
392 onCreateFolderOperationFinish((CreateFolderOperation)operation, result);
393
394 }
395 }
396
397
398 /**
399 * Updates the view associated to the activity after the finish of an operation trying
400 * to create a new folder.
401 *
402 * @param operation Creation operation performed.
403 * @param result Result of the creation.
404 */
405 private void onCreateFolderOperationFinish(
406 CreateFolderOperation operation, RemoteOperationResult result
407 ) {
408
409 if (result.isSuccess()) {
410 dismissLoadingDialog();
411 refreshListOfFilesFragment();
412 } else {
413 dismissLoadingDialog();
414 try {
415 Toast msg = Toast.makeText(FolderPickerActivity.this,
416 ErrorMessageAdapter.getErrorCauseMessage(result, operation, getResources()),
417 Toast.LENGTH_LONG);
418 msg.show();
419
420 } catch (NotFoundException e) {
421 Log_OC.e(TAG, "Error while trying to show fail message " , e);
422 }
423 }
424 }
425
426
427
428 private class SyncBroadcastReceiver extends BroadcastReceiver {
429
430 /**
431 * {@link BroadcastReceiver} to enable syncing feedback in UI
432 */
433 @Override
434 public void onReceive(Context context, Intent intent) {
435 try {
436 String event = intent.getAction();
437 Log_OC.d(TAG, "Received broadcast " + event);
438 String accountName = intent.getStringExtra(FileSyncAdapter.EXTRA_ACCOUNT_NAME);
439 String synchFolderRemotePath = intent.getStringExtra(FileSyncAdapter.EXTRA_FOLDER_PATH);
440 RemoteOperationResult synchResult = (RemoteOperationResult)intent.
441 getSerializableExtra(FileSyncAdapter.EXTRA_RESULT);
442 boolean sameAccount = (getAccount() != null &&
443 accountName.equals(getAccount().name) && getStorageManager() != null);
444
445 if (sameAccount) {
446
447 if (FileSyncAdapter.EVENT_FULL_SYNC_START.equals(event)) {
448 mSyncInProgress = true;
449
450 } else {
451 OCFile currentFile = (getFile() == null) ? null :
452 getStorageManager().getFileByPath(getFile().getRemotePath());
453 OCFile currentDir = (getCurrentFolder() == null) ? null :
454 getStorageManager().getFileByPath(getCurrentFolder().getRemotePath());
455
456 if (currentDir == null) {
457 // current folder was removed from the server
458 Toast.makeText( FolderPickerActivity.this,
459 String.format(
460 getString(R.string.sync_current_folder_was_removed),
461 getCurrentFolder().getFileName()),
462 Toast.LENGTH_LONG)
463 .show();
464 browseToRoot();
465
466 } else {
467 if (currentFile == null && !getFile().isFolder()) {
468 // currently selected file was removed in the server, and now we know it
469 currentFile = currentDir;
470 }
471
472 if (synchFolderRemotePath != null && currentDir.getRemotePath().
473 equals(synchFolderRemotePath)) {
474 OCFileListFragment fileListFragment = getListOfFilesFragment();
475 if (fileListFragment != null) {
476 fileListFragment.listDirectory(currentDir);
477 }
478 }
479 setFile(currentFile);
480 }
481
482 mSyncInProgress = (!FileSyncAdapter.EVENT_FULL_SYNC_END.equals(event) &&
483 !RefreshFolderOperation.EVENT_SINGLE_FOLDER_SHARES_SYNCED.equals(event));
484
485 if (RefreshFolderOperation.EVENT_SINGLE_FOLDER_CONTENTS_SYNCED.
486 equals(event) &&
487 /// TODO refactor and make common
488 synchResult != null && !synchResult.isSuccess() &&
489 (synchResult.getCode() == ResultCode.UNAUTHORIZED ||
490 synchResult.isIdPRedirection() ||
491 (synchResult.isException() && synchResult.getException()
492 instanceof AuthenticatorException))) {
493
494 try {
495 OwnCloudClient client;
496 OwnCloudAccount ocAccount =
497 new OwnCloudAccount(getAccount(), context);
498 client = (OwnCloudClientManagerFactory.getDefaultSingleton().
499 removeClientFor(ocAccount));
500
501 if (client != null) {
502 OwnCloudCredentials cred = client.getCredentials();
503 if (cred != null) {
504 AccountManager am = AccountManager.get(context);
505 if (cred.authTokenExpires()) {
506 am.invalidateAuthToken(
507 getAccount().type,
508 cred.getAuthToken()
509 );
510 } else {
511 am.clearPassword(getAccount());
512 }
513 }
514 }
515 requestCredentialsUpdate();
516
517 } catch (AccountNotFoundException e) {
518 Log_OC.e(TAG, "Account " + getAccount() + " was removed!", e);
519 }
520
521 }
522 }
523 removeStickyBroadcast(intent);
524 Log_OC.d(TAG, "Setting progress visibility to " + mSyncInProgress);
525 setSupportProgressBarIndeterminateVisibility(mSyncInProgress /*|| mRefreshSharesInProgress*/);
526
527 setBackgroundText();
528
529 }
530
531 } catch (RuntimeException e) {
532 // avoid app crashes after changing the serial id of RemoteOperationResult
533 // in owncloud library with broadcast notifications pending to process
534 removeStickyBroadcast(intent);
535 }
536 }
537 }
538
539
540
541 /**
542 * Shows the information of the {@link OCFile} received as a
543 * parameter in the second fragment.
544 *
545 * @param file {@link OCFile} whose details will be shown
546 */
547 @Override
548 public void showDetails(OCFile file) {
549
550 }
551
552 /**
553 * {@inheritDoc}
554 */
555 @Override
556 public void onTransferStateChanged(OCFile file, boolean downloading, boolean uploading) {
557
558 }
559
560 @Override
561 public void onRefresh() {
562 refreshList(true);
563 }
564
565 @Override
566 public void onRefresh(boolean enforced) {
567 refreshList(enforced);
568 }
569
570 private void refreshList(boolean ignoreETag) {
571 OCFileListFragment listOfFiles = getListOfFilesFragment();
572 if (listOfFiles != null) {
573 OCFile folder = listOfFiles.getCurrentFile();
574 if (folder != null) {
575 startSyncFolderOperation(folder, ignoreETag);
576 }
577 }
578 }
579 }