OSDN Git Service

Save scrolling position and restore it when going back
[android-x86/packages-apps-CMFileManager.git] / src / com / cyanogenmod / filemanager / ui / widgets / NavigationView.java
1 /*
2  * Copyright (C) 2012 The CyanogenMod Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package com.cyanogenmod.filemanager.ui.widgets;
18
19 import android.app.Activity;
20 import android.content.Context;
21 import android.content.SharedPreferences;
22 import android.content.res.TypedArray;
23 import android.os.AsyncTask;
24 import android.os.storage.StorageVolume;
25 import android.util.AttributeSet;
26 import android.util.Log;
27 import android.view.View;
28 import android.view.animation.AccelerateInterpolator;
29 import android.view.animation.AlphaAnimation;
30 import android.view.animation.Animation;
31 import android.widget.AdapterView;
32 import android.widget.ListAdapter;
33 import android.widget.ListView;
34 import android.widget.RelativeLayout;
35 import android.widget.Toast;
36
37 import com.cyanogenmod.filemanager.FileManagerApplication;
38 import com.cyanogenmod.filemanager.R;
39 import com.cyanogenmod.filemanager.adapters.FileSystemObjectAdapter;
40 import com.cyanogenmod.filemanager.adapters.FileSystemObjectAdapter.OnSelectionChangedListener;
41 import com.cyanogenmod.filemanager.console.ConsoleAllocException;
42 import com.cyanogenmod.filemanager.listeners.OnHistoryListener;
43 import com.cyanogenmod.filemanager.listeners.OnRequestRefreshListener;
44 import com.cyanogenmod.filemanager.listeners.OnSelectionListener;
45 import com.cyanogenmod.filemanager.model.Directory;
46 import com.cyanogenmod.filemanager.model.FileSystemObject;
47 import com.cyanogenmod.filemanager.model.ParentDirectory;
48 import com.cyanogenmod.filemanager.model.Symlink;
49 import com.cyanogenmod.filemanager.parcelables.NavigationViewInfoParcelable;
50 import com.cyanogenmod.filemanager.parcelables.SearchInfoParcelable;
51 import com.cyanogenmod.filemanager.preferences.AccessMode;
52 import com.cyanogenmod.filemanager.preferences.DisplayRestrictions;
53 import com.cyanogenmod.filemanager.preferences.FileManagerSettings;
54 import com.cyanogenmod.filemanager.preferences.NavigationLayoutMode;
55 import com.cyanogenmod.filemanager.preferences.ObjectIdentifier;
56 import com.cyanogenmod.filemanager.preferences.Preferences;
57 import com.cyanogenmod.filemanager.ui.ThemeManager;
58 import com.cyanogenmod.filemanager.ui.ThemeManager.Theme;
59 import com.cyanogenmod.filemanager.ui.policy.DeleteActionPolicy;
60 import com.cyanogenmod.filemanager.ui.policy.IntentsActionPolicy;
61 import com.cyanogenmod.filemanager.ui.widgets.FlingerListView.OnItemFlingerListener;
62 import com.cyanogenmod.filemanager.ui.widgets.FlingerListView.OnItemFlingerResponder;
63 import com.cyanogenmod.filemanager.util.CommandHelper;
64 import com.cyanogenmod.filemanager.util.DialogHelper;
65 import com.cyanogenmod.filemanager.util.ExceptionUtil;
66 import com.cyanogenmod.filemanager.util.ExceptionUtil.OnRelaunchCommandResult;
67 import com.cyanogenmod.filemanager.util.FileHelper;
68 import com.cyanogenmod.filemanager.util.StorageHelper;
69
70 import java.io.File;
71 import java.util.ArrayList;
72 import java.util.HashMap;
73 import java.util.List;
74 import java.util.Map;
75
76 /**
77  * The file manager implementation view (contains the graphical representation and the input
78  * management for a file manager; shows the folders/files, the mode view, react touch events,
79  * navigate, ...).
80  */
81 public class NavigationView extends RelativeLayout implements
82 AdapterView.OnItemClickListener, AdapterView.OnItemLongClickListener,
83 BreadcrumbListener, OnSelectionChangedListener, OnSelectionListener, OnRequestRefreshListener {
84
85     private static final String TAG = "NavigationView"; //$NON-NLS-1$
86
87     /**
88      * An interface to communicate selection changes events.
89      */
90     public interface OnNavigationSelectionChangedListener {
91         /**
92          * Method invoked when the selection changed.
93          *
94          * @param navView The navigation view that generate the event
95          * @param selectedItems The new selected items
96          */
97         void onSelectionChanged(NavigationView navView, List<FileSystemObject> selectedItems);
98     }
99
100     /**
101      * An interface to communicate a request for show the menu associated
102      * with an item.
103      */
104     public interface OnNavigationRequestMenuListener {
105         /**
106          * Method invoked when a request to show the menu associated
107          * with an item is started.
108          *
109          * @param navView The navigation view that generate the event
110          * @param item The item for which the request was started
111          */
112         void onRequestMenu(NavigationView navView, FileSystemObject item);
113     }
114
115     /**
116      * An interface to communicate a request when the user choose a file.
117      */
118     public interface OnFilePickedListener {
119         /**
120          * Method invoked when a request when the user choose a file.
121          *
122          * @param item The item choose
123          */
124         void onFilePicked(FileSystemObject item);
125     }
126
127     /**
128      * An interface to communicate a change of the current directory
129      */
130     public interface OnDirectoryChangedListener {
131         /**
132          * Method invoked when the current directory changes
133          *
134          * @param item The newly active directory
135          */
136         void onDirectoryChanged(FileSystemObject item);
137     }
138
139     /**
140      * The navigation view mode
141      * @hide
142      */
143     public enum NAVIGATION_MODE {
144         /**
145          * The navigation view acts as a browser, and allow open files itself.
146          */
147         BROWSABLE,
148         /**
149          * The navigation view acts as a picker of files
150          */
151         PICKABLE,
152     }
153
154     /**
155      * A listener for flinging events from {@link FlingerListView}
156      */
157     private final OnItemFlingerListener mOnItemFlingerListener = new OnItemFlingerListener() {
158
159         @Override
160         public boolean onItemFlingerStart(
161                 AdapterView<?> parent, View view, int position, long id) {
162             try {
163                 // Response if the item can be removed
164                 FileSystemObjectAdapter adapter = (FileSystemObjectAdapter)parent.getAdapter();
165                 FileSystemObject fso = adapter.getItem(position);
166                 if (fso != null) {
167                     if (fso instanceof ParentDirectory) {
168                         return false;
169                     }
170                     return true;
171                 }
172             } catch (Exception e) {
173                 ExceptionUtil.translateException(getContext(), e, true, false);
174             }
175             return false;
176         }
177
178         @Override
179         public void onItemFlingerEnd(OnItemFlingerResponder responder,
180                 AdapterView<?> parent, View view, int position, long id) {
181
182             try {
183                 // Response if the item can be removed
184                 FileSystemObjectAdapter adapter = (FileSystemObjectAdapter)parent.getAdapter();
185                 FileSystemObject fso = adapter.getItem(position);
186                 if (fso != null) {
187                     DeleteActionPolicy.removeFileSystemObject(
188                             getContext(),
189                             fso,
190                             NavigationView.this,
191                             NavigationView.this,
192                             responder);
193                     return;
194                 }
195
196                 // Cancels the flinger operation
197                 responder.cancel();
198
199             } catch (Exception e) {
200                 ExceptionUtil.translateException(getContext(), e, true, false);
201                 responder.cancel();
202             }
203         }
204     };
205
206     private class NavigationTask extends AsyncTask<String, Integer, List<FileSystemObject>> {
207         private final boolean mUseCurrent;
208         private final boolean mAddToHistory;
209         private final boolean mReload;
210         private boolean mHasChanged;
211         private boolean mIsNewHistory;
212         private String mNewDirChecked;
213         private final SearchInfoParcelable mSearchInfo;
214         private final FileSystemObject mScrollTo;
215
216         public NavigationTask(boolean useCurrent, boolean addToHistory, boolean reload,
217                 SearchInfoParcelable searchInfo, FileSystemObject scrollTo) {
218             super();
219             this.mUseCurrent = useCurrent;
220             this.mAddToHistory = addToHistory;
221             this.mSearchInfo = searchInfo;
222             this.mReload = reload;
223             this.mScrollTo = scrollTo;
224         }
225
226         /**
227          * {@inheritDoc}
228          */
229         @Override
230         protected List<FileSystemObject> doInBackground(String... params) {
231             // Check navigation security (don't allow to go outside the ChRooted environment if one
232             // is created)
233             mNewDirChecked = checkChRootedNavigation(params[0]);
234
235             //Check that it is really necessary change the directory
236             if (!mReload && NavigationView.this.mCurrentDir != null &&
237                     NavigationView.this.mCurrentDir.compareTo(mNewDirChecked) == 0) {
238                 return null;
239             }
240
241             mHasChanged = !(NavigationView.this.mCurrentDir != null &&
242                     NavigationView.this.mCurrentDir.compareTo(mNewDirChecked) == 0);
243             mIsNewHistory = (NavigationView.this.mCurrentDir != null);
244
245             try {
246                 //Reset the custom title view and returns to breadcrumb
247                 if (NavigationView.this.mTitle != null) {
248                     NavigationView.this.mTitle.post(new Runnable() {
249                         @Override
250                         public void run() {
251                             try {
252                                 NavigationView.this.mTitle.restoreView();
253                             } catch (Exception e) {
254                                 e.printStackTrace();
255                             }
256                         }
257                     });
258                 }
259
260
261                 //Start of loading data
262                 if (NavigationView.this.mBreadcrumb != null) {
263                     try {
264                         NavigationView.this.mBreadcrumb.startLoading();
265                     } catch (Throwable ex) {
266                         /**NON BLOCK**/
267                     }
268                 }
269
270                 //Get the files, resolve links and apply configuration
271                 //(sort, hidden, ...)
272                 List<FileSystemObject> files = NavigationView.this.mFiles;
273                 if (!mUseCurrent) {
274                     files = CommandHelper.listFiles(getContext(), mNewDirChecked, null);
275                 }
276                 return files;
277
278             } catch (final ConsoleAllocException e) {
279                 //Show exception and exists
280                 NavigationView.this.post(new Runnable() {
281                     @Override
282                     public void run() {
283                         Context ctx = getContext();
284                         Log.e(TAG, ctx.getString(
285                                 R.string.msgs_cant_create_console), e);
286                         DialogHelper.showToast(ctx,
287                                 R.string.msgs_cant_create_console,
288                                 Toast.LENGTH_LONG);
289                         ((Activity)ctx).finish();
290                     }
291                 });
292
293             } catch (Exception ex) {
294                 //End of loading data
295                 if (NavigationView.this.mBreadcrumb != null) {
296                     try {
297                         NavigationView.this.mBreadcrumb.endLoading();
298                     } catch (Throwable ex2) {
299                         /**NON BLOCK**/
300                     }
301                 }
302
303                 //Capture exception (attach task, and use listener to do the anim)
304                 ExceptionUtil.attachAsyncTask(
305                         ex,
306                         new AsyncTask<Object, Integer, Boolean>() {
307                             private List<FileSystemObject> mTaskFiles = null;
308                             @Override
309                             @SuppressWarnings({
310                                 "unchecked", "unqualified-field-access"
311                             })
312                             protected Boolean doInBackground(Object... taskParams) {
313                                 mTaskFiles = (List<FileSystemObject>)taskParams[0];
314                                 return Boolean.TRUE;
315                             }
316
317                             @Override
318                             @SuppressWarnings("unqualified-field-access")
319                             protected void onPostExecute(Boolean result) {
320                                 if (!result.booleanValue()) {
321                                     return;
322                                 }
323                                 onPostExecuteTask(
324                                         mTaskFiles, mAddToHistory, mIsNewHistory, mHasChanged,
325                                         mSearchInfo, mNewDirChecked, mScrollTo);
326                             }
327                         });
328                 final OnRelaunchCommandResult exListener =
329                         new OnRelaunchCommandResult() {
330                     @Override
331                     public void onSuccess() {
332                         done();
333                     }
334                     @Override
335                     public void onFailed(Throwable cause) {
336                         done();
337                     }
338                     @Override
339                     public void onCancelled() {
340                         done();
341                     }
342                     private void done() {
343                         // Do animation
344                         fadeEfect(false);
345                     }
346                 };
347                 ExceptionUtil.translateException(
348                         getContext(), ex, false, true, exListener);
349             }
350             return null;
351         }
352
353         /**
354          * {@inheritDoc}
355          */
356         @Override
357         protected void onCancelled(List<FileSystemObject> result) {
358             onCancelled();
359         }
360
361         /**
362          * {@inheritDoc}
363          */
364         @Override
365         protected void onPostExecute(List<FileSystemObject> files) {
366             // This means an exception. This method will be recalled then
367             if (files != null) {
368                 onPostExecuteTask(files, mAddToHistory, mIsNewHistory, mHasChanged,
369                         mSearchInfo, mNewDirChecked, mScrollTo);
370
371                 // Do animation
372                 fadeEfect(false);
373             }
374         }
375
376         /**
377          * Method that performs a fade animation.
378          *
379          * @param out Fade out (true); Fade in (false)
380          */
381         void fadeEfect(final boolean out) {
382             Activity activity = (Activity)getContext();
383             activity.runOnUiThread(new Runnable() {
384                 @Override
385                 public void run() {
386                     Animation fadeAnim = out ?
387                             new AlphaAnimation(1, 0) :
388                                 new AlphaAnimation(0, 1);
389                             fadeAnim.setDuration(50L);
390                             fadeAnim.setFillAfter(true);
391                             fadeAnim.setInterpolator(new AccelerateInterpolator());
392                             NavigationView.this.startAnimation(fadeAnim);
393                 }
394             });
395         }
396     };
397
398     private int mId;
399     private String mCurrentDir;
400     private NavigationLayoutMode mCurrentMode;
401     /**
402      * @hide
403      */
404     List<FileSystemObject> mFiles;
405     private FileSystemObjectAdapter mAdapter;
406
407     private OnHistoryListener mOnHistoryListener;
408     private OnNavigationSelectionChangedListener mOnNavigationSelectionChangedListener;
409     private OnNavigationRequestMenuListener mOnNavigationRequestMenuListener;
410     private OnFilePickedListener mOnFilePickedListener;
411     private OnDirectoryChangedListener mOnDirectoryChangedListener;
412
413     private boolean mChRooted;
414
415     private NAVIGATION_MODE mNavigationMode;
416
417     // Restrictions
418     private Map<DisplayRestrictions, Object> mRestrictions;
419
420     /**
421      * @hide
422      */
423     Breadcrumb mBreadcrumb;
424     /**
425      * @hide
426      */
427     NavigationCustomTitleView mTitle;
428     /**
429      * @hide
430      */
431     AdapterView<?> mAdapterView;
432
433     //The layout for icons mode
434     private static final int RESOURCE_MODE_ICONS_LAYOUT = R.layout.navigation_view_icons;
435     private static final int RESOURCE_MODE_ICONS_ITEM = R.layout.navigation_view_icons_item;
436     //The layout for simple mode
437     private static final int RESOURCE_MODE_SIMPLE_LAYOUT = R.layout.navigation_view_simple;
438     private static final int RESOURCE_MODE_SIMPLE_ITEM = R.layout.navigation_view_simple_item;
439     //The layout for details mode
440     private static final int RESOURCE_MODE_DETAILS_LAYOUT = R.layout.navigation_view_details;
441     private static final int RESOURCE_MODE_DETAILS_ITEM = R.layout.navigation_view_details_item;
442
443     //The current layout identifier (is shared for all the mode layout)
444     private static final int RESOURCE_CURRENT_LAYOUT = R.id.navigation_view_layout;
445
446     /**
447      * Constructor of <code>NavigationView</code>.
448      *
449      * @param context The current context
450      * @param attrs The attributes of the XML tag that is inflating the view.
451      */
452     public NavigationView(Context context, AttributeSet attrs) {
453         super(context, attrs);
454         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Navigable);
455         try {
456             init(a);
457         } finally {
458             a.recycle();
459         }
460     }
461
462     /**
463      * Constructor of <code>NavigationView</code>.
464      *
465      * @param context The current context
466      * @param attrs The attributes of the XML tag that is inflating the view.
467      * @param defStyle The default style to apply to this view. If 0, no style
468      *        will be applied (beyond what is included in the theme). This may
469      *        either be an attribute resource, whose value will be retrieved
470      *        from the current theme, or an explicit style resource.
471      */
472     public NavigationView(Context context, AttributeSet attrs, int defStyle) {
473         super(context, attrs, defStyle);
474         TypedArray a = context.obtainStyledAttributes(
475                 attrs, R.styleable.Navigable, defStyle, 0);
476         try {
477             init(a);
478         } finally {
479             a.recycle();
480         }
481     }
482
483     /**
484      * Invoked when the instance need to be saved.
485      *
486      * @return NavigationViewInfoParcelable The serialized info
487      */
488     public NavigationViewInfoParcelable onSaveState() {
489         //Return the persistent the data
490         NavigationViewInfoParcelable parcel = new NavigationViewInfoParcelable();
491         parcel.setId(this.mId);
492         parcel.setCurrentDir(this.mCurrentDir);
493         parcel.setChRooted(this.mChRooted);
494         parcel.setSelectedFiles(this.mAdapter.getSelectedItems());
495         parcel.setFiles(this.mFiles);
496
497         int firstVisiblePosition = mAdapterView.getFirstVisiblePosition();
498         if (firstVisiblePosition >= 0 && firstVisiblePosition < mAdapter.getCount()) {
499             FileSystemObject firstVisible = mAdapter
500                     .getItem(firstVisiblePosition);
501             parcel.setFirstVisible(firstVisible);
502         }
503
504         return parcel;
505     }
506
507     /**
508      * Invoked when the instance need to be restored.
509      *
510      * @param info The serialized info
511      * @return boolean If can restore
512      */
513     public boolean onRestoreState(NavigationViewInfoParcelable info) {
514         //Restore the data
515         this.mId = info.getId();
516         this.mCurrentDir = info.getCurrentDir();
517         this.mChRooted = info.getChRooted();
518         this.mFiles = info.getFiles();
519         this.mAdapter.setSelectedItems(info.getSelectedFiles());
520
521         final FileSystemObject firstVisible = info.getFirstVisible();
522
523         //Update the views
524         refresh(firstVisible);
525         return true;
526     }
527
528     /**
529      * Method that initializes the view. This method loads all the necessary
530      * information and create an appropriate layout for the view.
531      *
532      * @param tarray The type array
533      */
534     private void init(TypedArray tarray) {
535         // Retrieve the mode
536         this.mNavigationMode = NAVIGATION_MODE.BROWSABLE;
537         int mode = tarray.getInteger(
538                 R.styleable.Navigable_navigation,
539                 NAVIGATION_MODE.BROWSABLE.ordinal());
540         if (mode >= 0 && mode < NAVIGATION_MODE.values().length) {
541             this.mNavigationMode = NAVIGATION_MODE.values()[mode];
542         }
543
544         // Initialize default restrictions (no restrictions)
545         this.mRestrictions = new HashMap<DisplayRestrictions, Object>();
546
547         //Initialize variables
548         this.mFiles = new ArrayList<FileSystemObject>();
549
550         // Is ChRooted environment?
551         if (this.mNavigationMode.compareTo(NAVIGATION_MODE.PICKABLE) == 0) {
552             // Pick mode is always ChRooted
553             this.mChRooted = true;
554         } else {
555             this.mChRooted =
556                     FileManagerApplication.getAccessMode().compareTo(AccessMode.SAFE) == 0;
557         }
558
559         //Retrieve the default configuration
560         if (this.mNavigationMode.compareTo(NAVIGATION_MODE.BROWSABLE) == 0) {
561             SharedPreferences preferences = Preferences.getSharedPreferences();
562             int viewMode = preferences.getInt(
563                     FileManagerSettings.SETTINGS_LAYOUT_MODE.getId(),
564                     ((ObjectIdentifier)FileManagerSettings.
565                             SETTINGS_LAYOUT_MODE.getDefaultValue()).getId());
566             changeViewMode(NavigationLayoutMode.fromId(viewMode));
567         } else {
568             // Pick mode has always a details layout
569             changeViewMode(NavigationLayoutMode.DETAILS);
570         }
571     }
572
573     /**
574      * Method that returns the display restrictions to apply to this view.
575      *
576      * @return Map<DisplayRestrictions, Object> The restrictions to apply
577      */
578     public Map<DisplayRestrictions, Object> getRestrictions() {
579         return this.mRestrictions;
580     }
581
582     /**
583      * Method that sets the display restrictions to apply to this view.
584      *
585      * @param mRestrictions The restrictions to apply
586      */
587     public void setRestrictions(Map<DisplayRestrictions, Object> mRestrictions) {
588         this.mRestrictions = mRestrictions;
589     }
590
591     /**
592      * Method that returns the current file list of the navigation view.
593      *
594      * @return List<FileSystemObject> The current file list of the navigation view
595      */
596     public List<FileSystemObject> getFiles() {
597         if (this.mFiles == null) {
598             return null;
599         }
600         return new ArrayList<FileSystemObject>(this.mFiles);
601     }
602
603     /**
604      * Method that returns the current file list of the navigation view.
605      *
606      * @return List<FileSystemObject> The current file list of the navigation view
607      */
608     public List<FileSystemObject> getSelectedFiles() {
609         if (this.mAdapter != null && this.mAdapter.getSelectedItems() != null) {
610             return new ArrayList<FileSystemObject>(this.mAdapter.getSelectedItems());
611         }
612         return null;
613     }
614
615     /**
616      * Method that returns the custom title fragment associated with this navigation view.
617      *
618      * @return NavigationCustomTitleView The custom title view fragment
619      */
620     public NavigationCustomTitleView getCustomTitle() {
621         return this.mTitle;
622     }
623
624     /**
625      * Method that associates the custom title fragment with this navigation view.
626      *
627      * @param title The custom title view fragment
628      */
629     public void setCustomTitle(NavigationCustomTitleView title) {
630         this.mTitle = title;
631     }
632
633     /**
634      * Method that returns the breadcrumb associated with this navigation view.
635      *
636      * @return Breadcrumb The breadcrumb view fragment
637      */
638     public Breadcrumb getBreadcrumb() {
639         return this.mBreadcrumb;
640     }
641
642     /**
643      * Method that associates the breadcrumb with this navigation view.
644      *
645      * @param breadcrumb The breadcrumb view fragment
646      */
647     public void setBreadcrumb(Breadcrumb breadcrumb) {
648         this.mBreadcrumb = breadcrumb;
649         this.mBreadcrumb.addBreadcrumbListener(this);
650     }
651
652     /**
653      * Method that sets the listener for communicate history changes.
654      *
655      * @param onHistoryListener The listener for communicate history changes
656      */
657     public void setOnHistoryListener(OnHistoryListener onHistoryListener) {
658         this.mOnHistoryListener = onHistoryListener;
659     }
660
661     /**
662      * Method that sets the listener which communicates selection changes.
663      *
664      * @param onNavigationSelectionChangedListener The listener reference
665      */
666     public void setOnNavigationSelectionChangedListener(
667             OnNavigationSelectionChangedListener onNavigationSelectionChangedListener) {
668         this.mOnNavigationSelectionChangedListener = onNavigationSelectionChangedListener;
669     }
670
671     /**
672      * Method that sets the listener for menu item requests.
673      *
674      * @param onNavigationRequestMenuListener The listener reference
675      */
676     public void setOnNavigationOnRequestMenuListener(
677             OnNavigationRequestMenuListener onNavigationRequestMenuListener) {
678         this.mOnNavigationRequestMenuListener = onNavigationRequestMenuListener;
679     }
680
681     /**
682      * @return the mOnFilePickedListener
683      */
684     public OnFilePickedListener getOnFilePickedListener() {
685         return this.mOnFilePickedListener;
686     }
687
688     /**
689      * Method that sets the listener for picked items
690      *
691      * @param onFilePickedListener The listener reference
692      */
693     public void setOnFilePickedListener(OnFilePickedListener onFilePickedListener) {
694         this.mOnFilePickedListener = onFilePickedListener;
695     }
696
697     /**
698      * Method that sets the listener for directory changes
699      *
700      * @param onDirectoryChangedListener The listener reference
701      */
702     public void setOnDirectoryChangedListener(
703             OnDirectoryChangedListener onDirectoryChangedListener) {
704         this.mOnDirectoryChangedListener = onDirectoryChangedListener;
705     }
706
707     /**
708      * Method that sets if the view should use flinger gesture detection.
709      *
710      * @param useFlinger If the view should use flinger gesture detection
711      */
712     public void setUseFlinger(boolean useFlinger) {
713         if (this.mCurrentMode.compareTo(NavigationLayoutMode.ICONS) == 0) {
714             // Not supported
715             return;
716         }
717         // Set the flinger listener (only when navigate)
718         if (this.mNavigationMode.compareTo(NAVIGATION_MODE.BROWSABLE) == 0) {
719             if (this.mAdapterView instanceof FlingerListView) {
720                 if (useFlinger) {
721                     ((FlingerListView)this.mAdapterView).
722                     setOnItemFlingerListener(this.mOnItemFlingerListener);
723                 } else {
724                     ((FlingerListView)this.mAdapterView).setOnItemFlingerListener(null);
725                 }
726             }
727         }
728     }
729
730     /**
731      * Method that forces the view to scroll to the file system object passed.
732      *
733      * @param fso The file system object
734      */
735     public void scrollTo(final FileSystemObject fso) {
736
737         this.mAdapterView.post(new Runnable() {
738
739             @Override
740             public void run() {
741                 if (fso != null) {
742                     try {
743                         int position = mAdapter.getPosition(fso);
744                         mAdapterView.setSelection(position);
745
746                         // Make the scrollbar appear
747                         if (position > 0) {
748                             mAdapterView.scrollBy(0, 1);
749                             mAdapterView.scrollBy(0, -1);
750                         }
751
752                     } catch (Exception e) {
753                         mAdapterView.setSelection(0);
754                     }
755                 } else {
756                     mAdapterView.setSelection(0);
757                 }
758             }
759         });
760
761     }
762
763     /**
764      * Method that refresh the view data.
765      */
766     public void refresh() {
767         refresh(false);
768     }
769
770     /**
771      * Method that refresh the view data.
772      *
773      * @param restore Restore previous position
774      */
775     public void refresh(boolean restore) {
776         FileSystemObject fso = null;
777         // Try to restore the previous scroll position
778         if (restore) {
779             try {
780                 if (this.mAdapterView != null && this.mAdapter != null) {
781                     int position = this.mAdapterView.getFirstVisiblePosition();
782                     fso = this.mAdapter.getItem(position);
783                 }
784             } catch (Throwable _throw) {/**NON BLOCK**/}
785         }
786         refresh(fso);
787     }
788
789     /**
790      * Method that refresh the view data.
791      *
792      * @param scrollTo Scroll to object
793      */
794     public void refresh(FileSystemObject scrollTo) {
795         //Check that current directory was set
796         if (this.mCurrentDir == null || this.mFiles == null) {
797             return;
798         }
799
800         //Reload data
801         changeCurrentDir(this.mCurrentDir, false, true, false, null, scrollTo);
802     }
803
804     /**
805      * Method that recycles this object
806      */
807     public void recycle() {
808         if (this.mAdapter != null) {
809             this.mAdapter.dispose();
810         }
811     }
812
813     /**
814      * Method that change the view mode.
815      *
816      * @param newMode The new mode
817      */
818     @SuppressWarnings("unchecked")
819     public void changeViewMode(final NavigationLayoutMode newMode) {
820         //Check that it is really necessary change the mode
821         if (this.mCurrentMode != null && this.mCurrentMode.compareTo(newMode) == 0) {
822             return;
823         }
824
825         // If we should set the listview to response to flinger gesture detection
826         boolean useFlinger =
827                 Preferences.getSharedPreferences().getBoolean(
828                         FileManagerSettings.SETTINGS_USE_FLINGER.getId(),
829                         ((Boolean)FileManagerSettings.
830                                 SETTINGS_USE_FLINGER.
831                                 getDefaultValue()).booleanValue());
832
833         //Creates the new layout
834         AdapterView<ListAdapter> newView = null;
835         int itemResourceId = -1;
836         if (newMode.compareTo(NavigationLayoutMode.ICONS) == 0) {
837             newView = (AdapterView<ListAdapter>)inflate(
838                     getContext(), RESOURCE_MODE_ICONS_LAYOUT, null);
839             itemResourceId = RESOURCE_MODE_ICONS_ITEM;
840
841         } else if (newMode.compareTo(NavigationLayoutMode.SIMPLE) == 0) {
842             newView =  (AdapterView<ListAdapter>)inflate(
843                     getContext(), RESOURCE_MODE_SIMPLE_LAYOUT, null);
844             itemResourceId = RESOURCE_MODE_SIMPLE_ITEM;
845
846             // Set the flinger listener (only when navigate)
847             if (this.mNavigationMode.compareTo(NAVIGATION_MODE.BROWSABLE) == 0) {
848                 if (useFlinger && newView instanceof FlingerListView) {
849                     ((FlingerListView)newView).
850                     setOnItemFlingerListener(this.mOnItemFlingerListener);
851                 }
852             }
853
854         } else if (newMode.compareTo(NavigationLayoutMode.DETAILS) == 0) {
855             newView =  (AdapterView<ListAdapter>)inflate(
856                     getContext(), RESOURCE_MODE_DETAILS_LAYOUT, null);
857             itemResourceId = RESOURCE_MODE_DETAILS_ITEM;
858
859             // Set the flinger listener (only when navigate)
860             if (this.mNavigationMode.compareTo(NAVIGATION_MODE.BROWSABLE) == 0) {
861                 if (useFlinger && newView instanceof FlingerListView) {
862                     ((FlingerListView)newView).
863                     setOnItemFlingerListener(this.mOnItemFlingerListener);
864                 }
865             }
866         }
867
868         //Get the current adapter and its adapter list
869         List<FileSystemObject> files = new ArrayList<FileSystemObject>(this.mFiles);
870         final AdapterView<ListAdapter> current =
871                 (AdapterView<ListAdapter>)findViewById(RESOURCE_CURRENT_LAYOUT);
872         FileSystemObjectAdapter adapter =
873                 new FileSystemObjectAdapter(
874                         getContext(),
875                         new ArrayList<FileSystemObject>(),
876                         itemResourceId,
877                         this.mNavigationMode.compareTo(NAVIGATION_MODE.PICKABLE) == 0);
878         adapter.setOnSelectionChangedListener(this);
879
880         //Remove current layout
881         if (current != null) {
882             if (current.getAdapter() != null) {
883                 //Save selected items before dispose adapter
884                 FileSystemObjectAdapter currentAdapter =
885                         ((FileSystemObjectAdapter)current.getAdapter());
886                 adapter.setSelectedItems(currentAdapter.getSelectedItems());
887                 currentAdapter.dispose();
888             }
889             removeView(current);
890         }
891         this.mFiles = files;
892         adapter.addAll(files);
893
894         //Set the adapter
895         this.mAdapter = adapter;
896         newView.setAdapter(this.mAdapter);
897         newView.setOnItemClickListener(NavigationView.this);
898
899         //Add the new layout
900         this.mAdapterView = newView;
901         addView(newView, 0);
902         this.mCurrentMode = newMode;
903
904         // Pick mode doesn't implements the onlongclick
905         if (this.mNavigationMode.compareTo(NAVIGATION_MODE.BROWSABLE) == 0) {
906             this.mAdapterView.setOnItemLongClickListener(this);
907         } else {
908             this.mAdapterView.setOnItemLongClickListener(null);
909         }
910
911         //Save the preference (only in navigation browse mode)
912         if (this.mNavigationMode.compareTo(NAVIGATION_MODE.BROWSABLE) == 0) {
913             try {
914                 Preferences.savePreference(
915                         FileManagerSettings.SETTINGS_LAYOUT_MODE, newMode, true);
916             } catch (Exception ex) {
917                 Log.e(TAG, "Save of view mode preference fails", ex); //$NON-NLS-1$
918             }
919         }
920     }
921
922     /**
923      * Method that removes a {@link FileSystemObject} from the view
924      *
925      * @param fso The file system object
926      */
927     public void removeItem(FileSystemObject fso) {
928         // Delete also from internal list
929         if (fso != null) {
930             int cc = this.mFiles.size()-1;
931             for (int i = cc; i >= 0; i--) {
932                 FileSystemObject f = this.mFiles.get(i);
933                 if (f != null && f.compareTo(fso) == 0) {
934                     this.mFiles.remove(i);
935                     break;
936                 }
937             }
938         }
939         this.mAdapter.remove(fso);
940     }
941
942     /**
943      * Method that removes a file system object from his path from the view
944      *
945      * @param path The file system object path
946      */
947     public void removeItem(String path) {
948         FileSystemObject fso = this.mAdapter.getItem(path);
949         if (fso != null) {
950             this.mAdapter.remove(fso);
951         }
952     }
953
954     /**
955      * Method that returns the current directory.
956      *
957      * @return String The current directory
958      */
959     public String getCurrentDir() {
960         return this.mCurrentDir;
961     }
962
963     /**
964      * Method that changes the current directory of the view.
965      *
966      * @param newDir The new directory location
967      */
968     public void changeCurrentDir(final String newDir) {
969         changeCurrentDir(newDir, true, false, false, null, null);
970     }
971
972     /**
973      * Method that changes the current directory of the view.
974      *
975      * @param newDir The new directory location
976      * @param searchInfo The search information (if calling activity is {@link "SearchActivity"})
977      */
978     public void changeCurrentDir(final String newDir, SearchInfoParcelable searchInfo) {
979         changeCurrentDir(newDir, true, false, false, searchInfo, null);
980     }
981
982     /**
983      * Method that changes the current directory of the view.
984      *
985      * @param newDir The new directory location
986      * @param addToHistory Add the directory to history
987      * @param reload Force the reload of the data
988      * @param useCurrent If this method must use the actual data (for back actions)
989      * @param searchInfo The search information (if calling activity is {@link "SearchActivity"})
990      * @param scrollTo If not null, then listview must scroll to this item
991      */
992     private void changeCurrentDir(
993             final String newDir, final boolean addToHistory,
994             final boolean reload, final boolean useCurrent,
995             final SearchInfoParcelable searchInfo, final FileSystemObject scrollTo) {
996         NavigationTask task = new NavigationTask(useCurrent, addToHistory, reload,
997                 searchInfo, scrollTo);
998         task.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, newDir);
999     }
1000
1001
1002     /**
1003      * Method invoked when a execution ends.
1004      *
1005      * @param files The files obtains from the list
1006      * @param addToHistory If add path to history
1007      * @param isNewHistory If is new history
1008      * @param hasChanged If current directory was changed
1009      * @param searchInfo The search information (if calling activity is {@link "SearchActivity"})
1010      * @param newDir The new directory
1011      * @param scrollTo If not null, then listview must scroll to this item
1012      * @hide
1013      */
1014     void onPostExecuteTask(
1015             List<FileSystemObject> files, boolean addToHistory, boolean isNewHistory,
1016             boolean hasChanged, SearchInfoParcelable searchInfo,
1017             String newDir, final FileSystemObject scrollTo) {
1018         try {
1019             //Check that there is not errors and have some data
1020             if (files == null) {
1021                 return;
1022             }
1023
1024             //Apply user preferences
1025             List<FileSystemObject> sortedFiles =
1026                     FileHelper.applyUserPreferences(files, this.mRestrictions, this.mChRooted);
1027
1028             //Remove parent directory if we are in the root of a chrooted environment
1029             if (this.mChRooted && StorageHelper.isStorageVolume(newDir)) {
1030                 if (files.size() > 0 && files.get(0) instanceof ParentDirectory) {
1031                     files.remove(0);
1032                 }
1033             }
1034
1035             //Add to history?
1036             if (addToHistory && hasChanged && isNewHistory) {
1037                 if (this.mOnHistoryListener != null) {
1038                     //Communicate the need of a history change
1039                     this.mOnHistoryListener.onNewHistory(onSaveState());
1040                 }
1041             }
1042
1043             //Load the data
1044             loadData(sortedFiles);
1045             this.mFiles = sortedFiles;
1046             if (searchInfo != null) {
1047                 searchInfo.setSuccessNavigation(true);
1048             }
1049
1050             //Change the breadcrumb
1051             if (this.mBreadcrumb != null) {
1052                 this.mBreadcrumb.changeBreadcrumbPath(newDir, this.mChRooted);
1053             }
1054
1055             //If scrollTo is null, the position will be set to 0
1056             scrollTo(scrollTo);
1057
1058             //The current directory is now the "newDir"
1059             this.mCurrentDir = newDir;
1060             if (this.mOnDirectoryChangedListener != null) {
1061                 FileSystemObject dir = FileHelper.createFileSystemObject(new File(newDir));
1062                 this.mOnDirectoryChangedListener.onDirectoryChanged(dir);
1063             }
1064         } finally {
1065             //If calling activity is search, then save the search history
1066             if (searchInfo != null) {
1067                 this.mOnHistoryListener.onNewHistory(searchInfo);
1068             }
1069
1070             //End of loading data
1071             try {
1072                 NavigationView.this.mBreadcrumb.endLoading();
1073             } catch (Throwable ex) {
1074                 /**NON BLOCK**/
1075             }
1076         }
1077     }
1078
1079     /**
1080      * Method that loads the files in the adapter.
1081      *
1082      * @param files The files to load in the adapter
1083      * @hide
1084      */
1085     @SuppressWarnings("unchecked")
1086     private void loadData(final List<FileSystemObject> files) {
1087         //Notify data to adapter view
1088         final AdapterView<ListAdapter> view =
1089                 (AdapterView<ListAdapter>)findViewById(RESOURCE_CURRENT_LAYOUT);
1090         FileSystemObjectAdapter adapter = (FileSystemObjectAdapter)view.getAdapter();
1091         adapter.setNotifyOnChange(false);
1092         adapter.clear();
1093         adapter.addAll(files);
1094         adapter.notifyDataSetChanged();
1095     }
1096
1097     /**
1098      * {@inheritDoc}
1099      */
1100     @Override
1101     public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
1102         // Different actions depending on user preference
1103
1104         // Get the adapter and the fso
1105         FileSystemObjectAdapter adapter = ((FileSystemObjectAdapter)parent.getAdapter());
1106         FileSystemObject fso = adapter.getItem(position);
1107
1108         // Parent directory hasn't actions
1109         if (fso instanceof ParentDirectory) {
1110             return false;
1111         }
1112
1113         // Pick mode doesn't implements the onlongclick
1114         if (this.mNavigationMode.compareTo(NAVIGATION_MODE.PICKABLE) == 0) {
1115             return false;
1116         }
1117
1118         onRequestMenu(fso);
1119         return true; //Always consume the event
1120     }
1121
1122     /**
1123      * Method that opens or navigates to the {@link FileSystemObject}
1124      *
1125      * @param fso The file system object
1126      */
1127     public void open(FileSystemObject fso) {
1128         open(fso, null);
1129     }
1130
1131     /**
1132      * Method that opens or navigates to the {@link FileSystemObject}
1133      *
1134      * @param fso The file system object
1135      * @param searchInfo The search info
1136      */
1137     public void open(FileSystemObject fso, SearchInfoParcelable searchInfo) {
1138         // If is a folder, then navigate to
1139         if (FileHelper.isDirectory(fso)) {
1140             changeCurrentDir(fso.getFullPath(), searchInfo);
1141         } else {
1142             // Open the file with the preferred registered app
1143             IntentsActionPolicy.openFileSystemObject(getContext(), fso, false, null, null);
1144         }
1145     }
1146
1147     /**
1148      * {@inheritDoc}
1149      */
1150     @Override
1151     public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
1152         try {
1153             FileSystemObject fso = ((FileSystemObjectAdapter)parent.getAdapter()).getItem(position);
1154             if (fso instanceof ParentDirectory) {
1155                 changeCurrentDir(fso.getParent(), true, false, false, null, null);
1156                 return;
1157             } else if (fso instanceof Directory) {
1158                 changeCurrentDir(fso.getFullPath(), true, false, false, null, null);
1159                 return;
1160             } else if (fso instanceof Symlink) {
1161                 Symlink symlink = (Symlink)fso;
1162                 if (symlink.getLinkRef() != null && symlink.getLinkRef() instanceof Directory) {
1163                     changeCurrentDir(
1164                             symlink.getLinkRef().getFullPath(), true, false, false, null, null);
1165                     return;
1166                 }
1167
1168                 // Open the link ref
1169                 fso = symlink.getLinkRef();
1170             }
1171
1172             // Open the file (edit or pick)
1173             if (this.mNavigationMode.compareTo(NAVIGATION_MODE.BROWSABLE) == 0) {
1174                 // Open the file with the preferred registered app
1175                 IntentsActionPolicy.openFileSystemObject(getContext(), fso, false, null, null);
1176             } else {
1177                 // Request a file pick selection
1178                 if (this.mOnFilePickedListener != null) {
1179                     this.mOnFilePickedListener.onFilePicked(fso);
1180                 }
1181             }
1182         } catch (Throwable ex) {
1183             ExceptionUtil.translateException(getContext(), ex);
1184         }
1185     }
1186
1187     /**
1188      * {@inheritDoc}
1189      */
1190     @Override
1191     public void onRequestRefresh(Object o, boolean clearSelection) {
1192         if (o instanceof FileSystemObject) {
1193             refresh((FileSystemObject)o);
1194         } else if (o == null) {
1195             refresh();
1196         }
1197         if (clearSelection) {
1198             onDeselectAll();
1199         }
1200     }
1201
1202     /**
1203      * {@inheritDoc}
1204      */
1205     @Override
1206     public void onRequestRemove(Object o, boolean clearSelection) {
1207         if (o != null && o instanceof FileSystemObject) {
1208             removeItem((FileSystemObject)o);
1209         } else {
1210             onRequestRefresh(null, clearSelection);
1211         }
1212         if (clearSelection) {
1213             onDeselectAll();
1214         }
1215     }
1216
1217     /**
1218      * {@inheritDoc}
1219      */
1220     @Override
1221     public void onNavigateTo(Object o) {
1222         // Ignored
1223     }
1224
1225     /**
1226      * {@inheritDoc}
1227      */
1228     @Override
1229     public void onBreadcrumbItemClick(BreadcrumbItem item) {
1230         changeCurrentDir(item.getItemPath(), true, true, false, null, null);
1231     }
1232
1233     /**
1234      * {@inheritDoc}
1235      */
1236     @Override
1237     public void onSelectionChanged(final List<FileSystemObject> selectedItems) {
1238         if (this.mOnNavigationSelectionChangedListener != null) {
1239             this.mOnNavigationSelectionChangedListener.onSelectionChanged(this, selectedItems);
1240         }
1241     }
1242
1243     /**
1244      * Method invoked when a request to show the menu associated
1245      * with an item is started.
1246      *
1247      * @param item The item for which the request was started
1248      */
1249     public void onRequestMenu(final FileSystemObject item) {
1250         if (this.mOnNavigationRequestMenuListener != null) {
1251             this.mOnNavigationRequestMenuListener.onRequestMenu(this, item);
1252         }
1253     }
1254
1255     /**
1256      * {@inheritDoc}
1257      */
1258     @Override
1259     public void onToggleSelection(FileSystemObject fso) {
1260         if (this.mAdapter != null) {
1261             this.mAdapter.toggleSelection(fso);
1262         }
1263     }
1264
1265     /**
1266      * {@inheritDoc}
1267      */
1268     @Override
1269     public void onDeselectAll() {
1270         if (this.mAdapter != null) {
1271             this.mAdapter.deselectedAll();
1272         }
1273     }
1274
1275     /**
1276      * {@inheritDoc}
1277      */
1278     @Override
1279     public void onSelectAllVisibleItems() {
1280         if (this.mAdapter != null) {
1281             this.mAdapter.selectedAllVisibleItems();
1282         }
1283     }
1284
1285     /**
1286      * {@inheritDoc}
1287      */
1288     @Override
1289     public void onDeselectAllVisibleItems() {
1290         if (this.mAdapter != null) {
1291             this.mAdapter.deselectedAllVisibleItems();
1292         }
1293     }
1294
1295     /**
1296      * {@inheritDoc}
1297      */
1298     @Override
1299     public List<FileSystemObject> onRequestSelectedFiles() {
1300         return this.getSelectedFiles();
1301     }
1302
1303     /**
1304      * {@inheritDoc}
1305      */
1306     @Override
1307     public List<FileSystemObject> onRequestCurrentItems() {
1308         return this.getFiles();
1309     }
1310
1311     /**
1312      * {@inheritDoc}
1313      */
1314     @Override
1315     public String onRequestCurrentDir() {
1316         return this.mCurrentDir;
1317     }
1318
1319     /**
1320      * Method that creates a ChRooted environment, protecting the user to break anything
1321      * in the device
1322      * @hide
1323      */
1324     public void createChRooted() {
1325         // If we are in a ChRooted environment, then do nothing
1326         if (this.mChRooted) return;
1327         this.mChRooted = true;
1328
1329         //Change to first storage volume
1330         StorageVolume[] volumes =
1331                 StorageHelper.getStorageVolumes(getContext());
1332         if (volumes != null && volumes.length > 0) {
1333             changeCurrentDir(volumes[0].getPath(), false, true, false, null, null);
1334         }
1335     }
1336
1337     /**
1338      * Method that exits from a ChRooted environment
1339      * @hide
1340      */
1341     public void exitChRooted() {
1342         // If we aren't in a ChRooted environment, then do nothing
1343         if (!this.mChRooted) return;
1344         this.mChRooted = false;
1345
1346         // Refresh
1347         refresh();
1348     }
1349
1350     /**
1351      * Method that ensures that the user don't go outside the ChRooted environment
1352      *
1353      * @param newDir The new directory to navigate to
1354      * @return String
1355      */
1356     private String checkChRootedNavigation(String newDir) {
1357         // If we aren't in ChRooted environment, then there is nothing to check
1358         if (!this.mChRooted) return newDir;
1359
1360         // Check if the path is owned by one of the storage volumes
1361         if (!StorageHelper.isPathInStorageVolume(newDir)) {
1362             StorageVolume[] volumes = StorageHelper.getStorageVolumes(getContext());
1363             if (volumes != null && volumes.length > 0) {
1364                 return volumes[0].getPath();
1365             }
1366         }
1367         return newDir;
1368     }
1369
1370     /**
1371      * Method that applies the current theme to the activity
1372      */
1373     public void applyTheme() {
1374         //- Breadcrumb
1375         if (getBreadcrumb() != null) {
1376             getBreadcrumb().applyTheme();
1377         }
1378
1379         //- Redraw the adapter view
1380         Theme theme = ThemeManager.getCurrentTheme(getContext());
1381         theme.setBackgroundDrawable(getContext(), this, "background_drawable"); //$NON-NLS-1$
1382         if (this.mAdapter != null) {
1383             this.mAdapter.notifyThemeChanged();
1384         }
1385         if (this.mAdapterView instanceof ListView) {
1386             ((ListView)this.mAdapterView).setDivider(
1387                     theme.getDrawable(getContext(), "horizontal_divider_drawable")); //$NON-NLS-1$
1388         }
1389         refresh();
1390     }
1391
1392 }