OSDN Git Service

cmfm: use courier typeface for hex editor
[android-x86/packages-apps-CMFileManager.git] / src / com / cyanogenmod / filemanager / activities / EditorActivity.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.activities;
18
19 import android.app.ActionBar;
20 import android.app.Activity;
21 import android.app.AlertDialog;
22 import android.content.BroadcastReceiver;
23 import android.content.Context;
24 import android.content.DialogInterface;
25 import android.content.DialogInterface.OnClickListener;
26 import android.content.Intent;
27 import android.content.IntentFilter;
28 import android.content.res.Configuration;
29 import android.graphics.Typeface;
30 import android.graphics.drawable.Drawable;
31 import android.net.Uri;
32 import android.os.AsyncTask;
33 import android.os.Bundle;
34 import android.os.Handler;
35 import android.preference.PreferenceActivity;
36 import android.text.Editable;
37 import android.text.InputType;
38 import android.text.SpannableStringBuilder;
39 import android.text.TextWatcher;
40 import android.util.Log;
41 import android.view.KeyEvent;
42 import android.view.LayoutInflater;
43 import android.view.MenuItem;
44 import android.view.View;
45 import android.view.ViewGroup;
46 import android.widget.AdapterView;
47 import android.widget.AdapterView.OnItemClickListener;
48 import android.widget.ArrayAdapter;
49 import android.widget.EditText;
50 import android.widget.ImageView;
51 import android.widget.ListPopupWindow;
52 import android.widget.ListView;
53 import android.widget.ProgressBar;
54 import android.widget.TextView;
55 import android.widget.TextView.BufferType;
56 import android.widget.Toast;
57
58 import com.android.internal.util.HexDump;
59 import com.cyanogenmod.filemanager.R;
60 import com.cyanogenmod.filemanager.activities.preferences.EditorPreferenceFragment;
61 import com.cyanogenmod.filemanager.activities.preferences.EditorSHColorSchemePreferenceFragment;
62 import com.cyanogenmod.filemanager.activities.preferences.SettingsPreferences;
63 import com.cyanogenmod.filemanager.adapters.HighlightedSimpleMenuListAdapter;
64 import com.cyanogenmod.filemanager.adapters.SimpleMenuListAdapter;
65 import com.cyanogenmod.filemanager.ash.HighlightColors;
66 import com.cyanogenmod.filemanager.ash.ISyntaxHighlightResourcesResolver;
67 import com.cyanogenmod.filemanager.ash.SyntaxHighlightFactory;
68 import com.cyanogenmod.filemanager.ash.SyntaxHighlightProcessor;
69 import com.cyanogenmod.filemanager.commands.AsyncResultListener;
70 import com.cyanogenmod.filemanager.commands.WriteExecutable;
71 import com.cyanogenmod.filemanager.console.ConsoleBuilder;
72 import com.cyanogenmod.filemanager.model.FileSystemObject;
73 import com.cyanogenmod.filemanager.preferences.FileManagerSettings;
74 import com.cyanogenmod.filemanager.preferences.Preferences;
75 import com.cyanogenmod.filemanager.ui.ThemeManager;
76 import com.cyanogenmod.filemanager.ui.ThemeManager.Theme;
77 import com.cyanogenmod.filemanager.ui.widgets.ButtonItem;
78 import com.cyanogenmod.filemanager.util.AndroidHelper;
79 import com.cyanogenmod.filemanager.util.CommandHelper;
80 import com.cyanogenmod.filemanager.util.DialogHelper;
81 import com.cyanogenmod.filemanager.util.ExceptionUtil;
82 import com.cyanogenmod.filemanager.util.ExceptionUtil.OnRelaunchCommandResult;
83 import com.cyanogenmod.filemanager.util.FileHelper;
84 import com.cyanogenmod.filemanager.util.MediaHelper;
85 import com.cyanogenmod.filemanager.util.ResourcesHelper;
86 import com.cyanogenmod.filemanager.util.StringHelper;
87
88 import java.io.BufferedReader;
89 import java.io.ByteArrayInputStream;
90 import java.io.ByteArrayOutputStream;
91 import java.io.File;
92 import java.io.OutputStream;
93 import java.io.StringReader;
94 import java.util.ArrayList;
95 import java.util.Arrays;
96 import java.util.List;
97 import java.util.UUID;
98
99 /**
100  * An internal activity for view and edit files.
101  */
102 public class EditorActivity extends Activity implements TextWatcher {
103
104     private static final String TAG = "EditorActivity"; //$NON-NLS-1$
105
106     private static boolean DEBUG = false;
107
108     private static final int WRITE_RETRIES = 3;
109
110     private final BroadcastReceiver mNotificationReceiver = new BroadcastReceiver() {
111         @Override
112         public void onReceive(Context context, Intent intent) {
113             if (intent != null) {
114                 if (intent.getAction().compareTo(FileManagerSettings.INTENT_THEME_CHANGED) == 0) {
115                     applyTheme();
116                     return;
117                 }
118                 if (intent.getAction().compareTo(FileManagerSettings.INTENT_SETTING_CHANGED) == 0) {
119                     // The settings has changed
120                     String key = intent.getStringExtra(FileManagerSettings.EXTRA_SETTING_CHANGED_KEY);
121                     if (key != null) {
122                         final EditorActivity activity = EditorActivity.this;
123
124                         // No suggestions
125                         if (key.compareTo(FileManagerSettings.SETTINGS_EDITOR_NO_SUGGESTIONS.getId()) == 0) {
126                             // Ignore in binary files
127                             if (activity.mBinary) return;
128
129                             // Do we have a different setting?
130                             boolean noSuggestionsSetting =
131                                     Preferences.getSharedPreferences().getBoolean(
132                                         FileManagerSettings.SETTINGS_EDITOR_NO_SUGGESTIONS.getId(),
133                                         ((Boolean)FileManagerSettings.SETTINGS_EDITOR_NO_SUGGESTIONS.
134                                                 getDefaultValue()).booleanValue());
135                             if (noSuggestionsSetting != activity.mNoSuggestions) {
136                                 activity.mHandler.post(new Runnable() {
137                                     @Override
138                                     public void run() {
139                                         toggleNoSuggestions();
140                                     }
141                                 });
142                             }
143
144                         // Word wrap
145                         } else if (key.compareTo(FileManagerSettings.SETTINGS_EDITOR_WORD_WRAP.getId()) == 0) {
146                             // Ignore in binary files
147                             if (activity.mBinary) return;
148
149                             // Do we have a different setting?
150                             boolean wordWrapSetting = Preferences.getSharedPreferences().getBoolean(
151                                     FileManagerSettings.SETTINGS_EDITOR_WORD_WRAP.getId(),
152                                     ((Boolean)FileManagerSettings.SETTINGS_EDITOR_WORD_WRAP.
153                                             getDefaultValue()).booleanValue());
154                             if (wordWrapSetting != activity.mWordWrap) {
155                                 activity.mHandler.post(new Runnable() {
156                                     @Override
157                                     public void run() {
158                                         toggleWordWrap();
159                                     }
160                                 });
161                             }
162
163                         // Syntax highlight
164                         // Default theme color scheme
165                         // Color scheme
166                         } else if (key.compareTo(FileManagerSettings.SETTINGS_EDITOR_SYNTAX_HIGHLIGHT.getId()) == 0) {
167                             // Ignore in binary files
168                             if (activity.mBinary) return;
169
170                             // Do we have a different setting?
171                             boolean syntaxHighlightSetting =
172                                     Preferences.getSharedPreferences().getBoolean(
173                                         FileManagerSettings.SETTINGS_EDITOR_SYNTAX_HIGHLIGHT.getId(),
174                                         ((Boolean)FileManagerSettings.SETTINGS_EDITOR_SYNTAX_HIGHLIGHT.
175                                                 getDefaultValue()).booleanValue());
176                             if (syntaxHighlightSetting != activity.mSyntaxHighlight) {
177                                 activity.mHandler.post(new Runnable() {
178                                     @Override
179                                     public void run() {
180                                         toggleSyntaxHighlight();
181                                     }
182                                 });
183                             }
184
185                         } else if (key.compareTo(FileManagerSettings.SETTINGS_EDITOR_SH_USE_THEME_DEFAULT.getId()) == 0 ||
186                                    key.compareTo(FileManagerSettings.SETTINGS_EDITOR_SH_COLOR_SCHEME.getId()) == 0 ) {
187                             // Ignore in binary files
188                             if (activity.mBinary) return;
189
190                             // Reload the syntax highlight
191                             activity.mHandler.post(new Runnable() {
192                                 @Override
193                                 public void run() {
194                                     reloadSyntaxHighlight();
195                                 }
196                             });
197                         }
198                     }
199                     return;
200                 }
201             }
202         }
203     };
204
205     private class HexDumpAdapter extends ArrayAdapter<String> {
206         private class ViewHolder {
207             TextView mTextView;
208         }
209
210         public HexDumpAdapter(Context context, List<String> data) {
211             super(context, R.layout.hexdump_line, data);
212         }
213
214         @Override
215         public View getView(int position, View convertView, ViewGroup parent) {
216             View v = convertView;
217             if (v == null) {
218                 final Context context = getContext();
219                 LayoutInflater inflater = LayoutInflater.from(context);
220                 Theme theme = ThemeManager.getCurrentTheme(context);
221
222                 v = inflater.inflate(R.layout.hexdump_line, parent, false);
223                 ViewHolder viewHolder = new EditorActivity.HexDumpAdapter.ViewHolder();
224                 viewHolder.mTextView = (TextView)v.findViewById(android.R.id.text1);
225
226                 viewHolder.mTextView.setTextAppearance(context, R.style.hexeditor_text_appearance);
227                 viewHolder.mTextView.setTypeface(mHexTypeface);
228                 theme.setTextColor(context, viewHolder.mTextView, "text_color"); //$NON-NLS-1$
229
230                 v.setTag(viewHolder);
231             }
232
233             String text = getItem(position);
234             ViewHolder viewHolder = (ViewHolder)v.getTag();
235             viewHolder.mTextView.setText(text);
236
237             return v;
238         }
239     }
240
241     /**
242      * Internal interface to notify progress update
243      */
244     private interface OnProgressListener {
245         void onProgress(int progress);
246     }
247
248     /**
249      * An internal listener for read a file
250      */
251     private class AsyncReader implements AsyncResultListener {
252
253         final Object mSync = new Object();
254         ByteArrayOutputStream mByteBuffer = null;
255         ArrayList<String> mBinaryBuffer = null;
256         SpannableStringBuilder mBuffer = null;
257         Exception mCause;
258         long mSize;
259         FileSystemObject mReadFso;
260         OnProgressListener mListener;
261
262         /**
263          * Constructor of <code>AsyncReader</code>. For enclosing access.
264          */
265         public AsyncReader() {
266             super();
267         }
268
269         /**
270          * {@inheritDoc}
271          */
272         @Override
273         public void onAsyncStart() {
274             this.mByteBuffer = new ByteArrayOutputStream((int)this.mReadFso.getSize());
275             this.mSize = 0;
276         }
277
278         /**
279          * {@inheritDoc}
280          */
281         @Override
282         public void onAsyncEnd(boolean cancelled) {/**NON BLOCK**/}
283
284         /**
285          * {@inheritDoc}
286          */
287         @Override
288         public void onAsyncExitCode(int exitCode) {
289             synchronized (this.mSync) {
290                 this.mSync.notify();
291             }
292         }
293
294         /**
295          * {@inheritDoc}
296          */
297         @Override
298         public void onPartialResult(Object result) {
299             try {
300                 if (result == null) return;
301                 byte[] partial = (byte[])result;
302
303                 // Check if the file is a binary file. In this case the editor
304                 // is read-only
305                 if (!EditorActivity.this.mReadOnly) {
306                     for (int i = 0; i < partial.length-1; i++) {
307                         if (!StringHelper.isPrintableCharacter((char)partial[i])) {
308                             EditorActivity.this.mBinary = true;
309                             EditorActivity.this.mReadOnly = true;
310                             break;
311                         }
312                     }
313                 }
314
315                 this.mByteBuffer.write(partial, 0, partial.length);
316                 this.mSize += partial.length;
317                 if (this.mListener != null && this.mReadFso != null) {
318                     int progress = 0;
319                     if (this.mReadFso.getSize() != 0) {
320                         progress = (int)((this.mSize*100) / this.mReadFso.getSize());
321                     }
322                     this.mListener.onProgress(progress);
323                 }
324             } catch (Exception e) {
325                 this.mCause = e;
326             }
327         }
328
329         /**
330          * {@inheritDoc}
331          */
332         @Override
333         public void onException(Exception cause) {
334             this.mCause = cause;
335         }
336     }
337
338     /**
339      * An internal listener for write a file
340      */
341     private class AsyncWriter implements AsyncResultListener {
342
343         Exception mCause;
344
345         /**
346          * Constructor of <code>AsyncWriter</code>. For enclosing access.
347          */
348         public AsyncWriter() {
349             super();
350         }
351
352         /**
353          * {@inheritDoc}
354          */
355         @Override
356         public void onAsyncStart() {/**NON BLOCK**/}
357
358         /**
359          * {@inheritDoc}
360          */
361         @Override
362         public void onAsyncEnd(boolean cancelled) {/**NON BLOCK**/}
363
364         /**
365          * {@inheritDoc}
366          */
367         @Override
368         public void onAsyncExitCode(int exitCode) {/**NON BLOCK**/}
369
370         /**
371          * {@inheritDoc}
372          */
373         @Override
374         public void onPartialResult(Object result) {/**NON BLOCK**/}
375
376         /**
377          * {@inheritDoc}
378          */
379         @Override
380         public void onException(Exception cause) {
381             this.mCause = cause;
382         }
383     }
384
385     /**
386      * An internal class to resolve resources for the syntax highlight library.
387      * @hide
388      */
389     class ResourcesResolver implements ISyntaxHighlightResourcesResolver {
390         @Override
391         public CharSequence getString(String id, String resid) {
392             return EditorActivity.this.getString(
393                         ResourcesHelper.getIdentifier(
394                             EditorActivity.this.getResources(), "string", resid)); //$NON-NLS-1$
395         }
396
397         @Override
398         public int getInteger(String id, String resid, int def) {
399             return EditorActivity.this.getResources().
400                     getInteger(
401                         ResourcesHelper.getIdentifier(
402                             EditorActivity.this.getResources(), "integer", resid)); //$NON-NLS-1$
403         }
404
405         @Override
406         public int getColor(String id, String resid, int def) {
407             final Context ctx = EditorActivity.this;
408             try {
409                 // Is default theme color scheme enabled?
410                 if (isDefaultThemeColorScheme()) {
411                     return ThemeManager.getCurrentTheme(ctx).getColor(ctx, resid);
412                 }
413
414                 // Use the user-defined settings
415                 int[] colors = getUserColorScheme();
416                 HighlightColors[] schemeColors = HighlightColors.values();
417                 int cc = schemeColors.length;
418                 int cc2 = colors.length;
419                 for (int i = 0; i < cc; i++) {
420                     if (schemeColors[i].getId().compareTo(id) == 0) {
421                         if (cc2 >= i) {
422                             // User-defined
423                             return colors[i];
424                         }
425
426                         // Theme default
427                         return ThemeManager.getCurrentTheme(ctx).getColor(ctx, resid);
428                     }
429
430                 }
431
432             } catch (Exception ex) {
433                 // Resource not found
434             }
435             return def;
436         }
437
438         /**
439          * Method that returns if we should return the default theme color scheme or not
440          *
441          * @return boolean Whether return the default theme color scheme or not
442          */
443         private boolean isDefaultThemeColorScheme() {
444             Boolean defaultValue =
445                     (Boolean)FileManagerSettings.
446                                 SETTINGS_EDITOR_SH_USE_THEME_DEFAULT.getDefaultValue();
447             return Preferences.getSharedPreferences().getBoolean(
448                         FileManagerSettings.SETTINGS_EDITOR_SH_USE_THEME_DEFAULT.getId(),
449                         defaultValue.booleanValue());
450         }
451
452         /**
453          * Method that returns the user-defined color scheme
454          *
455          * @return int[] The user-defined color scheme
456          */
457         private int[] getUserColorScheme() {
458             String defaultValue =
459                     (String)FileManagerSettings.
460                                 SETTINGS_EDITOR_SH_COLOR_SCHEME.getDefaultValue();
461             String value = Preferences.getSharedPreferences().getString(
462                                 FileManagerSettings.SETTINGS_EDITOR_SH_COLOR_SCHEME.getId(),
463                                 defaultValue);
464             return EditorSHColorSchemePreferenceFragment.toColorShemeArray(value);
465         }
466     }
467
468     /**
469      * @hide
470      */
471     FileSystemObject mFso;
472
473     private int mBufferSize;
474     private long mMaxFileSize;
475
476     /**
477      * @hide
478      */
479     boolean mDirty;
480     /**
481      * @hide
482      */
483     boolean mReadOnly;
484     /**
485      * @hide
486      */
487     boolean mBinary;
488
489     /**
490      * @hide
491      */
492     TextView mTitle;
493     /**
494      * @hide
495      */
496     EditText mEditor;
497     /**
498      * @hide
499      */
500     ListView mBinaryEditor;
501     /**
502      * @hide
503      */
504     View mProgress;
505     /**
506      * @hide
507      */
508     ProgressBar mProgressBar;
509     /**
510      * @hide
511      */
512     TextView mProgressBarMsg;
513     /**
514      * @hide
515      */
516     ButtonItem mSave;
517
518     // No suggestions status
519     /**
520      * @hide
521      */
522     boolean mNoSuggestions;
523
524     // Word wrap status
525     private ViewGroup mWordWrapView;
526     private ViewGroup mNoWordWrapView;
527     /**
528      * @hide
529      */
530     boolean mWordWrap;
531
532     // Syntax highlight status
533     /**
534      * @hide
535      */
536     boolean mSyntaxHighlight;
537     /**
538      * @hide
539      */
540     SyntaxHighlightProcessor mSyntaxHighlightProcessor;
541     private int mEditStart;
542     private int mEditEnd;
543
544     private View mOptionsAnchorView;
545
546     private Typeface mHexTypeface;
547
548     private final Object mExecSync = new Object();
549
550     /**
551      * @hide
552      */
553     Handler mHandler;
554
555     /**
556      * @hide
557      */
558     String mHexLineSeparator;
559
560     /**
561      * Intent extra parameter for the path of the file to open.
562      */
563     public static final String EXTRA_OPEN_FILE = "extra_open_file";  //$NON-NLS-1$
564
565     /**
566      * {@inheritDoc}
567      */
568     @Override
569     protected void onCreate(Bundle state) {
570         if (DEBUG) {
571             Log.d(TAG, "EditorActivity.onCreate"); //$NON-NLS-1$
572         }
573
574         this.mHandler = new Handler();
575
576         // Load typeface for hex editor
577         mHexTypeface = Typeface.createFromAsset(getAssets(), "fonts/Courier-Prime.ttf");
578
579         // Register the broadcast receiver
580         IntentFilter filter = new IntentFilter();
581         filter.addAction(FileManagerSettings.INTENT_THEME_CHANGED);
582         filter.addAction(FileManagerSettings.INTENT_SETTING_CHANGED);
583         registerReceiver(this.mNotificationReceiver, filter);
584
585         // Generate a random separator
586         this.mHexLineSeparator = UUID.randomUUID().toString() + UUID.randomUUID().toString();
587
588         // Set the theme before setContentView
589         Theme theme = ThemeManager.getCurrentTheme(this);
590         theme.setBaseTheme(this, false);
591
592         //Set the main layout of the activity
593         setContentView(R.layout.editor);
594
595         // Get the limit vars
596         this.mBufferSize = getResources().getInteger(R.integer.buffer_size);
597         long availMem = AndroidHelper.getAvailableMemory(this);
598         this.mMaxFileSize = Math.min(availMem,
599                 getResources().getInteger(R.integer.editor_max_file_size));
600
601         //Initialize
602         initTitleActionBar();
603         initLayout();
604
605         // Apply the theme
606         applyTheme();
607
608         // Initialize the console
609         initializeConsole();
610
611         // Read the file
612         readFile();
613
614         //Save state
615         super.onCreate(state);
616     }
617
618     /**
619      * {@inheritDoc}
620      */
621     @Override
622     protected void onDestroy() {
623         if (DEBUG) {
624             Log.d(TAG, "EditorActivity.onDestroy"); //$NON-NLS-1$
625         }
626
627         // Unregister the receiver
628         try {
629             unregisterReceiver(this.mNotificationReceiver);
630         } catch (Throwable ex) {
631             /**NON BLOCK**/
632         }
633
634         //All destroy. Continue
635         super.onDestroy();
636     }
637
638     /**
639      * {@inheritDoc}
640      */
641     @Override
642     public void onConfigurationChanged(Configuration newConfig) {
643         super.onConfigurationChanged(newConfig);
644     }
645
646     /**
647      * Method that initializes the titlebar of the activity.
648      */
649     private void initTitleActionBar() {
650         //Configure the action bar options
651         getActionBar().setBackgroundDrawable(
652                 getResources().getDrawable(R.drawable.bg_holo_titlebar));
653         getActionBar().setDisplayOptions(
654                 ActionBar.DISPLAY_SHOW_CUSTOM | ActionBar.DISPLAY_SHOW_HOME);
655         getActionBar().setDisplayHomeAsUpEnabled(true);
656         View customTitle = getLayoutInflater().inflate(R.layout.simple_customtitle, null, false);
657         this.mTitle = (TextView)customTitle.findViewById(R.id.customtitle_title);
658         this.mTitle.setText(R.string.editor);
659         this.mTitle.setContentDescription(getString(R.string.editor));
660         this.mSave = (ButtonItem)customTitle.findViewById(R.id.ab_button1);
661         this.mSave.setImageResource(R.drawable.ic_holo_light_save);
662         this.mSave.setContentDescription(getString(R.string.actionbar_button_save_cd));
663         this.mSave.setVisibility(View.GONE);
664
665         ButtonItem configuration = (ButtonItem)customTitle.findViewById(R.id.ab_button2);
666         configuration.setImageResource(R.drawable.ic_holo_light_overflow);
667         configuration.setContentDescription(getString(R.string.actionbar_button_overflow_cd));
668
669         View status = findViewById(R.id.editor_status);
670         boolean showOptionsMenu = AndroidHelper.showOptionsMenu(this);
671         configuration.setVisibility(showOptionsMenu ? View.VISIBLE : View.GONE);
672         this.mOptionsAnchorView = showOptionsMenu ? configuration : status;
673
674         getActionBar().setCustomView(customTitle);
675     }
676
677     /**
678      * Method that initializes the layout and components of the activity.
679      */
680     private void initLayout() {
681         this.mEditor = (EditText)findViewById(R.id.editor);
682         this.mEditor.setText(null);
683         this.mEditor.addTextChangedListener(this);
684         this.mEditor.setEnabled(false);
685         this.mWordWrapView = (ViewGroup)findViewById(R.id.editor_word_wrap_view);
686         this.mNoWordWrapView = (ViewGroup)findViewById(R.id.editor_no_word_wrap_view);
687         this.mWordWrapView.setVisibility(View.VISIBLE);
688         this.mNoWordWrapView.setVisibility(View.GONE);
689
690         this.mBinaryEditor = (ListView)findViewById(R.id.editor_binary);
691
692         this.mNoSuggestions = false;
693         this.mWordWrap = true;
694         this.mSyntaxHighlight = true;
695
696         // Load the no suggestions setting
697         boolean noSuggestionsSetting = Preferences.getSharedPreferences().getBoolean(
698                 FileManagerSettings.SETTINGS_EDITOR_NO_SUGGESTIONS.getId(),
699                 ((Boolean)FileManagerSettings.SETTINGS_EDITOR_NO_SUGGESTIONS.
700                         getDefaultValue()).booleanValue());
701         if (noSuggestionsSetting != this.mNoSuggestions) {
702             toggleNoSuggestions();
703         }
704
705         // Load the word wrap setting
706         boolean wordWrapSetting = Preferences.getSharedPreferences().getBoolean(
707                 FileManagerSettings.SETTINGS_EDITOR_WORD_WRAP.getId(),
708                 ((Boolean)FileManagerSettings.SETTINGS_EDITOR_WORD_WRAP.
709                         getDefaultValue()).booleanValue());
710         if (wordWrapSetting != this.mWordWrap) {
711             toggleWordWrap();
712         }
713
714         // Load the syntax highlight setting
715         boolean syntaxHighlighSetting = Preferences.getSharedPreferences().getBoolean(
716                 FileManagerSettings.SETTINGS_EDITOR_SYNTAX_HIGHLIGHT.getId(),
717                 ((Boolean)FileManagerSettings.SETTINGS_EDITOR_SYNTAX_HIGHLIGHT.
718                         getDefaultValue()).booleanValue());
719         if (syntaxHighlighSetting != this.mSyntaxHighlight) {
720             toggleSyntaxHighlight();
721         }
722
723         this.mProgress = findViewById(R.id.editor_progress);
724         this.mProgressBar = (ProgressBar)findViewById(R.id.editor_progress_bar);
725         this.mProgressBarMsg = (TextView)findViewById(R.id.editor_progress_msg);
726     }
727
728     /**
729      * Method that toggle the no suggestions property of the editor
730      * @hide
731      */
732     /**package**/ void toggleNoSuggestions() {
733         synchronized (this.mExecSync) {
734             int type = InputType.TYPE_CLASS_TEXT |
735                        InputType.TYPE_TEXT_FLAG_MULTI_LINE |
736                        InputType.TYPE_TEXT_FLAG_IME_MULTI_LINE;
737             if (!this.mNoSuggestions) {
738                 type |= InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS;
739             }
740             this.mEditor.setInputType(type);
741             this.mNoSuggestions = !this.mNoSuggestions;
742         }
743     }
744
745     /**
746      * Method that toggle the word wrap property of the editor
747      * @hide
748      */
749     /**package**/ void toggleWordWrap() {
750         synchronized (this.mExecSync) {
751             ViewGroup vSrc = this.mWordWrap ? this.mWordWrapView : this.mNoWordWrapView;
752             ViewGroup vDst = this.mWordWrap ? this.mNoWordWrapView : this.mWordWrapView;
753             ViewGroup vSrcParent = this.mWordWrap
754                                                 ? this.mWordWrapView
755                                                 : (ViewGroup)this.mNoWordWrapView.getChildAt(0);
756             ViewGroup vDstParent = this.mWordWrap
757                                                 ? (ViewGroup)this.mNoWordWrapView.getChildAt(0)
758                                                 : this.mWordWrapView;
759             vSrc.setVisibility(View.GONE);
760             vSrcParent.removeView(this.mEditor);
761             vDstParent.addView(this.mEditor);
762             vDst.setVisibility(View.VISIBLE);
763             vDst.scrollTo(0, 0);
764             this.mWordWrap = !this.mWordWrap;
765         }
766     }
767
768     /**
769      * Method that toggles the syntax highlight property of the editor
770      * @hide
771      */
772     /**package**/ void toggleSyntaxHighlight() {
773         synchronized (this.mExecSync) {
774             if (this.mSyntaxHighlightProcessor != null) {
775                 try {
776                     if (this.mSyntaxHighlight) {
777                         this.mSyntaxHighlightProcessor.clear(this.mEditor.getText());
778                     } else {
779                         this.mSyntaxHighlightProcessor.process(this.mEditor.getText());
780                     }
781                 } catch (Exception ex) {
782                     // An error in a syntax library, should not break down app.
783                     Log.e(TAG, "Syntax highlight failed.", ex); //$NON-NLS-1$
784                 }
785             }
786
787             this.mSyntaxHighlight = !this.mSyntaxHighlight;
788         }
789     }
790
791     /**
792      * Method that reloads the syntax highlight of the current file
793      * @hide
794      */
795     /**package**/ void reloadSyntaxHighlight() {
796         synchronized (this.mExecSync) {
797             if (this.mSyntaxHighlightProcessor != null) {
798                 try {
799                     this.mSyntaxHighlightProcessor.initialize();
800                     this.mSyntaxHighlightProcessor.process(this.mEditor.getText());
801                 } catch (Exception ex) {
802                     // An error in a syntax library, should not break down app.
803                     Log.e(TAG, "Syntax highlight failed.", ex); //$NON-NLS-1$
804                 }
805             }
806         }
807     }
808
809     /**
810      * {@inheritDoc}
811      */
812     @Override
813     public boolean onKeyUp(int keyCode, KeyEvent event) {
814         switch (keyCode) {
815             case KeyEvent.KEYCODE_MENU:
816                 showOverflowPopUp(this.mOptionsAnchorView);
817                 return true;
818             case KeyEvent.KEYCODE_BACK:
819                 checkDirtyState();
820                 return true;
821             default:
822                 return super.onKeyUp(keyCode, event);
823         }
824     }
825
826     /**
827      * {@inheritDoc}
828      */
829     @Override
830     public boolean onOptionsItemSelected(MenuItem item) {
831        switch (item.getItemId()) {
832           case android.R.id.home:
833               if ((getActionBar().getDisplayOptions() & ActionBar.DISPLAY_HOME_AS_UP)
834                       == ActionBar.DISPLAY_HOME_AS_UP) {
835                   checkDirtyState();
836               }
837               return true;
838           default:
839              return super.onOptionsItemSelected(item);
840        }
841     }
842
843     /**
844      * Method that shows a popup with the activity main menu.
845      *
846      * @param anchor The anchor of the popup
847      */
848     private void showOverflowPopUp(View anchor) {
849         SimpleMenuListAdapter adapter =
850                 new HighlightedSimpleMenuListAdapter(this, R.menu.editor);
851         MenuItem noSuggestions = adapter.getMenu().findItem(R.id.mnu_no_suggestions);
852         if (noSuggestions != null) {
853             if (this.mBinary) {
854                 adapter.getMenu().removeItem(R.id.mnu_no_suggestions);
855             } else {
856                 noSuggestions.setChecked(this.mNoSuggestions);
857             }
858         }
859         MenuItem wordWrap = adapter.getMenu().findItem(R.id.mnu_word_wrap);
860         if (wordWrap != null) {
861             if (this.mBinary) {
862                 adapter.getMenu().removeItem(R.id.mnu_word_wrap);
863             } else {
864                 wordWrap.setChecked(this.mWordWrap);
865             }
866         }
867         MenuItem syntaxHighlight = adapter.getMenu().findItem(R.id.mnu_syntax_highlight);
868         if (syntaxHighlight != null) {
869             if (this.mBinary) {
870                 adapter.getMenu().removeItem(R.id.mnu_syntax_highlight);
871             } else {
872                 syntaxHighlight.setChecked(this.mSyntaxHighlight);
873             }
874         }
875
876         final ListPopupWindow popup =
877                 DialogHelper.createListPopupWindow(this, adapter, anchor);
878         popup.setOnItemClickListener(new OnItemClickListener() {
879             @Override
880             public void onItemClick(
881                     final AdapterView<?> parent, final View v,
882                     final int position, final long id) {
883                 final int itemId = (int)id;
884                 switch (itemId) {
885                     case R.id.mnu_no_suggestions:
886                         toggleNoSuggestions();
887                         break;
888                     case R.id.mnu_word_wrap:
889                         toggleWordWrap();
890                         break;
891                     case R.id.mnu_syntax_highlight:
892                         toggleSyntaxHighlight();
893                         break;
894                     case R.id.mnu_settings:
895                         //Settings
896                         Intent settings = new Intent(EditorActivity.this, SettingsPreferences.class);
897                         settings.putExtra(
898                                 PreferenceActivity.EXTRA_SHOW_FRAGMENT,
899                                 EditorPreferenceFragment.class.getName());
900                         startActivity(settings);
901                         break;
902                 }
903                 popup.dismiss();
904             }
905         });
906         popup.show();
907     }
908
909     /**
910      * Method invoked when an action item is clicked.
911      *
912      * @param view The button pushed
913      */
914     public void onActionBarItemClick(View view) {
915         switch (view.getId()) {
916             case R.id.ab_button1:
917                 // Save the file
918                 checkAndWrite();
919                 break;
920
921             case R.id.ab_button2:
922                 // Show overflow menu
923                 showOverflowPopUp(this.mOptionsAnchorView);
924                 break;
925
926             default:
927                 break;
928         }
929     }
930
931     /**
932      * Method that initializes a console
933      */
934     private boolean initializeConsole() {
935         try {
936             ConsoleBuilder.getConsole(this);
937             // There is a console allocated. Use it.
938             return true;
939         } catch (Throwable _throw) {
940             // Capture the exception
941             ExceptionUtil.translateException(this, _throw, false, true);
942         }
943         return false;
944     }
945
946     /**
947      * Method that reads the requested file
948      */
949     private void readFile() {
950         // For now editor is not dirty and editable.
951         setDirty(false);
952         this.mBinary = false;
953
954         // Check for a valid action
955         String action = getIntent().getAction();
956         if (action == null ||
957                 (action.compareTo(Intent.ACTION_VIEW) != 0) &&
958                 (action.compareTo(Intent.ACTION_EDIT) != 0)) {
959             DialogHelper.showToast(
960                     this, R.string.editor_invalid_file_msg, Toast.LENGTH_SHORT);
961             return;
962         }
963         // This var should be set depending on ACTION_VIEW or ACTION_EDIT action, but for
964         // better compatibility, IntentsActionPolicy use always ACTION_VIEW, so we have
965         // to ignore this check here
966         this.mReadOnly = false;
967
968         // Read the intent and check that is has a valid request
969         String path = uriToPath(this, getIntent().getData());
970         if (path == null || path.length() == 0) {
971             DialogHelper.showToast(
972                     this, R.string.editor_invalid_file_msg, Toast.LENGTH_SHORT);
973             return;
974         }
975
976         // Set the title of the dialog
977         File f = new File(path);
978         this.mTitle.setText(f.getName());
979
980         // Check that the file exists (the real file, not the symlink)
981         try {
982             this.mFso = CommandHelper.getFileInfo(this, path, true, null);
983             if (this.mFso == null) {
984                 DialogHelper.showToast(
985                         this, R.string.editor_file_not_found_msg, Toast.LENGTH_SHORT);
986                 return;
987             }
988         } catch (Exception e) {
989             Log.e(TAG, "Failed to get file reference", e); //$NON-NLS-1$
990             DialogHelper.showToast(
991                     this, R.string.editor_file_not_found_msg, Toast.LENGTH_SHORT);
992             return;
993         }
994
995         // Check that we can handle the length of the file (by device)
996         if (this.mMaxFileSize < this.mFso.getSize()) {
997             DialogHelper.showToast(
998                     this, R.string.editor_file_exceed_size_msg, Toast.LENGTH_SHORT);
999             return;
1000         }
1001
1002         // Get the syntax highlight processor
1003         SyntaxHighlightFactory shpFactory =
1004                 SyntaxHighlightFactory.getDefaultFactory(new ResourcesResolver());
1005         this.mSyntaxHighlightProcessor = shpFactory.getSyntaxHighlightProcessor(f);
1006         if (this.mSyntaxHighlightProcessor != null) {
1007             this.mSyntaxHighlightProcessor.initialize();
1008         }
1009
1010         // Check that we have read access
1011         try {
1012             FileHelper.ensureReadAccess(
1013                     ConsoleBuilder.getConsole(this),
1014                     this.mFso,
1015                     null);
1016
1017             // Read the file in background
1018             asyncRead();
1019
1020         } catch (Exception ex) {
1021             ExceptionUtil.translateException(
1022                     this, ex, false, true, new OnRelaunchCommandResult() {
1023                 @Override
1024                 public void onSuccess() {
1025                     // Read the file in background
1026                     asyncRead();
1027                 }
1028
1029                 @Override
1030                 public void onFailed(Throwable cause) {
1031                     finish();
1032                 }
1033
1034                 @Override
1035                 public void onCancelled() {
1036                     finish();
1037                 }
1038             });
1039         }
1040     }
1041
1042     /**
1043      * Method that does the read of the file in background
1044      * @hide
1045      */
1046     void asyncRead() {
1047         // Do the load of the file
1048         AsyncTask<FileSystemObject, Integer, Boolean> mReadTask =
1049                             new AsyncTask<FileSystemObject, Integer, Boolean>() {
1050
1051             private Exception mCause;
1052             private AsyncReader mReader;
1053             private boolean changeToBinaryMode;
1054             private boolean changeToDisplaying;
1055
1056             @Override
1057             protected void onPreExecute() {
1058                 // Show the progress
1059                 this.changeToBinaryMode = false;
1060                 this.changeToDisplaying = false;
1061                 doProgress(true, 0);
1062             }
1063
1064             @Override
1065             protected Boolean doInBackground(FileSystemObject... params) {
1066                 final EditorActivity activity = EditorActivity.this;
1067
1068                 // Only one argument (the file to open)
1069                 FileSystemObject fso = params[0];
1070                 this.mCause = null;
1071
1072                 // Read the file in an async listener
1073                 try {
1074                     while (true) {
1075                         // Configure the reader
1076                         this.mReader = new AsyncReader();
1077                         this.mReader.mReadFso = fso;
1078                         this.mReader.mListener = new OnProgressListener() {
1079                             @Override
1080                             @SuppressWarnings("synthetic-access")
1081                             public void onProgress(int progress) {
1082                                 publishProgress(Integer.valueOf(progress));
1083                             }
1084                         };
1085
1086                         // Execute the command (read the file)
1087                         CommandHelper.read(activity, fso.getFullPath(), this.mReader, null);
1088
1089                         // Wait for
1090                         synchronized (this.mReader.mSync) {
1091                             this.mReader.mSync.wait();
1092                         }
1093
1094                         // 100%
1095                         publishProgress(new Integer(100));
1096
1097                         // Check if the read was successfully
1098                         if (this.mReader.mCause != null) {
1099                             this.mCause = this.mReader.mCause;
1100                             return Boolean.FALSE;
1101                         }
1102                         break;
1103                     }
1104
1105                     // Now we have the byte array with all the data. is a binary file?
1106                     // Then dump them byte array to hex dump string (only if users settings
1107                     // to dump file)
1108                     boolean hexDump =
1109                             Preferences.getSharedPreferences().getBoolean(
1110                                 FileManagerSettings.SETTINGS_EDITOR_HEXDUMP.getId(),
1111                                 ((Boolean)FileManagerSettings.SETTINGS_EDITOR_HEXDUMP.
1112                                         getDefaultValue()).booleanValue());
1113                     if (activity.mBinary && hexDump) {
1114                         // we do not use the Hexdump helper class, because we need to show the
1115                         // progress of the dump process
1116                         final String data = toHexPrintableString(toHexDump(
1117                                 this.mReader.mByteBuffer.toByteArray()));
1118                         this.mReader.mBinaryBuffer = new ArrayList<String>();
1119                         BufferedReader reader = new BufferedReader(new StringReader(data));
1120                         String line;
1121                         while ((line = reader.readLine()) != null) {
1122                             this.mReader.mBinaryBuffer.add(line);
1123                         }
1124                         Log.i(TAG, "Bytes read: " + data.length()); //$NON-NLS-1$
1125                     } else {
1126                         final String data = new String(this.mReader.mByteBuffer.toByteArray());
1127                         this.mReader.mBuffer = new SpannableStringBuilder(data);
1128                         Log.i(TAG, "Bytes read: " + data.getBytes().length); //$NON-NLS-1$
1129                     }
1130                     this.mReader.mByteBuffer = null;
1131
1132                     // 100%
1133                     this.changeToDisplaying = true;
1134                     publishProgress(new Integer(0));
1135
1136                 } catch (Exception e) {
1137                     this.mCause = e;
1138                     return Boolean.FALSE;
1139                 }
1140
1141                 return Boolean.TRUE;
1142             }
1143
1144             @Override
1145             protected void onProgressUpdate(Integer... values) {
1146                 // Do progress
1147                 doProgress(true, values[0].intValue());
1148             }
1149
1150             @Override
1151             protected void onPostExecute(Boolean result) {
1152                 final EditorActivity activity = EditorActivity.this;
1153                 // Is error?
1154                 if (!result.booleanValue()) {
1155                     if (this.mCause != null) {
1156                         ExceptionUtil.translateException(activity, this.mCause);
1157                         activity.mEditor.setEnabled(false);
1158                     }
1159                 } else {
1160                     // Now we have the buffer, set the text of the editor
1161                     if (activity.mBinary) {
1162                         HexDumpAdapter adapter = new HexDumpAdapter(EditorActivity.this,
1163                                 this.mReader.mBinaryBuffer);
1164                         mBinaryEditor.setAdapter(adapter);
1165
1166                         // Cleanup
1167                         this.mReader.mBinaryBuffer = null;
1168                     } else {
1169                         activity.mEditor.setText(
1170                                 this.mReader.mBuffer, BufferType.EDITABLE);
1171
1172                         // Highlight editor text syntax
1173                         if (activity.mSyntaxHighlight &&
1174                             activity.mSyntaxHighlightProcessor != null) {
1175                             try {
1176                                 activity.mSyntaxHighlightProcessor.process(
1177                                         activity.mEditor.getText());
1178                             } catch (Exception ex) {
1179                                 // An error in a syntax library, should not break down app.
1180                                 Log.e(TAG, "Syntax highlight failed.", ex); //$NON-NLS-1$
1181                             }
1182                         }
1183
1184                         //Cleanup
1185                         this.mReader.mBuffer = null;
1186                     }
1187
1188                     setDirty(false);
1189                     activity.mEditor.setEnabled(!activity.mReadOnly);
1190
1191                     // Notify read-only mode
1192                     if (activity.mReadOnly) {
1193                         DialogHelper.showToast(
1194                                 activity,
1195                                 R.string.editor_read_only_mode,
1196                                 Toast.LENGTH_SHORT);
1197                     }
1198                 }
1199
1200                 doProgress(false, 0);
1201             }
1202
1203             @Override
1204             protected void onCancelled() {
1205                 // Hide the progress
1206                 doProgress(false, 0);
1207             }
1208
1209             /**
1210              * Method that update the progress status
1211              *
1212              * @param visible If the progress bar need to be hidden
1213              * @param progress The progress
1214              */
1215             private void doProgress(boolean visible, int progress) {
1216                 final EditorActivity activity = EditorActivity.this;
1217
1218                 // Show the progress bar
1219                 activity.mProgressBar.setProgress(progress);
1220                 activity.mProgress.setVisibility(visible ? View.VISIBLE : View.GONE);
1221
1222                 if (this.changeToBinaryMode) {
1223                     mWordWrapView.setVisibility(View.GONE);
1224                     mNoWordWrapView.setVisibility(View.GONE);
1225                     mBinaryEditor.setVisibility(View.VISIBLE);
1226
1227                     // Show hex dumping text
1228                     activity.mProgressBarMsg.setText(R.string.dumping_message);
1229                     this.changeToBinaryMode = false;
1230                 }
1231                 else if (this.changeToDisplaying) {
1232                     activity.mProgressBarMsg.setText(R.string.displaying_message);
1233                     this.changeToDisplaying = false;
1234                 }
1235             }
1236
1237             /**
1238              * Create a hex dump of the data while show progress to user
1239              *
1240              * @param data The data to hex dump
1241              * @return StringBuilder The hex dump buffer
1242              */
1243             private String toHexDump(byte[] data) {
1244                 //Change to binary mode
1245                 this.changeToBinaryMode = true;
1246
1247                 // Start progress
1248                 publishProgress(Integer.valueOf(0));
1249
1250                 // Calculate max dir size
1251                 int length = data.length;
1252
1253                 final int DISPLAY_SIZE = 16;  // Bytes per line
1254                 ByteArrayInputStream bais = new ByteArrayInputStream(data);
1255                 byte[] line = new byte[DISPLAY_SIZE];
1256                 int read = 0;
1257                 int offset = 0;
1258                 StringBuilder sb = new StringBuilder();
1259                 while ((read = bais.read(line, 0, DISPLAY_SIZE)) != -1) {
1260                     //offset   dump(16)   data\n
1261                     String linedata = new String(line, 0, read);
1262                     sb.append(HexDump.toHexString(offset));
1263                     sb.append(" "); //$NON-NLS-1$
1264                     String hexDump = HexDump.toHexString(line, 0, read);
1265                     if (hexDump.length() != (DISPLAY_SIZE * 2)) {
1266                         char[] array = new char[(DISPLAY_SIZE * 2) - hexDump.length()];
1267                         Arrays.fill(array, ' ');
1268                         hexDump += new String(array);
1269                     }
1270                     sb.append(hexDump);
1271                     sb.append(" "); //$NON-NLS-1$
1272                     sb.append(linedata);
1273                     sb.append(EditorActivity.this.mHexLineSeparator);
1274                     offset += DISPLAY_SIZE;
1275                     if (offset % 5 == 0) {
1276                         publishProgress(Integer.valueOf((offset * 100) / length));
1277                     }
1278                 }
1279
1280                 // End of the dump process
1281                 publishProgress(Integer.valueOf(100));
1282
1283                 return sb.toString();
1284             }
1285
1286             /**
1287              * Method that converts to a visual printable hex string
1288              *
1289              * @param string The string to check
1290              */
1291             private String toHexPrintableString(String string) {
1292                 // Remove characters without visual representation
1293                 final String REPLACED_SYMBOL = "."; //$NON-NLS-1$
1294                 final String NEWLINE = System.getProperty("line.separator"); //$NON-NLS-1$
1295                 String printable = string.replaceAll("\\p{Cntrl}", REPLACED_SYMBOL); //$NON-NLS-1$
1296                 printable = printable.replaceAll("[^\\p{Print}]", REPLACED_SYMBOL); //$NON-NLS-1$
1297                 printable = printable.replaceAll("\\p{C}", REPLACED_SYMBOL); //$NON-NLS-1$
1298                 printable = printable.replaceAll(EditorActivity.this.mHexLineSeparator, NEWLINE);
1299                 return printable;
1300             }
1301         };
1302         mReadTask.execute(this.mFso);
1303     }
1304
1305     private void checkAndWrite() {
1306         // Check that we have write access
1307         try {
1308             FileHelper.ensureWriteAccess(
1309                     ConsoleBuilder.getConsole(this),
1310                     this.mFso,
1311                     null);
1312
1313             // Write the file
1314             ensureSyncWrite();
1315
1316         } catch (Exception ex) {
1317             ExceptionUtil.translateException(
1318                     this, ex, false, true, new OnRelaunchCommandResult() {
1319                 @Override
1320                 public void onSuccess() {
1321                     // Write the file
1322                     ensureSyncWrite();
1323                 }
1324
1325                 @Override
1326                 public void onFailed(Throwable cause) {/**NON BLOCK**/}
1327
1328                 @Override
1329                 public void onCancelled() {/**NON BLOCK**/}
1330             });
1331         }
1332     }
1333
1334     /**
1335      * Method that checks that the write to disk operation was successfully and the
1336      * expected bytes are written to disk.
1337      * @hide
1338      */
1339     void ensureSyncWrite() {
1340         try {
1341             for (int i = 0; i < WRITE_RETRIES; i++) {
1342                 // Configure the writer
1343                 AsyncWriter writer = new AsyncWriter();
1344
1345                 // Write to disk
1346                 final byte[] data = this.mEditor.getText().toString().getBytes();
1347                 long expected = data.length;
1348                 syncWrite(writer, data);
1349
1350                 // Sleep a bit
1351                 Thread.sleep(150L);
1352
1353                 // Is error?
1354                 if (writer.mCause != null) {
1355                     Log.e(TAG, "Write operation failed. Retries: " + i, writer.mCause);
1356                     if (i == (WRITE_RETRIES-1)) {
1357                         // Something was wrong. The file probably is corrupted
1358                         DialogHelper.showToast(
1359                                 this, R.string.msgs_operation_failure, Toast.LENGTH_SHORT);
1360                         break;
1361                     }
1362
1363                     // Retry
1364                     continue;
1365                 }
1366
1367                 // Check that all the bytes were written
1368                 FileSystemObject fso =
1369                         CommandHelper.getFileInfo(this, this.mFso.getFullPath(), true, null);
1370                 if (fso == null || fso.getSize() != expected) {
1371                     Log.e(TAG, String.format(
1372                             "Size is not the same. Expected: %d, Written: %d. Retries: %d",
1373                             expected, fso == null ? -1 : fso.getSize(), i));
1374                     if (i == (WRITE_RETRIES-1)) {
1375                         // Something was wrong. The destination data is not the same
1376                         // as the source data
1377                         DialogHelper.showToast(
1378                                 this, R.string.msgs_operation_failure, Toast.LENGTH_SHORT);
1379                         break;
1380                     }
1381
1382                     // Retry
1383                     continue;
1384                 }
1385
1386                 // Success. The file was saved
1387                 DialogHelper.showToast(
1388                         this, R.string.editor_successfully_saved, Toast.LENGTH_SHORT);
1389                 setDirty(false);
1390
1391                 // Send a message that allow other activities to update his data
1392                 Intent intent = new Intent(FileManagerSettings.INTENT_FILE_CHANGED);
1393                 intent.putExtra(
1394                         FileManagerSettings.EXTRA_FILE_CHANGED_KEY, this.mFso.getFullPath());
1395                 sendBroadcast(intent);
1396
1397                 // Done
1398                 break;
1399
1400             }
1401         } catch (Exception ex) {
1402             // Something was wrong, but the file was NOT written
1403             Log.e(TAG, "The file wasn't written.", ex);
1404             DialogHelper.showToast(
1405                     this, R.string.msgs_operation_failure, Toast.LENGTH_SHORT);
1406         }
1407     }
1408
1409     /**
1410      * Method that write the file.
1411      *
1412      * @param writer The command listener
1413      * @param bytes The bytes to write
1414      * @throws Exception If something was wrong
1415      */
1416     private void syncWrite(AsyncWriter writer, byte[] bytes) throws Exception {
1417         // Create the writable command
1418         WriteExecutable cmd =
1419                 CommandHelper.write(this, this.mFso.getFullPath(), writer, null);
1420
1421         // Obtain access to the buffer (IMP! don't close the buffer here, it's manage
1422         // by the command)
1423         OutputStream os = cmd.createOutputStream();
1424         try {
1425             // Retrieve the text from the editor
1426             ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
1427             try {
1428                 // Buffered write
1429                 byte[] data = new byte[this.mBufferSize];
1430                 int read = 0, written = 0;
1431                 while ((read = bais.read(data, 0, this.mBufferSize)) != -1) {
1432                     os.write(data, 0, read);
1433                     written += read;
1434                 }
1435                 Log.i(TAG, "Bytes written: " + written); //$NON-NLS-1$
1436             } finally {
1437                 try {
1438                     bais.close();
1439                 } catch (Exception e) {/**NON BLOCK**/}
1440             }
1441
1442         } finally {
1443             // Ok. Data is written or ensure buffer close
1444             cmd.end();
1445         }
1446     }
1447
1448     /**
1449      * {@inheritDoc}
1450      */
1451     @Override
1452     public void beforeTextChanged(
1453             CharSequence s, int start, int count, int after) {/**NON BLOCK**/}
1454
1455     /**
1456      * {@inheritDoc}
1457      */
1458     @Override
1459     public void onTextChanged(CharSequence s, int start, int before, int count) {
1460         this.mEditStart = start;
1461         this.mEditEnd = start + count;
1462     }
1463
1464     /**
1465      * {@inheritDoc}
1466      */
1467     @Override
1468     public void afterTextChanged(Editable s) {
1469         setDirty(true);
1470         if (this.mSyntaxHighlightProcessor != null) {
1471             this.mSyntaxHighlightProcessor.process(s, this.mEditStart, this.mEditEnd);
1472         }
1473     }
1474
1475     /**
1476      * Method that sets if the editor is dirty (has changed)
1477      *
1478      * @param dirty If the editor is dirty
1479      * @hide
1480      */
1481     void setDirty(boolean dirty) {
1482         this.mDirty = dirty;
1483         this.mSave.setVisibility(dirty ? View.VISIBLE : View.GONE);
1484     }
1485
1486     /**
1487      * Check the dirty state of the editor, and ask the user to save the changes
1488      * prior to exit.
1489      */
1490     public void checkDirtyState() {
1491         if (this.mDirty) {
1492             AlertDialog dlg = DialogHelper.createYesNoDialog(
1493                     this,
1494                     R.string.editor_dirty_ask_title,
1495                     R.string.editor_dirty_ask_msg,
1496                     new OnClickListener() {
1497                         @Override
1498                         public void onClick(DialogInterface dialog, int which) {
1499                             if (which == DialogInterface.BUTTON_POSITIVE) {
1500                                 dialog.dismiss();
1501                                 setResult(Activity.RESULT_OK);
1502                                 finish();
1503                             }
1504                         }
1505                     });
1506             DialogHelper.delegateDialogShow(this, dlg);
1507             return;
1508         }
1509         setResult(Activity.RESULT_OK);
1510         finish();
1511     }
1512
1513     /**
1514      * Method that applies the current theme to the activity
1515      * @hide
1516      */
1517     void applyTheme() {
1518         Theme theme = ThemeManager.getCurrentTheme(this);
1519         theme.setBaseTheme(this, false);
1520
1521         //- ActionBar
1522         theme.setTitlebarDrawable(this, getActionBar(), "titlebar_drawable"); //$NON-NLS-1$
1523         View v = getActionBar().getCustomView().findViewById(R.id.customtitle_title);
1524         theme.setTextColor(this, (TextView)v, "text_color"); //$NON-NLS-1$
1525         v = findViewById(R.id.ab_button1);
1526         theme.setImageDrawable(this, (ImageView)v, "ab_save_drawable"); //$NON-NLS-1$
1527         v = findViewById(R.id.ab_button2);
1528         theme.setImageDrawable(this, (ImageView)v, "ab_overflow_drawable"); //$NON-NLS-1$
1529         //- View
1530         v = findViewById(R.id.editor_layout);
1531         theme.setBackgroundDrawable(this, v, "background_drawable"); //$NON-NLS-1$
1532         v = findViewById(R.id.editor);
1533         theme.setTextColor(this, (TextView)v, "text_color"); //$NON-NLS-1$
1534         //- ProgressBar
1535         Drawable dw = theme.getDrawable(this, "horizontal_progress_bar"); //$NON-NLS-1$
1536         this.mProgressBar.setProgressDrawable(dw);
1537         v = findViewById(R.id.editor_progress_msg);
1538         theme.setTextColor(this, (TextView)v, "text_color"); //$NON-NLS-1$
1539
1540         // Need a full process of syntax highlight
1541         if (!this.mBinary && this.mSyntaxHighlight && this.mSyntaxHighlightProcessor != null) {
1542             reloadSyntaxHighlight();
1543         }
1544     }
1545
1546     /**
1547      * Method that resolves the content uri to a valid system path
1548      *
1549      * @param ctx The current context
1550      * @param uri The content uri
1551      * @return String The system path
1552      */
1553     private static String uriToPath(Context ctx, Uri uri) {
1554         File file = MediaHelper.contentUriToFile(ctx.getContentResolver(), uri);
1555         if (file == null) {
1556             file = new File(uri.getPath());
1557         }
1558         return file.getAbsolutePath();
1559     }
1560 }