OSDN Git Service

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