OSDN Git Service

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