2 * ownCloud Android client application
4 * Copyright (C) 2015 ownCloud Inc.
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.
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.
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/>.
20 package com
.owncloud
.android
.ui
.activity
;
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
;
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
;
65 import java
.util
.ArrayList
;
67 public class FolderPickerActivity
extends FileActivity
implements FileFragment
.ContainerActivity
,
68 OnClickListener
, OnEnforceableRefreshListener
{
70 public static final String EXTRA_FOLDER
= UploadFilesActivity
.class.getCanonicalName()
72 public static final String EXTRA_FILE
= UploadFilesActivity
.class.getCanonicalName()
74 public static final String EXTRA_FILES
= UploadFilesActivity
.class.getCanonicalName()
76 //TODO: Think something better
78 private SyncBroadcastReceiver mSyncBroadcastReceiver
;
80 private static final String TAG
= FolderPickerActivity
.class.getSimpleName();
82 private static final String TAG_LIST_OF_FOLDERS
= "LIST_OF_FOLDERS";
84 private boolean mSyncInProgress
= false
;
86 protected Button mCancelBtn
;
87 protected Button mChooseBtn
;
88 private ProgressBar mProgressBar
;
92 protected void onCreate(Bundle savedInstanceState
) {
93 Log_OC
.d(TAG
, "onCreate() start");
95 super.onCreate(savedInstanceState
);
97 setContentView(R
.layout
.files_folder_picker
);
99 if (savedInstanceState
== null
) {
103 // sets callback listeners for UI elements
107 ActionBar actionBar
= getSupportActionBar();
108 actionBar
.setDisplayShowTitleEnabled(true
);
109 actionBar
.setNavigationMode(ActionBar
.NAVIGATION_MODE_STANDARD
);
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
118 // sets message for empty list of folders
121 Log_OC
.d(TAG
, "onCreate() end");
125 protected void onStart() {
130 * Called when the ownCloud {@link Account} associated to the Activity was just updated.
133 protected void onAccountSet(boolean stateWasRecovered
) {
134 super.onAccountSet(stateWasRecovered
);
135 if (getAccount() != null
) {
139 OCFile folder
= getFile();
140 if (folder
== null
|| !folder
.isFolder()) {
141 // fall back to root folder
142 setFile(getStorageManager().getFileByPath(OCFile
.ROOT_PATH
));
146 if (!stateWasRecovered
) {
147 OCFileListFragment listOfFolders
= getListOfFilesFragment();
148 listOfFolders
.listDirectory(folder
/*, false*/);
150 startSyncFolderOperation(folder
, false
);
153 updateNavigationElementsInActionBar();
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();
169 * Show a text message on screen view for notifying user if content is
170 * loading or folder is empty
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
;
180 listFragment
.setMessageForEmptyList(getString(message
));
182 Log
.e(TAG
, "OCFileListFragment is null");
186 protected OCFileListFragment
getListOfFilesFragment() {
187 Fragment listOfFiles
= getSupportFragmentManager().findFragmentByTag(FolderPickerActivity
.TAG_LIST_OF_FOLDERS
);
188 if (listOfFiles
!= null
) {
189 return (OCFileListFragment
)listOfFiles
;
191 Log_OC
.wtf(TAG
, "Access to unexisting list of files fragment!!");
199 * Updates action bar and second fragment, if in dual pane mode.
202 public void onBrowsedDownTo(OCFile directory
) {
204 updateNavigationElementsInActionBar();
206 startSyncFolderOperation(directory
, false
);
211 public void startSyncFolderOperation(OCFile folder
, boolean ignoreETag
) {
212 long currentSyncTime
= System
.currentTimeMillis();
214 mSyncInProgress
= true
;
216 // perform folder synchronization
217 RemoteOperation synchFolderOp
= new RefreshFolderOperation( folder
,
220 getFileOperationsHelper().isSharedSupported(),
224 getApplicationContext()
226 synchFolderOp
.execute(getAccount(), this, null
, null
);
228 mProgressBar
.setIndeterminate(true
);
234 protected void onResume() {
236 Log_OC
.e(TAG
, "onResume() start");
238 // refresh list of files
239 refreshListOfFilesFragment();
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
);
250 Log_OC
.d(TAG
, "onResume() end");
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
;
262 Log_OC
.d(TAG
, "onPause() end");
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
);
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());
283 getSupportFragmentManager(),
284 CreateFolderDialogFragment
.CREATE_FOLDER_FRAGMENT
288 case android
.R
.id
.home
: {
289 OCFile currentDir
= getCurrentFolder();
290 if(currentDir
!= null
&& currentDir
.getParentId() != 0) {
296 retval
= super.onOptionsItemSelected(item
);
301 protected OCFile
getCurrentFolder() {
302 OCFile file
= getFile();
304 if (file
.isFolder()) {
306 } else if (getStorageManager() != null
) {
307 String parentPath
= file
.getRemotePath().substring(0, file
.getRemotePath().lastIndexOf(file
.getFileName()));
308 return getStorageManager().getFileByPath(parentPath
);
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);
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
);
337 public void onBackPressed() {
338 OCFileListFragment listOfFiles
= getListOfFilesFragment();
339 if (listOfFiles
!= null
) { // should never be null, indeed
340 int levelsUp
= listOfFiles
.onBrowseUp();
345 setFile(listOfFiles
.getCurrentFile());
346 updateNavigationElementsInActionBar();
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
);
358 ?
getString(R
.string
.default_display_name_for_root_folder
)
359 : currentDir
.getFileName()
364 * Set per-view controllers
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);
374 public void onClick(View v
) {
375 if (v
== mCancelBtn
) {
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
);
382 Intent data
= new Intent();
383 data
.putExtra(EXTRA_FOLDER
, getCurrentFolder());
384 if (targetFile
!= null
) {
385 data
.putExtra(EXTRA_FILE
, targetFile
);
387 if (targetFiles
!= null
){
388 data
.putParcelableArrayListExtra(EXTRA_FILES
, targetFiles
);
390 setResult(RESULT_OK
, data
);
397 public void onRemoteOperationFinish(RemoteOperation operation
, RemoteOperationResult result
) {
398 super.onRemoteOperationFinish(operation
, result
);
400 if (operation
instanceof CreateFolderOperation
) {
401 onCreateFolderOperationFinish((CreateFolderOperation
)operation
, result
);
408 * Updates the view associated to the activity after the finish of an operation trying
409 * to create a new folder.
411 * @param operation Creation operation performed.
412 * @param result Result of the creation.
414 private void onCreateFolderOperationFinish(
415 CreateFolderOperation operation
, RemoteOperationResult result
418 if (result
.isSuccess()) {
419 dismissLoadingDialog();
420 refreshListOfFilesFragment();
422 dismissLoadingDialog();
424 Toast msg
= Toast
.makeText(FolderPickerActivity
.this,
425 ErrorMessageAdapter
.getErrorCauseMessage(result
, operation
, getResources()),
429 } catch (NotFoundException e
) {
430 Log_OC
.e(TAG
, "Error while trying to show fail message " , e
);
437 private class SyncBroadcastReceiver
extends BroadcastReceiver
{
440 * {@link BroadcastReceiver} to enable syncing feedback in UI
443 public void onReceive(Context context
, Intent intent
) {
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
);
456 if (FileSyncAdapter
.EVENT_FULL_SYNC_START
.equals(event
)) {
457 mSyncInProgress
= true
;
460 OCFile currentFile
= (getFile() == null
) ? null
:
461 getStorageManager().getFileByPath(getFile().getRemotePath());
462 OCFile currentDir
= (getCurrentFolder() == null
) ? null
:
463 getStorageManager().getFileByPath(getCurrentFolder().getRemotePath());
465 if (currentDir
== null
) {
466 // current folder was removed from the server
467 Toast
.makeText( FolderPickerActivity
.this,
469 getString(R
.string
.sync_current_folder_was_removed
),
470 getCurrentFolder().getFileName()),
476 if (currentFile
== null
&& !getFile().isFolder()) {
477 // currently selected file was removed in the server, and now we know it
478 currentFile
= currentDir
;
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);
490 setFile(currentFile
);
493 mSyncInProgress
= (!FileSyncAdapter
.EVENT_FULL_SYNC_END
.equals(event
) &&
494 !RefreshFolderOperation
.EVENT_SINGLE_FOLDER_SHARES_SYNCED
.equals(event
));
496 if (RefreshFolderOperation
.EVENT_SINGLE_FOLDER_CONTENTS_SYNCED
.
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
))) {
506 OwnCloudClient client
;
507 OwnCloudAccount ocAccount
=
508 new OwnCloudAccount(getAccount(), context
);
509 client
= (OwnCloudClientManagerFactory
.getDefaultSingleton().
510 removeClientFor(ocAccount
));
512 if (client
!= null
) {
513 OwnCloudCredentials cred
= client
.getCredentials();
515 AccountManager am
= AccountManager
.get(context
);
516 if (cred
.authTokenExpires()) {
517 am
.invalidateAuthToken(
522 am
.clearPassword(getAccount());
526 requestCredentialsUpdate();
528 } catch (AccountNotFoundException e
) {
529 Log_OC
.e(TAG
, "Account " + getAccount() + " was removed!", e
);
534 removeStickyBroadcast(intent
);
535 Log_OC
.d(TAG
, "Setting progress visibility to " + mSyncInProgress
);
537 mProgressBar
.setIndeterminate(mSyncInProgress
);
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
);
553 * Shows the information of the {@link OCFile} received as a
554 * parameter in the second fragment.
556 * @param file {@link OCFile} whose details will be shown
559 public void showDetails(OCFile file
) {
567 public void onTransferStateChanged(OCFile file
, boolean downloading
, boolean uploading
) {
572 public void onRefresh() {
577 public void onRefresh(boolean enforced
) {
578 refreshList(enforced
);
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
);