OSDN Git Service

Remove console selection (Issue #17) - Part I (UX)
[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.widget.AdapterView;
29 import android.widget.ListAdapter;
30 import android.widget.RelativeLayout;
31 import android.widget.Toast;
32
33 import com.cyanogenmod.filemanager.FileManagerApplication;
34 import com.cyanogenmod.filemanager.R;
35 import com.cyanogenmod.filemanager.adapters.FileSystemObjectAdapter;
36 import com.cyanogenmod.filemanager.adapters.FileSystemObjectAdapter.OnSelectionChangedListener;
37 import com.cyanogenmod.filemanager.console.ConsoleAllocException;
38 import com.cyanogenmod.filemanager.listeners.OnHistoryListener;
39 import com.cyanogenmod.filemanager.listeners.OnRequestRefreshListener;
40 import com.cyanogenmod.filemanager.listeners.OnSelectionListener;
41 import com.cyanogenmod.filemanager.model.Directory;
42 import com.cyanogenmod.filemanager.model.FileSystemObject;
43 import com.cyanogenmod.filemanager.model.ParentDirectory;
44 import com.cyanogenmod.filemanager.model.Symlink;
45 import com.cyanogenmod.filemanager.parcelables.NavigationViewInfoParcelable;
46 import com.cyanogenmod.filemanager.parcelables.SearchInfoParcelable;
47 import com.cyanogenmod.filemanager.preferences.FileManagerSettings;
48 import com.cyanogenmod.filemanager.preferences.NavigationLayoutMode;
49 import com.cyanogenmod.filemanager.preferences.ObjectIdentifier;
50 import com.cyanogenmod.filemanager.preferences.Preferences;
51 import com.cyanogenmod.filemanager.ui.policy.IntentsActionPolicy;
52 import com.cyanogenmod.filemanager.util.CommandHelper;
53 import com.cyanogenmod.filemanager.util.DialogHelper;
54 import com.cyanogenmod.filemanager.util.ExceptionUtil;
55 import com.cyanogenmod.filemanager.util.FileHelper;
56 import com.cyanogenmod.filemanager.util.MimeTypeHelper;
57 import com.cyanogenmod.filemanager.util.StorageHelper;
58
59 import java.util.ArrayList;
60 import java.util.List;
61
62 /**
63  * The file manager implementation view (contains the graphical representation and the input
64  * management for a file manager; shows the folders/files, the mode view, react touch events,
65  * navigate, ...).
66  */
67 public class NavigationView extends RelativeLayout implements
68     AdapterView.OnItemClickListener, AdapterView.OnItemLongClickListener,
69     BreadcrumbListener, OnSelectionChangedListener, OnSelectionListener, OnRequestRefreshListener {
70
71     /**
72      * An interface to communicate selection changes events.
73      */
74     public interface OnNavigationSelectionChangedListener {
75         /**
76          * Method invoked when the selection changed.
77          *
78          * @param navView The navigation view that generate the event
79          * @param selectedItems The new selected items
80          */
81         void onSelectionChanged(NavigationView navView, List<FileSystemObject> selectedItems);
82     }
83
84     /**
85      * An interface to communicate a request for show the menu associated
86      * with an item.
87      */
88     public interface OnNavigationRequestMenuListener {
89         /**
90          * Method invoked when a request to show the menu associated
91          * with an item is started.
92          *
93          * @param navView The navigation view that generate the event
94          * @param item The item for which the request was started
95          */
96         void onRequestMenu(NavigationView navView, FileSystemObject item);
97     }
98
99     /**
100      * An interface to communicate a request when the user choose a file.
101      */
102     public interface OnFilePickedListener {
103         /**
104          * Method invoked when a request when the user choose a file.
105          *
106          * @param item The item choose
107          */
108         void onFilePicked(FileSystemObject item);
109     }
110
111     /**
112      * The navigation view mode
113      * @hide
114      */
115     public enum NAVIGATION_MODE {
116         /**
117          * The navigation view acts as a browser, and allow open files itself.
118          */
119         BROWSABLE,
120         /**
121          * The navigation view acts as a picker of files
122          */
123         PICKABLE,
124     }
125
126     private static final String TAG = "NavigationView"; //$NON-NLS-1$
127
128     private int mId;
129     private String mCurrentDir;
130     private NavigationLayoutMode mCurrentMode;
131     /**
132      * @hide
133      */
134     List<FileSystemObject> mFiles;
135     private FileSystemObjectAdapter mAdapter;
136
137     private final Object mSync = new Object();
138
139     private OnHistoryListener mOnHistoryListener;
140     private OnNavigationSelectionChangedListener mOnNavigationSelectionChangedListener;
141     private OnNavigationRequestMenuListener mOnNavigationRequestMenuListener;
142     private OnFilePickedListener mOnFilePickedListener;
143
144     private boolean mChRooted;
145
146     private NAVIGATION_MODE mNavigationMode;
147
148     private String mMimeType = MimeTypeHelper.ALL_MIME_TYPES;
149
150     /**
151      * @hide
152      */
153     Breadcrumb mBreadcrumb;
154     /**
155      * @hide
156      */
157     NavigationCustomTitleView mTitle;
158     /**
159      * @hide
160      */
161     AdapterView<?> mAdapterView;
162
163     //The layout for icons mode
164     private static final int RESOURCE_MODE_ICONS_LAYOUT = R.layout.navigation_view_icons;
165     private static final int RESOURCE_MODE_ICONS_ITEM = R.layout.navigation_view_icons_item;
166     //The layout for simple mode
167     private static final int RESOURCE_MODE_SIMPLE_LAYOUT = R.layout.navigation_view_simple;
168     private static final int RESOURCE_MODE_SIMPLE_ITEM = R.layout.navigation_view_simple_item;
169     //The layout for details mode
170     private static final int RESOURCE_MODE_DETAILS_LAYOUT = R.layout.navigation_view_details;
171     private static final int RESOURCE_MODE_DETAILS_ITEM = R.layout.navigation_view_details_item;
172
173     //The current layout identifier (is shared for all the mode layout)
174     private static final int RESOURCE_CURRENT_LAYOUT = R.id.navigation_view_layout;
175
176     /**
177      * Constructor of <code>NavigationView</code>.
178      *
179      * @param context The current context
180      * @param attrs The attributes of the XML tag that is inflating the view.
181      */
182     public NavigationView(Context context, AttributeSet attrs) {
183         super(context, attrs);
184         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Navigable);
185         try {
186             init(a);
187         } finally {
188             a.recycle();
189         }
190     }
191
192     /**
193      * Constructor of <code>NavigationView</code>.
194      *
195      * @param context The current context
196      * @param attrs The attributes of the XML tag that is inflating the view.
197      * @param defStyle The default style to apply to this view. If 0, no style
198      *        will be applied (beyond what is included in the theme). This may
199      *        either be an attribute resource, whose value will be retrieved
200      *        from the current theme, or an explicit style resource.
201      */
202     public NavigationView(Context context, AttributeSet attrs, int defStyle) {
203         super(context, attrs, defStyle);
204         TypedArray a = context.obtainStyledAttributes(
205                 attrs, R.styleable.Navigable, defStyle, 0);
206         try {
207             init(a);
208         } finally {
209             a.recycle();
210         }
211     }
212
213     /**
214      * Invoked when the instance need to be saved.
215      *
216      * @return NavigationViewInfoParcelable The serialized info
217      */
218     public NavigationViewInfoParcelable onSaveState() {
219         //Return the persistent the data
220         NavigationViewInfoParcelable parcel = new NavigationViewInfoParcelable();
221         parcel.setId(this.mId);
222         parcel.setCurrentDir(this.mCurrentDir);
223         parcel.setChRooted(this.mChRooted);
224         parcel.setSelectedFiles(this.mAdapter.getSelectedItems());
225         parcel.setFiles(this.mFiles);
226         return parcel;
227     }
228
229     /**
230      * Invoked when the instance need to be restored.
231      *
232      * @param info The serialized info
233      */
234     public void onRestoreState(NavigationViewInfoParcelable info) {
235         //Restore the data
236         this.mId = info.getId();
237         this.mCurrentDir = info.getCurrentDir();
238         this.mChRooted = info.getChRooted();
239         this.mFiles = info.getFiles();
240         this.mAdapter.setSelectedItems(info.getSelectedFiles());
241
242         //Update the views
243         refresh();
244     }
245
246     /**
247      * Method that initializes the view. This method loads all the necessary
248      * information and create an appropriate layout for the view.
249      *
250      * @param tarray The type array
251      */
252     private void init(TypedArray tarray) {
253         // Retrieve the mode
254         this.mNavigationMode = NAVIGATION_MODE.BROWSABLE;
255         int mode = tarray.getInteger(
256                                 R.styleable.Navigable_navigation,
257                                 NAVIGATION_MODE.BROWSABLE.ordinal());
258         if (mode >= 0 && mode < NAVIGATION_MODE.values().length) {
259             this.mNavigationMode = NAVIGATION_MODE.values()[mode];
260         }
261
262         //Initialize variables
263         this.mFiles = new ArrayList<FileSystemObject>();
264
265         // Is ChRooted environment?
266         if (this.mNavigationMode.compareTo(NAVIGATION_MODE.PICKABLE) == 0) {
267             // Pick mode is always ChRooted
268             this.mChRooted = true;
269         } else {
270             this.mChRooted = !FileManagerApplication.isAdvancedMode();
271         }
272
273         //Retrieve the default configuration
274         if (this.mNavigationMode.compareTo(NAVIGATION_MODE.BROWSABLE) == 0) {
275             SharedPreferences preferences = Preferences.getSharedPreferences();
276             int viewMode = preferences.getInt(
277                     FileManagerSettings.SETTINGS_LAYOUT_MODE.getId(),
278                     ((ObjectIdentifier)FileManagerSettings.
279                             SETTINGS_LAYOUT_MODE.getDefaultValue()).getId());
280             changeViewMode(NavigationLayoutMode.fromId(viewMode));
281         } else {
282             // Pick mode has always a details layout
283             changeViewMode(NavigationLayoutMode.DETAILS);
284         }
285     }
286
287     /**
288      * Method that returns the mime/type used by this class. Only the files with this mime/type
289      * are shown.
290      *
291      * @return String The mime/type
292      */
293     public String getMimeType() {
294         return this.mMimeType;
295     }
296
297     /**
298      * Method that sets the mime/type used by this class. Only the files with this mime/type
299      * are shown.
300      *
301      * @param mimeType String The mime/type
302      */
303     public void setMimeType(String mimeType) {
304         this.mMimeType = mimeType;
305     }
306
307     /**
308      * Method that returns the current file list of the navigation view.
309      *
310      * @return List<FileSystemObject> The current file list of the navigation view
311      */
312     public List<FileSystemObject> getFiles() {
313         if (this.mFiles == null) {
314             return null;
315         }
316         return new ArrayList<FileSystemObject>(this.mFiles);
317     }
318
319     /**
320      * Method that returns the current file list of the navigation view.
321      *
322      * @return List<FileSystemObject> The current file list of the navigation view
323      */
324     public List<FileSystemObject> getSelectedFiles() {
325         if (this.mAdapter != null && this.mAdapter.getSelectedItems() != null) {
326             return new ArrayList<FileSystemObject>(this.mAdapter.getSelectedItems());
327         }
328         return null;
329     }
330
331     /**
332      * Method that returns the custom title fragment associated with this navigation view.
333      *
334      * @return NavigationCustomTitleView The custom title view fragment
335      */
336     public NavigationCustomTitleView getCustomTitle() {
337         return this.mTitle;
338     }
339
340     /**
341      * Method that associates the custom title fragment with this navigation view.
342      *
343      * @param title The custom title view fragment
344      */
345     public void setCustomTitle(NavigationCustomTitleView title) {
346         this.mTitle = title;
347     }
348
349     /**
350      * Method that returns the breadcrumb associated with this navigation view.
351      *
352      * @return Breadcrumb The breadcrumb view fragment
353      */
354     public Breadcrumb getBreadcrumb() {
355         return this.mBreadcrumb;
356     }
357
358     /**
359      * Method that associates the breadcrumb with this navigation view.
360      *
361      * @param breadcrumb The breadcrumb view fragment
362      */
363     public void setBreadcrumb(Breadcrumb breadcrumb) {
364         this.mBreadcrumb = breadcrumb;
365         this.mBreadcrumb.addBreadcrumbListener(this);
366     }
367
368     /**
369      * Method that sets the listener for communicate history changes.
370      *
371      * @param onHistoryListener The listener for communicate history changes
372      */
373     public void setOnHistoryListener(OnHistoryListener onHistoryListener) {
374         this.mOnHistoryListener = onHistoryListener;
375     }
376
377     /**
378      * Method that sets the listener which communicates selection changes.
379      *
380      * @param onNavigationSelectionChangedListener The listener reference
381      */
382     public void setOnNavigationSelectionChangedListener(
383             OnNavigationSelectionChangedListener onNavigationSelectionChangedListener) {
384         this.mOnNavigationSelectionChangedListener = onNavigationSelectionChangedListener;
385     }
386
387     /**
388      * Method that sets the listener for menu item requests.
389      *
390      * @param onNavigationRequestMenuListener The listener reference
391      */
392     public void setOnNavigationOnRequestMenuListener(
393             OnNavigationRequestMenuListener onNavigationRequestMenuListener) {
394         this.mOnNavigationRequestMenuListener = onNavigationRequestMenuListener;
395     }
396
397     /**
398      * @return the mOnFilePickedListener
399      */
400     public OnFilePickedListener getOnFilePickedListener() {
401         return this.mOnFilePickedListener;
402     }
403
404     /**
405      * Method that sets the listener for picked items
406      *
407      * @param onFilePickedListener The listener reference
408      */
409     public void setOnFilePickedListener(OnFilePickedListener onFilePickedListener) {
410         this.mOnFilePickedListener = onFilePickedListener;
411     }
412
413     /**
414      * Method that forces the view to scroll to the file system object passed.
415      *
416      * @param fso The file system object
417      */
418     public void scrollTo(FileSystemObject fso) {
419         if (fso != null) {
420             try {
421                 int position = this.mAdapter.getPosition(fso);
422                 this.mAdapterView.setSelection(position);
423             } catch (Exception e) {
424                 this.mAdapterView.setSelection(0);
425             }
426         }
427     }
428
429     /**
430      * Method that refresh the view data.
431      */
432     public void refresh() {
433         FileSystemObject fso = null;
434         // Try to restore the previous scroll position
435         try {
436             if (this.mAdapterView != null && this.mAdapter != null) {
437                 int position = this.mAdapterView.getFirstVisiblePosition();
438                 fso = this.mAdapter.getItem(position);
439             }
440         } catch (Throwable _throw) {/**NON BLOCK**/}
441         refresh(fso);
442     }
443
444     /**
445      * Method that refresh the view data.
446      *
447      * @param scrollTo Scroll to object
448      */
449     public void refresh(FileSystemObject scrollTo) {
450         //Check that current directory was set
451         if (this.mCurrentDir == null || this.mFiles == null) {
452             return;
453         }
454
455         //Reload data
456         changeCurrentDir(this.mCurrentDir, false, true, false, null, scrollTo);
457     }
458
459     /**
460      * Method that change the view mode.
461      *
462      * @param newMode The new mode
463      */
464     @SuppressWarnings({ "unchecked", "null" })
465     public void changeViewMode(final NavigationLayoutMode newMode) {
466         synchronized (this.mSync) {
467             //Check that it is really necessary change the mode
468             if (this.mCurrentMode != null && this.mCurrentMode.compareTo(newMode) == 0) {
469                 return;
470             }
471
472             //Creates the new layout
473             AdapterView<ListAdapter> newView = null;
474             int itemResourceId = -1;
475             if (newMode.compareTo(NavigationLayoutMode.ICONS) == 0) {
476                 newView = (AdapterView<ListAdapter>)inflate(
477                         getContext(), RESOURCE_MODE_ICONS_LAYOUT, null);
478                 itemResourceId = RESOURCE_MODE_ICONS_ITEM;
479             } else if (newMode.compareTo(NavigationLayoutMode.SIMPLE) == 0) {
480                 newView =  (AdapterView<ListAdapter>)inflate(
481                         getContext(), RESOURCE_MODE_SIMPLE_LAYOUT, null);
482                 itemResourceId = RESOURCE_MODE_SIMPLE_ITEM;
483             } else if (newMode.compareTo(NavigationLayoutMode.DETAILS) == 0) {
484                 newView =  (AdapterView<ListAdapter>)inflate(
485                         getContext(), RESOURCE_MODE_DETAILS_LAYOUT, null);
486                 itemResourceId = RESOURCE_MODE_DETAILS_ITEM;
487             }
488
489             //Get the current adapter and its adapter list
490             List<FileSystemObject> files = new ArrayList<FileSystemObject>(this.mFiles);
491             final AdapterView<ListAdapter> current =
492                     (AdapterView<ListAdapter>)findViewById(RESOURCE_CURRENT_LAYOUT);
493             FileSystemObjectAdapter adapter =
494                     new FileSystemObjectAdapter(
495                             getContext(),
496                             new ArrayList<FileSystemObject>(),
497                             itemResourceId,
498                             this.mNavigationMode.compareTo(NAVIGATION_MODE.PICKABLE) == 0);
499             adapter.setOnSelectionChangedListener(this);
500
501             //Remove current layout
502             if (current != null) {
503                 if (current.getAdapter() != null) {
504                     //Save selected items before dispose adapter
505                     FileSystemObjectAdapter currentAdapter =
506                             ((FileSystemObjectAdapter)current.getAdapter());
507                     adapter.setSelectedItems(currentAdapter.getSelectedItems());
508                     currentAdapter.dispose();
509                 }
510                 removeView(current);
511             }
512             this.mFiles = files;
513             adapter.addAll(files);
514             adapter.notifyDataSetChanged();
515
516             //Set the adapter
517             this.mAdapter = adapter;
518             newView.setAdapter(this.mAdapter);
519             newView.setOnItemClickListener(NavigationView.this);
520
521             //Add the new layout
522             this.mAdapterView = newView;
523             addView(newView, 0);
524             this.mCurrentMode = newMode;
525
526             // Pick mode doesn't implements the onlongclick
527             if (this.mNavigationMode.compareTo(NAVIGATION_MODE.BROWSABLE) == 0) {
528                 this.mAdapterView.setOnItemLongClickListener(this);
529             } else {
530                 this.mAdapterView.setOnItemLongClickListener(null);
531             }
532
533             //Save the preference (only in navigation browse mode)
534             if (this.mNavigationMode.compareTo(NAVIGATION_MODE.BROWSABLE) == 0) {
535                 try {
536                     Preferences.savePreference(
537                             FileManagerSettings.SETTINGS_LAYOUT_MODE, newMode, true);
538                 } catch (Exception ex) {
539                     Log.e(TAG, "Save of view mode preference fails", ex); //$NON-NLS-1$
540                 }
541             }
542         }
543     }
544
545     /**
546      * Method that removes a {@link FileSystemObject} from the view
547      *
548      * @param fso The file system object
549      */
550     public void removeItem(FileSystemObject fso) {
551         this.mAdapter.remove(fso);
552         this.mAdapter.notifyDataSetChanged();
553     }
554
555     /**
556      * Method that removes a file system object from his path from the view
557      *
558      * @param path The file system object path
559      */
560     public void removeItem(String path) {
561         FileSystemObject fso = this.mAdapter.getItem(path);
562         if (fso != null) {
563             this.mAdapter.remove(fso);
564             this.mAdapter.notifyDataSetChanged();
565         }
566     }
567
568     /**
569      * Method that returns the current directory.
570      *
571      * @return String The current directory
572      */
573     public String getCurrentDir() {
574         return this.mCurrentDir;
575     }
576
577     /**
578      * Method that changes the current directory of the view.
579      *
580      * @param newDir The new directory location
581      */
582     public void changeCurrentDir(final String newDir) {
583         changeCurrentDir(newDir, true, false, false, null, null);
584     }
585
586     /**
587      * Method that changes the current directory of the view.
588      *
589      * @param newDir The new directory location
590      * @param searchInfo The search information (if calling activity is {@link "SearchActivity"})
591      */
592     public void changeCurrentDir(final String newDir, SearchInfoParcelable searchInfo) {
593         changeCurrentDir(newDir, true, false, false, searchInfo, null);
594     }
595
596     /**
597      * Method that changes the current directory of the view.
598      *
599      * @param newDir The new directory location
600      * @param addToHistory Add the directory to history
601      * @param reload Force the reload of the data
602      * @param useCurrent If this method must use the actual data (for back actions)
603      * @param searchInfo The search information (if calling activity is {@link "SearchActivity"})
604      * @param scrollTo If not null, then listview must scroll to this item
605      */
606     private void changeCurrentDir(
607             final String newDir, final boolean addToHistory,
608             final boolean reload, final boolean useCurrent,
609             final SearchInfoParcelable searchInfo, final FileSystemObject scrollTo) {
610
611         // Check navigation security (don't allow to go outside the ChRooted environment if one
612         // is created)
613         final String fNewDir = checkChRootedNavigation(newDir);
614
615         synchronized (this.mSync) {
616             //Check that it is really necessary change the directory
617             if (!reload && this.mCurrentDir != null && this.mCurrentDir.compareTo(fNewDir) == 0) {
618                 return;
619             }
620
621             final boolean hasChanged =
622                     !(this.mCurrentDir != null && this.mCurrentDir.compareTo(fNewDir) == 0);
623             final boolean isNewHistory = (this.mCurrentDir != null);
624
625             //Execute the listing in a background process
626             AsyncTask<String, Integer, List<FileSystemObject>> task =
627                     new AsyncTask<String, Integer, List<FileSystemObject>>() {
628                         /**
629                          * {@inheritDoc}
630                          */
631                         @Override
632                         protected List<FileSystemObject> doInBackground(String... params) {
633                             try {
634                                 //Reset the custom title view and returns to breadcrumb
635                                 if (NavigationView.this.mTitle != null) {
636                                     NavigationView.this.mTitle.post(new Runnable() {
637                                         @Override
638                                         public void run() {
639                                             try {
640                                                 NavigationView.this.mTitle.restoreView();
641                                             } catch (Exception e) {
642                                                 e.printStackTrace();
643                                             }
644                                         }
645                                     });
646                                 }
647
648
649                                 //Start of loading data
650                                 if (NavigationView.this.mBreadcrumb != null) {
651                                     try {
652                                         NavigationView.this.mBreadcrumb.startLoading();
653                                     } catch (Throwable ex) {
654                                         /**NON BLOCK**/
655                                     }
656                                 }
657
658                                 //Get the files, resolve links and apply configuration
659                                 //(sort, hidden, ...)
660                                 List<FileSystemObject> files = NavigationView.this.mFiles;
661                                 if (!useCurrent) {
662                                     files = CommandHelper.listFiles(getContext(), fNewDir, null);
663                                 }
664                                 return files;
665                             } catch (final ConsoleAllocException e) {
666                                 //Show exception and exists
667                                 NavigationView.this.post(new Runnable() {
668                                     @Override
669                                     public void run() {
670                                         Context ctx = getContext();
671                                         Log.e(TAG, ctx.getString(
672                                                 R.string.msgs_cant_create_console), e);
673                                         DialogHelper.showToast(ctx,
674                                                 R.string.msgs_cant_create_console,
675                                                 Toast.LENGTH_LONG);
676                                         ((Activity)ctx).finish();
677                                     }
678                                 });
679                                 return null;
680
681                             } catch (Exception ex) {
682                                 //End of loading data
683                                 if (NavigationView.this.mBreadcrumb != null) {
684                                     try {
685                                         NavigationView.this.mBreadcrumb.endLoading();
686                                     } catch (Throwable ex2) {
687                                         /**NON BLOCK**/
688                                     }
689                                 }
690
691                                 //Capture exception
692                                 ExceptionUtil.attachAsyncTask(
693                                     ex,
694                                     new AsyncTask<Object, Integer, Boolean>() {
695                                         @Override
696                                         @SuppressWarnings("unchecked")
697                                         protected Boolean doInBackground(Object... taskParams) {
698                                             final List<FileSystemObject> files =
699                                                     (List<FileSystemObject>)taskParams[0];
700                                             NavigationView.this.mAdapterView.post(
701                                                     new Runnable() {
702                                                         @Override
703                                                         public void run() {
704                                                             onPostExecuteTask(
705                                                                     files, addToHistory,
706                                                                     isNewHistory, hasChanged,
707                                                                     searchInfo, fNewDir, scrollTo);
708                                                         }
709                                                     });
710                                             return Boolean.TRUE;
711                                         }
712
713                                     });
714                                 ExceptionUtil.translateException(getContext(), ex);
715                             }
716                             return null;
717                         }
718
719                         /**
720                          * {@inheritDoc}
721                          */
722                         @Override
723                         protected void onPostExecute(List<FileSystemObject> files) {
724                             onPostExecuteTask(
725                                     files, addToHistory, isNewHistory,
726                                     hasChanged, searchInfo, fNewDir, scrollTo);
727                         }
728                    };
729             task.execute(fNewDir);
730         }
731     }
732
733
734     /**
735      * Method invoked when a execution ends.
736      *
737      * @param files The files obtains from the list
738      * @param addToHistory If add path to history
739      * @param isNewHistory If is new history
740      * @param hasChanged If current directory was changed
741      * @param searchInfo The search information (if calling activity is {@link "SearchActivity"})
742      * @param newDir The new directory
743      * @param scrollTo If not null, then listview must scroll to this item
744      * @hide
745      */
746     void onPostExecuteTask(
747             List<FileSystemObject> files, boolean addToHistory, boolean isNewHistory,
748             boolean hasChanged, SearchInfoParcelable searchInfo,
749             String newDir, final FileSystemObject scrollTo) {
750         try {
751             //Check that there is not errors and have some data
752             if (files == null) {
753                 return;
754             }
755
756             //Apply user preferences
757             List<FileSystemObject> sortedFiles =
758                     FileHelper.applyUserPreferences(files, this.mMimeType, this.mChRooted);
759
760             //Remove parent directory if we are in the root of a chrooted environment
761             if (this.mChRooted && StorageHelper.isStorageVolume(newDir)) {
762                 if (files.size() > 0 && files.get(0) instanceof ParentDirectory) {
763                     files.remove(0);
764                 }
765             }
766
767             //Load the data
768             loadData(sortedFiles);
769             NavigationView.this.mFiles = sortedFiles;
770             if (searchInfo != null) {
771                 searchInfo.setSuccessNavigation(true);
772             }
773
774             //Add to history?
775             if (addToHistory && hasChanged && isNewHistory) {
776                 if (NavigationView.this.mOnHistoryListener != null) {
777                     //Communicate the need of a history change
778                     NavigationView.this.mOnHistoryListener.onNewHistory(onSaveState());
779                 }
780             }
781
782             //Change the breadcrumb
783             if (NavigationView.this.mBreadcrumb != null) {
784                 NavigationView.this.mBreadcrumb.changeBreadcrumbPath(newDir, this.mChRooted);
785             }
786
787             //Scroll to object?
788             if (scrollTo != null) {
789                 scrollTo(scrollTo);
790             }
791
792             //The current directory is now the "newDir"
793             NavigationView.this.mCurrentDir = newDir;
794
795         } finally {
796             //If calling activity is search, then save the search history
797             if (searchInfo != null) {
798                 NavigationView.this.mOnHistoryListener.onNewHistory(searchInfo);
799             }
800
801             //End of loading data
802             try {
803                 NavigationView.this.mBreadcrumb.endLoading();
804             } catch (Throwable ex) {
805                 /**NON BLOCK**/
806             }
807         }
808     }
809
810     /**
811      * Method that loads the files in the adapter.
812      *
813      * @param files The files to load in the adapter
814      * @hide
815      */
816     @SuppressWarnings("unchecked")
817     private void loadData(final List<FileSystemObject> files) {
818         //Notify data to adapter view
819         final AdapterView<ListAdapter> view =
820                 (AdapterView<ListAdapter>)findViewById(RESOURCE_CURRENT_LAYOUT);
821         FileSystemObjectAdapter adapter = (FileSystemObjectAdapter)view.getAdapter();
822         adapter.clear();
823         adapter.addAll(files);
824         adapter.notifyDataSetChanged();
825         view.setSelection(0);
826     }
827
828     /**
829      * {@inheritDoc}
830      */
831     @Override
832     public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
833         // Different actions depending on user preference
834
835         // Get the adapter and the fso
836         FileSystemObjectAdapter adapter = ((FileSystemObjectAdapter)parent.getAdapter());
837         FileSystemObject fso = adapter.getItem(position);
838
839         // Parent directory hasn't actions
840         if (fso instanceof ParentDirectory) {
841             return false;
842         }
843
844         // Pick mode doesn't implements the onlongclick
845         if (this.mNavigationMode.compareTo(NAVIGATION_MODE.PICKABLE) == 0) {
846             return false;
847         }
848
849         onRequestMenu(fso);
850         return true; //Always consume the event
851     }
852
853     /**
854      * Method that opens or navigates to the {@link FileSystemObject}
855      *
856      * @param fso The file system object
857      */
858     public void open(FileSystemObject fso) {
859         open(fso, null);
860     }
861
862     /**
863      * Method that opens or navigates to the {@link FileSystemObject}
864      *
865      * @param fso The file system object
866      * @param searchInfo The search info
867      */
868     public void open(FileSystemObject fso, SearchInfoParcelable searchInfo) {
869         // If is a folder, then navigate to
870         if (FileHelper.isDirectory(fso)) {
871             changeCurrentDir(fso.getFullPath(), searchInfo);
872         } else {
873             // Open the file with the preferred registered app
874             IntentsActionPolicy.openFileSystemObject(getContext(), fso, false, null, null);
875         }
876     }
877
878     /**
879      * {@inheritDoc}
880      */
881     @Override
882     public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
883         try {
884             FileSystemObject fso = ((FileSystemObjectAdapter)parent.getAdapter()).getItem(position);
885             if (fso instanceof ParentDirectory) {
886                 changeCurrentDir(fso.getParent(), true, false, false, null, null);
887             } else if (fso instanceof Directory) {
888                 changeCurrentDir(fso.getFullPath(), true, false, false, null, null);
889             } else if (fso instanceof Symlink) {
890                 Symlink symlink = (Symlink)fso;
891                 if (symlink.getLinkRef() != null && symlink.getLinkRef() instanceof Directory) {
892                     changeCurrentDir(
893                             symlink.getLinkRef().getFullPath(), true, false, false, null, null);
894                 }
895             } else {
896                 if (this.mNavigationMode.compareTo(NAVIGATION_MODE.BROWSABLE) == 0) {
897                     // Open the file with the preferred registered app
898                     IntentsActionPolicy.openFileSystemObject(getContext(), fso, false, null, null);
899                 } else {
900                     // Request a file pick selection
901                     if (this.mOnFilePickedListener != null) {
902                         this.mOnFilePickedListener.onFilePicked(fso);
903                     }
904                 }
905             }
906         } catch (Throwable ex) {
907             ExceptionUtil.translateException(getContext(), ex);
908         }
909     }
910
911     /**
912      * {@inheritDoc}
913      */
914     @Override
915     public void onRequestRefresh(Object o) {
916         if (o instanceof FileSystemObject) {
917             refresh((FileSystemObject)o);
918         }
919         onDeselectAll();
920     }
921
922     /**
923      * {@inheritDoc}
924      */
925     @Override
926     public void onRequestRemove(Object o) {
927         if (o instanceof FileSystemObject) {
928             removeItem((FileSystemObject)o);
929         }
930         onDeselectAll();
931     }
932
933     /**
934      * {@inheritDoc}
935      */
936     @Override
937     public void onNavigateTo(Object o) {
938         // Ignored
939     }
940
941     /**
942      * {@inheritDoc}
943      */
944     @Override
945     public void onBreadcrumbItemClick(BreadcrumbItem item) {
946         changeCurrentDir(item.getItemPath(), true, true, false, null, null);
947     }
948
949     /**
950      * {@inheritDoc}
951      */
952     @Override
953     public void onSelectionChanged(final List<FileSystemObject> selectedItems) {
954         if (this.mOnNavigationSelectionChangedListener != null) {
955             this.mOnNavigationSelectionChangedListener.onSelectionChanged(this, selectedItems);
956         }
957     }
958
959     /**
960      * Method invoked when a request to show the menu associated
961      * with an item is started.
962      *
963      * @param item The item for which the request was started
964      */
965     public void onRequestMenu(final FileSystemObject item) {
966         if (this.mOnNavigationRequestMenuListener != null) {
967             this.mOnNavigationRequestMenuListener.onRequestMenu(this, item);
968         }
969     }
970
971     /**
972      * {@inheritDoc}
973      */
974     @Override
975     public void onToggleSelection(FileSystemObject fso) {
976         if (this.mAdapter != null) {
977             this.mAdapter.toggleSelection(fso);
978         }
979     }
980
981     /**
982      * {@inheritDoc}
983      */
984     @Override
985     public void onDeselectAll() {
986         if (this.mAdapter != null) {
987             this.mAdapter.deselectedAll();
988         }
989     }
990
991     /**
992      * {@inheritDoc}
993      */
994     @Override
995     public void onSelectAllVisibleItems() {
996         if (this.mAdapter != null) {
997             this.mAdapter.selectedAllVisibleItems();
998         }
999     }
1000
1001     /**
1002      * {@inheritDoc}
1003      */
1004     @Override
1005     public void onDeselectAllVisibleItems() {
1006         if (this.mAdapter != null) {
1007             this.mAdapter.deselectedAllVisibleItems();
1008         }
1009     }
1010
1011     /**
1012      * {@inheritDoc}
1013      */
1014     @Override
1015     public List<FileSystemObject> onRequestSelectedFiles() {
1016         return this.getSelectedFiles();
1017     }
1018
1019     /**
1020      * {@inheritDoc}
1021      */
1022     @Override
1023     public List<FileSystemObject> onRequestCurrentItems() {
1024         return this.getFiles();
1025     }
1026
1027     /**
1028      * {@inheritDoc}
1029      */
1030     @Override
1031     public String onRequestCurrentDir() {
1032         return this.mCurrentDir;
1033     }
1034
1035     /**
1036      * Method that creates a ChRooted environment, protecting the user to break anything
1037      * in the device
1038      * @hide
1039      */
1040     public void createChRooted() {
1041         // If we are in a ChRooted environment, then do nothing
1042         if (this.mChRooted) return;
1043         this.mChRooted = true;
1044
1045         //Change to first storage volume
1046         StorageVolume[] volumes =
1047                 StorageHelper.getStorageVolumes(getContext());
1048         if (volumes != null && volumes.length > 0) {
1049             changeCurrentDir(volumes[0].getPath(), false, true, false, null, null);
1050         }
1051     }
1052
1053     /**
1054      * Method that exits from a ChRooted environment
1055      * @hide
1056      */
1057     public void exitChRooted() {
1058         // If we aren't in a ChRooted environment, then do nothing
1059         if (!this.mChRooted) return;
1060         this.mChRooted = false;
1061
1062         // Refresh
1063         refresh();
1064     }
1065
1066     /**
1067      * Method that ensures that the user don't go outside the ChRooted environment
1068      *
1069      * @param newDir The new directory to navigate to
1070      * @return String
1071      */
1072     private String checkChRootedNavigation(String newDir) {
1073         // If we aren't in ChRooted environment, then there is nothing to check
1074         if (!this.mChRooted) return newDir;
1075
1076         // Check if the path is owned by one of the storage volumes
1077         if (!StorageHelper.isPathInStorageVolume(newDir)) {
1078             StorageVolume[] volumes = StorageHelper.getStorageVolumes(getContext());
1079             if (volumes != null && volumes.length > 0) {
1080                 return volumes[0].getPath();
1081             }
1082         }
1083         return newDir;
1084     }
1085
1086 }