OSDN Git Service

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