OSDN Git Service

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