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