1 package com
.owncloud
.android
.ui
.activity
;
3 import java
.io
.IOException
;
5 import android
.accounts
.Account
;
6 import android
.accounts
.AccountManager
;
7 import android
.accounts
.AuthenticatorException
;
8 import android
.accounts
.OperationCanceledException
;
9 import android
.content
.BroadcastReceiver
;
10 import android
.content
.Context
;
11 import android
.content
.Intent
;
12 import android
.content
.IntentFilter
;
13 import android
.os
.Bundle
;
14 import android
.support
.v4
.app
.Fragment
;
15 import android
.support
.v4
.app
.FragmentTransaction
;
16 import android
.util
.Log
;
17 import android
.view
.View
;
18 import android
.view
.View
.OnClickListener
;
19 import android
.view
.ViewGroup
;
20 import android
.widget
.ArrayAdapter
;
21 import android
.widget
.Button
;
22 import android
.widget
.TextView
;
23 import android
.widget
.Toast
;
25 import com
.actionbarsherlock
.app
.ActionBar
;
26 import com
.actionbarsherlock
.app
.ActionBar
.OnNavigationListener
;
27 import com
.actionbarsherlock
.view
.Menu
;
28 import com
.actionbarsherlock
.view
.MenuInflater
;
29 import com
.actionbarsherlock
.view
.MenuItem
;
30 import com
.actionbarsherlock
.view
.Window
;
31 import com
.owncloud
.android
.R
;
32 import com
.owncloud
.android
.datamodel
.OCFile
;
33 import com
.owncloud
.android
.lib
.common
.OwnCloudAccount
;
34 import com
.owncloud
.android
.lib
.common
.OwnCloudClient
;
35 import com
.owncloud
.android
.lib
.common
.OwnCloudClientManagerFactory
;
36 import com
.owncloud
.android
.lib
.common
.OwnCloudCredentials
;
37 import com
.owncloud
.android
.lib
.common
.accounts
.AccountUtils
.AccountNotFoundException
;
38 import com
.owncloud
.android
.lib
.common
.operations
.RemoteOperation
;
39 import com
.owncloud
.android
.lib
.common
.operations
.RemoteOperationResult
;
40 import com
.owncloud
.android
.lib
.common
.operations
.RemoteOperationResult
.ResultCode
;
41 import com
.owncloud
.android
.operations
.SynchronizeFolderOperation
;
42 import com
.owncloud
.android
.services
.observer
.FileObserverService
;
43 import com
.owncloud
.android
.syncadapter
.FileSyncAdapter
;
44 import com
.owncloud
.android
.ui
.dialog
.CreateFolderDialogFragment
;
45 import com
.owncloud
.android
.ui
.fragment
.FileFragment
;
46 import com
.owncloud
.android
.ui
.fragment
.MoveFileListFragment
;
47 import com
.owncloud
.android
.utils
.DisplayUtils
;
48 import com
.owncloud
.android
.utils
.Log_OC
;
50 public class MoveActivity
extends HookActivity
implements FileFragment
.ContainerActivity
,
51 OnNavigationListener
, OnClickListener
{
53 private ArrayAdapter
<String
> mDirectories
;
55 private SyncBroadcastReceiver mSyncBroadcastReceiver
;
57 public static final int DIALOG_SHORT_WAIT
= 0;
59 public static final String ACTION_DETAILS
= "com.owncloud.android.ui.activity.action.DETAILS";
61 private static final String TAG
= MoveActivity
.class.getSimpleName();
63 private static final String TAG_LIST_OF_FILES
= "LIST_OF_FILES";
65 private boolean mSyncInProgress
= false
;
67 private Button mCancelBtn
;
68 private Button mChooseBtn
;
72 protected void onCreate(Bundle savedInstanceState
) {
73 Log_OC
.d(TAG
, "onCreate() start");
74 requestWindowFeature(Window
.FEATURE_INDETERMINATE_PROGRESS
);
76 super.onCreate(savedInstanceState
); // this calls onAccountChanged() when ownCloud Account is valid
78 /// grant that FileObserverService is watching favourite files
79 if (savedInstanceState
== null
) {
80 Intent initObserversIntent
= FileObserverService
.makeInitIntent(this);
81 startService(initObserversIntent
);
86 // Inflate and set the layout view
87 setContentView(R
.layout
.files_move
);
88 if (savedInstanceState
== null
) {
95 mDirectories
= new CustomArrayAdapter
<String
>(this, R
.layout
.sherlock_spinner_dropdown_item
);
96 getSupportActionBar().setHomeButtonEnabled(true
); // mandatory since Android ICS, according to the official documentation
97 setSupportProgressBarIndeterminateVisibility(mSyncInProgress
/*|| mRefreshSharesInProgress*/); // always AFTER setContentView(...) ; to work around bug in its implementation
101 Log_OC
.d(TAG
, "onCreate() end");
105 private void createMinFragments() {
106 MoveFileListFragment listOfFiles
= new MoveFileListFragment();
107 FragmentTransaction transaction
= getSupportFragmentManager().beginTransaction();
108 transaction
.add(R
.id
.fragment_container
, listOfFiles
, TAG_LIST_OF_FILES
);
109 transaction
.commit();
113 * Show a text message on screen view for notifying user if content is
114 * loading or folder is empty
116 private void setBackgroundText() {
117 MoveFileListFragment MoveFileListFragment
= getListOfFilesFragment();
118 if (MoveFileListFragment
!= null
) {
119 int message
= R
.string
.file_list_loading
;
120 if (!mSyncInProgress
) {
121 // In case folder list is empty
122 message
= R
.string
.file_list_empty_moving
;
124 MoveFileListFragment
.setMessageForEmptyList(getString(message
));
126 Log
.e(TAG
, "MoveFileListFragment is null");
130 private MoveFileListFragment
getListOfFilesFragment() {
131 Fragment listOfFiles
= getSupportFragmentManager().findFragmentByTag(MoveActivity
.TAG_LIST_OF_FILES
);
132 if (listOfFiles
!= null
) {
133 return (MoveFileListFragment
)listOfFiles
;
135 Log_OC
.wtf(TAG
, "Access to unexisting list of files fragment!!");
139 // Custom array adapter to override text colors
140 private class CustomArrayAdapter
<T
> extends ArrayAdapter
<T
> {
142 public CustomArrayAdapter(MoveActivity ctx
, int view
) {
146 public View
getView(int position
, View convertView
, ViewGroup parent
) {
147 View v
= super.getView(position
, convertView
, parent
);
149 ((TextView
) v
).setTextColor(getResources().getColorStateList(
150 android
.R
.color
.white
));
152 fixRoot((TextView
) v
);
156 public View
getDropDownView(int position
, View convertView
,
158 View v
= super.getDropDownView(position
, convertView
, parent
);
160 ((TextView
) v
).setTextColor(getResources().getColorStateList(
161 android
.R
.color
.white
));
163 fixRoot((TextView
) v
);
167 private void fixRoot(TextView v
) {
168 if (v
.getText().equals(OCFile
.PATH_SEPARATOR
)) {
169 v
.setText(R
.string
.default_display_name_for_root_folder
);
178 * Updates action bar and second fragment, if in dual pane mode.
181 public void onBrowsedDownTo(OCFile directory
) {
182 pushDirname(directory
);
185 startSyncFolderOperation(directory
);
190 * Shows the information of the {@link OCFile} received as a
191 * parameter in the second fragment.
193 * @param file {@link OCFile} whose details will be shown
196 public void showDetails(OCFile file
) {
204 public void onTransferStateChanged(OCFile file
, boolean downloading
, boolean uploading
) {
209 * Pushes a directory to the drop down list
210 * @param directory to push
211 * @throws IllegalArgumentException If the {@link OCFile#isFolder()} returns false.
213 public void pushDirname(OCFile directory
) {
214 if(!directory
.isFolder()){
215 throw new IllegalArgumentException("Only directories may be pushed!");
217 mDirectories
.insert(directory
.getFileName(), 0);
221 public void startSyncFolderOperation(OCFile folder
) {
222 long currentSyncTime
= System
.currentTimeMillis();
224 mSyncInProgress
= true
;
226 // perform folder synchronization
227 RemoteOperation synchFolderOp
= new SynchronizeFolderOperation( folder
,
230 getFileOperationsHelper().isSharedSupported(),
233 getApplicationContext()
235 synchFolderOp
.execute(getAccount(), this, null
, null
);
237 setSupportProgressBarIndeterminateVisibility(true
);
243 protected void onResume() {
245 Log_OC
.e(TAG
, "onResume() start");
247 // refresh list of files
248 refreshListOfFilesFragment();
250 // Listen for sync messages
251 IntentFilter syncIntentFilter
= new IntentFilter(FileSyncAdapter
.EVENT_FULL_SYNC_START
);
252 syncIntentFilter
.addAction(FileSyncAdapter
.EVENT_FULL_SYNC_END
);
253 syncIntentFilter
.addAction(FileSyncAdapter
.EVENT_FULL_SYNC_FOLDER_CONTENTS_SYNCED
);
254 syncIntentFilter
.addAction(SynchronizeFolderOperation
.EVENT_SINGLE_FOLDER_CONTENTS_SYNCED
);
255 syncIntentFilter
.addAction(SynchronizeFolderOperation
.EVENT_SINGLE_FOLDER_SHARES_SYNCED
);
256 mSyncBroadcastReceiver
= new SyncBroadcastReceiver();
257 registerReceiver(mSyncBroadcastReceiver
, syncIntentFilter
);
259 Log_OC
.d(TAG
, "onResume() end");
263 protected void onStart() {
265 getSupportActionBar().setIcon(DisplayUtils
.getSeasonalIconId());
269 protected void onDestroy() {
274 public boolean onCreateOptionsMenu(Menu menu
) {
275 MenuInflater inflater
= getSherlock().getMenuInflater();
276 inflater
.inflate(R
.menu
.main_menu
, menu
);
277 menu
.findItem(R
.id
.action_upload
).setVisible(false
);
278 menu
.findItem(R
.id
.action_settings
).setVisible(false
);
279 menu
.findItem(R
.id
.action_sync_account
).setVisible(false
);
284 public boolean onOptionsItemSelected(MenuItem item
) {
285 boolean retval
= true
;
286 switch (item
.getItemId()) {
287 case R
.id
.action_create_dir
: {
288 CreateFolderDialogFragment dialog
=
289 CreateFolderDialogFragment
.newInstance(getCurrentDir());
290 dialog
.show(getSupportFragmentManager(), "createdirdialog");
293 case android
.R
.id
.home
: {
294 OCFile currentDir
= getCurrentDir();
295 if(currentDir
!= null
&& currentDir
.getParentId() != 0) {
301 retval
= super.onOptionsItemSelected(item
);
306 private OCFile
getCurrentDir() {
307 OCFile file
= getFile();
309 if (file
.isFolder()) {
311 } else if (getStorageManager() != null
) {
312 String parentPath
= file
.getRemotePath().substring(0, file
.getRemotePath().lastIndexOf(file
.getFileName()));
313 return getStorageManager().getFileByPath(parentPath
);
319 protected void refreshListOfFilesFragment() {
320 MoveFileListFragment fileListFragment
= getListOfFilesFragment();
321 if (fileListFragment
!= null
) {
322 fileListFragment
.listDirectory();
326 private class SyncBroadcastReceiver
extends BroadcastReceiver
{
329 * {@link BroadcastReceiver} to enable syncing feedback in UI
332 public void onReceive(Context context
, Intent intent
) {
334 String event
= intent
.getAction();
335 Log_OC
.d(TAG
, "Received broadcast " + event
);
336 String accountName
= intent
.getStringExtra(FileSyncAdapter
.EXTRA_ACCOUNT_NAME
);
337 String synchFolderRemotePath
= intent
.getStringExtra(FileSyncAdapter
.EXTRA_FOLDER_PATH
);
338 RemoteOperationResult synchResult
= (RemoteOperationResult
)intent
.getSerializableExtra(FileSyncAdapter
.EXTRA_RESULT
);
339 boolean sameAccount
= (getAccount() != null
&& accountName
.equals(getAccount().name
) && getStorageManager() != null
);
343 if (FileSyncAdapter
.EVENT_FULL_SYNC_START
.equals(event
)) {
344 mSyncInProgress
= true
;
347 OCFile currentFile
= (getFile() == null
) ? null
: getStorageManager().getFileByPath(getFile().getRemotePath());
348 OCFile currentDir
= (getCurrentDir() == null
) ? null
: getStorageManager().getFileByPath(getCurrentDir().getRemotePath());
350 if (currentDir
== null
) {
351 // current folder was removed from the server
352 Toast
.makeText( MoveActivity
.this,
353 String
.format(getString(R
.string
.sync_current_folder_was_removed
), mDirectories
.getItem(0)),
359 if (currentFile
== null
&& !getFile().isFolder()) {
360 // currently selected file was removed in the server, and now we know it
361 currentFile
= currentDir
;
364 if (synchFolderRemotePath
!= null
&& currentDir
.getRemotePath().equals(synchFolderRemotePath
)) {
365 MoveFileListFragment fileListFragment
= getListOfFilesFragment();
366 if (fileListFragment
!= null
) {
367 fileListFragment
.listDirectory(currentDir
);
370 setFile(currentFile
);
373 mSyncInProgress
= (!FileSyncAdapter
.EVENT_FULL_SYNC_END
.equals(event
) && !SynchronizeFolderOperation
.EVENT_SINGLE_FOLDER_SHARES_SYNCED
.equals(event
));
375 if (SynchronizeFolderOperation
.EVENT_SINGLE_FOLDER_CONTENTS_SYNCED
.
377 /// TODO refactor and make common
378 synchResult
!= null
&& !synchResult
.isSuccess() &&
379 (synchResult
.getCode() == ResultCode
.UNAUTHORIZED
||
380 synchResult
.isIdPRedirection() ||
381 (synchResult
.isException() && synchResult
.getException()
382 instanceof AuthenticatorException
))) {
384 OwnCloudClient client
= null
;
386 OwnCloudAccount ocAccount
=
387 new OwnCloudAccount(getAccount(), context
);
388 client
= (OwnCloudClientManagerFactory
.getDefaultSingleton().
389 removeClientFor(ocAccount
));
390 // TODO get rid of these exceptions
391 } catch (AccountNotFoundException e
) {
393 } catch (AuthenticatorException e
) {
395 } catch (OperationCanceledException e
) {
397 } catch (IOException e
) {
401 if (client
!= null
) {
402 OwnCloudCredentials cred
= client
.getCredentials();
404 AccountManager am
= AccountManager
.get(context
);
405 if (cred
.authTokenExpires()) {
406 am
.invalidateAuthToken(
411 am
.clearPassword(getAccount());
416 requestCredentialsUpdate();
420 removeStickyBroadcast(intent
);
421 Log_OC
.d(TAG
, "Setting progress visibility to " + mSyncInProgress
);
422 setSupportProgressBarIndeterminateVisibility(mSyncInProgress
/*|| mRefreshSharesInProgress*/);
428 } catch (RuntimeException e
) {
429 // avoid app crashes after changing the serial id of RemoteOperationResult
430 // in owncloud library with broadcast notifications pending to process
431 removeStickyBroadcast(intent
);
436 public void browseToRoot() {
437 MoveFileListFragment listOfFiles
= getListOfFilesFragment();
438 if (listOfFiles
!= null
) { // should never be null, indeed
439 while (mDirectories
.getCount() > 1) {
442 OCFile root
= getStorageManager().getFileByPath(OCFile
.ROOT_PATH
);
443 listOfFiles
.listDirectory(root
);
444 setFile(listOfFiles
.getCurrentFile());
445 startSyncFolderOperation(root
);
450 * Pops a directory name from the drop down list
451 * @return True, unless the stack is empty
453 public boolean popDirname() {
454 mDirectories
.remove(mDirectories
.getItem(0));
455 return !mDirectories
.isEmpty();
458 private void setNavigationListWithFolder(OCFile file
) {
459 mDirectories
.clear();
460 OCFile fileIt
= file
;
462 while(fileIt
!= null
&& fileIt
.getFileName() != OCFile
.ROOT_PATH
) {
463 if (fileIt
.isFolder()) {
464 mDirectories
.add(fileIt
.getFileName());
466 // get parent from path
467 parentPath
= fileIt
.getRemotePath().substring(0, fileIt
.getRemotePath().lastIndexOf(fileIt
.getFileName()));
468 fileIt
= getStorageManager().getFileByPath(parentPath
);
470 mDirectories
.add(OCFile
.PATH_SEPARATOR
);
474 public boolean onNavigationItemSelected(int itemPosition
, long itemId
) {
475 if (itemPosition
!= 0) {
476 String targetPath
= "";
477 for (int i
=itemPosition
; i
< mDirectories
.getCount() - 1; i
++) {
478 targetPath
= mDirectories
.getItem(i
) + OCFile
.PATH_SEPARATOR
+ targetPath
;
480 targetPath
= OCFile
.PATH_SEPARATOR
+ targetPath
;
481 OCFile targetFolder
= getStorageManager().getFileByPath(targetPath
);
482 if (targetFolder
!= null
) {
483 browseTo(targetFolder
);
486 // the next operation triggers a new call to this method, but it's necessary to
487 // ensure that the name exposed in the action bar is the current directory when the
488 // user selected it in the navigation list
489 if (getSupportActionBar().getNavigationMode() == ActionBar
.NAVIGATION_MODE_LIST
&& itemPosition
!= 0)
490 getSupportActionBar().setSelectedNavigationItem(0);
495 public void browseTo(OCFile folder
) {
496 if (folder
== null
|| !folder
.isFolder()) {
497 throw new IllegalArgumentException("Trying to browse to invalid folder " + folder
);
499 MoveFileListFragment listOfFiles
= getListOfFilesFragment();
500 if (listOfFiles
!= null
) {
501 setNavigationListWithFolder(folder
);
502 listOfFiles
.listDirectory(folder
);
503 setFile(listOfFiles
.getCurrentFile());
504 startSyncFolderOperation(folder
);
506 Log_OC
.e(TAG
, "Unexpected null when accessing list fragment");
511 public void onBackPressed() {
512 MoveFileListFragment listOfFiles
= getListOfFilesFragment();
513 if (listOfFiles
!= null
) { // should never be null, indeed
514 if (mDirectories
.getCount() <= 1) {
518 int levelsUp
= listOfFiles
.onBrowseUp();
519 for (int i
=0; i
< levelsUp
&& mDirectories
.getCount() > 1 ; i
++) {
523 if (listOfFiles
!= null
) { // should never be null, indeed
524 setFile(listOfFiles
.getCurrentFile());
528 private void updateNavigationElementsInActionBar(OCFile chosenFile
) {
529 ActionBar actionBar
= getSupportActionBar();
530 if (chosenFile
== null
) {
531 // only list of files - set for browsing through folders
532 OCFile currentDir
= getCurrentDir();
533 boolean noRoot
= (currentDir
!= null
&& currentDir
.getParentId() != 0);
534 actionBar
.setDisplayHomeAsUpEnabled(noRoot
);
535 actionBar
.setDisplayShowTitleEnabled(!noRoot
);
537 actionBar
.setTitle(getString(R
.string
.default_display_name_for_root_folder
));
539 actionBar
.setNavigationMode(!noRoot ? ActionBar
.NAVIGATION_MODE_STANDARD
: ActionBar
.NAVIGATION_MODE_LIST
);
540 actionBar
.setListNavigationCallbacks(mDirectories
, this); // assuming mDirectories is updated
543 actionBar
.setDisplayHomeAsUpEnabled(true
);
544 actionBar
.setDisplayShowTitleEnabled(true
);
545 actionBar
.setTitle(chosenFile
.getFileName());
546 actionBar
.setNavigationMode(ActionBar
.NAVIGATION_MODE_STANDARD
);
551 * Called when the ownCloud {@link Account} associated to the Activity was just updated.
554 protected void onAccountSet(boolean stateWasRecovered
) {
555 super.onAccountSet(stateWasRecovered
);
556 if (getAccount() != null
) {
557 /// Check whether the 'main' OCFile handled by the Activity is contained in the current Account
558 OCFile file
= getFile();
559 // get parent from path
560 String parentPath
= "";
562 if (file
.isDown() && file
.getLastSyncDateForProperties() == 0) {
563 // upload in progress - right now, files are not inserted in the local cache until the upload is successful
564 // get parent from path
565 parentPath
= file
.getRemotePath().substring(0, file
.getRemotePath().lastIndexOf(file
.getFileName()));
566 if (getStorageManager().getFileByPath(parentPath
) == null
)
567 file
= null
; // not able to know the directory where the file is uploading
569 file
= getStorageManager().getFileByPath(file
.getRemotePath()); // currentDir = null if not in the current Account
573 // fall back to root folder
574 file
= getStorageManager().getFileByPath(OCFile
.ROOT_PATH
); // never returns null
577 setNavigationListWithFolder(file
);
579 if (!stateWasRecovered
) {
580 Log_OC
.e(TAG
, "Initializing Fragments in onAccountChanged..");
581 if (file
.isFolder()) {
582 startSyncFolderOperation(file
);
585 updateNavigationElementsInActionBar(file
.isFolder() ? null
: file
);
593 private void initControls(){
594 mCancelBtn
= (Button
) findViewById(R
.id
.move_files_btn_cancel
);
595 mCancelBtn
.setOnClickListener(this);
596 mChooseBtn
= (Button
) findViewById(R
.id
.move_files_btn_choose
);
597 mChooseBtn
.setOnClickListener(this);
601 public void onClick(View v
) {
602 if (v
== mCancelBtn
) {