import android.text.Editable;
import android.text.InputType;
import android.text.SpannableStringBuilder;
-import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.Log;
import android.view.KeyEvent;
import com.cyanogenmod.filemanager.ash.SyntaxHighlightProcessor;
import com.cyanogenmod.filemanager.commands.AsyncResultListener;
import com.cyanogenmod.filemanager.commands.WriteExecutable;
+import com.cyanogenmod.filemanager.commands.shell.InvalidCommandDefinitionException;
+import com.cyanogenmod.filemanager.console.Console;
+import com.cyanogenmod.filemanager.console.ConsoleAllocException;
import com.cyanogenmod.filemanager.console.ConsoleBuilder;
+import com.cyanogenmod.filemanager.console.InsufficientPermissionsException;
+import com.cyanogenmod.filemanager.console.java.JavaConsole;
import com.cyanogenmod.filemanager.model.FileSystemObject;
import com.cyanogenmod.filemanager.preferences.FileManagerSettings;
import com.cyanogenmod.filemanager.preferences.Preferences;
import com.cyanogenmod.filemanager.ui.ThemeManager;
import com.cyanogenmod.filemanager.ui.ThemeManager.Theme;
+import com.cyanogenmod.filemanager.ui.policy.PrintActionPolicy;
import com.cyanogenmod.filemanager.ui.widgets.ButtonItem;
import com.cyanogenmod.filemanager.util.AndroidHelper;
import com.cyanogenmod.filemanager.util.CommandHelper;
import com.cyanogenmod.filemanager.util.FileHelper;
import com.cyanogenmod.filemanager.util.MediaHelper;
import com.cyanogenmod.filemanager.util.ResourcesHelper;
+import com.cyanogenmod.filemanager.util.StringHelper;
+import org.mozilla.universalchardet.UniversalDetector;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
import java.io.OutputStream;
import java.io.StringReader;
import java.util.ArrayList;
});
}
- } else if (key.compareTo(FileManagerSettings.SETTINGS_EDITOR_SH_USE_THEME_DEFAULT.getId()) == 0 ||
- key.compareTo(FileManagerSettings.SETTINGS_EDITOR_SH_COLOR_SCHEME.getId()) == 0 ) {
+ } else if (key.compareTo(FileManagerSettings.SETTINGS_EDITOR_SH_COLOR_SCHEME.getId()) == 0 ) {
// Ignore in binary files
if (activity.mBinary) return;
}
};
- private static class HexDumpAdapter extends ArrayAdapter<String> {
- private static class ViewHolder {
+ private class HexDumpAdapter extends ArrayAdapter<String> {
+ private class ViewHolder {
TextView mTextView;
}
viewHolder.mTextView = (TextView)v.findViewById(android.R.id.text1);
viewHolder.mTextView.setTextAppearance(context, R.style.hexeditor_text_appearance);
- viewHolder.mTextView.setTypeface(Typeface.MONOSPACE);
+ viewHolder.mTextView.setTypeface(mHexTypeface);
theme.setTextColor(context, viewHolder.mTextView, "text_color"); //$NON-NLS-1$
v.setTag(viewHolder);
return v;
}
+
+ /**
+ * Return the view as a document
+ *
+ * @return StringBuilder a buffer to the document
+ */
+ public StringBuilder toStringDocument() {
+ StringBuilder sb = new StringBuilder();
+ int c = getCount();
+ for (int i = 0; i < c; i++) {
+ sb.append(getItem(i));
+ sb.append("\n");
+ }
+ return sb;
+ }
}
/**
long mSize;
FileSystemObject mReadFso;
OnProgressListener mListener;
+ boolean mDetectEncoding = false;
+ UniversalDetector mDetector;
+ String mDetectedEncoding;
/**
* Constructor of <code>AsyncReader</code>. For enclosing access.
*/
- public AsyncReader() {
+ public AsyncReader(boolean detectEncoding) {
super();
+ mDetectEncoding = detectEncoding;
+ if (mDetectEncoding) {
+ mDetector = new UniversalDetector(null);
+ }
}
/**
* {@inheritDoc}
*/
@Override
- public void onAsyncEnd(boolean cancelled) {/**NON BLOCK**/}
+ public void onAsyncEnd(boolean cancelled) {
+ if (!cancelled && StringHelper.isBinaryData(mByteBuffer.toByteArray())) {
+ EditorActivity.this.mBinary = true;
+ EditorActivity.this.mReadOnly = true;
+ } else if (mDetector != null) {
+ mDetector.dataEnd();
+ mDetectedEncoding = mDetector.getDetectedCharset();
+ }
+ }
/**
* {@inheritDoc}
public void onPartialResult(Object result) {
try {
if (result == null) return;
- byte[] partial = (byte[])result;
-
- // Check if the file is a binary file. In this case the editor
- // is read-only
- if (!EditorActivity.this.mReadOnly) {
- for (int i = 0; i < partial.length-1; i++) {
- if (!isPrintableCharacter((char)partial[i])) {
- EditorActivity.this.mBinary = true;
- EditorActivity.this.mReadOnly = true;
- break;
- }
- }
+ byte[] partial = (byte[]) result;
+ if (mDetectEncoding) {
+ mDetector.handleData(partial, 0, partial.length);
}
-
this.mByteBuffer.write(partial, 0, partial.length);
this.mSize += partial.length;
if (this.mListener != null && this.mReadFso != null) {
public int getColor(String id, String resid, int def) {
final Context ctx = EditorActivity.this;
try {
- // Is default theme color scheme enabled?
- if (isDefaultThemeColorScheme()) {
- return ThemeManager.getCurrentTheme(ctx).getColor(ctx, resid);
- }
-
// Use the user-defined settings
int[] colors = getUserColorScheme();
HighlightColors[] schemeColors = HighlightColors.values();
}
/**
- * Method that returns if we should return the default theme color scheme or not
- *
- * @return boolean Whether return the default theme color scheme or not
- */
- private boolean isDefaultThemeColorScheme() {
- Boolean defaultValue =
- (Boolean)FileManagerSettings.
- SETTINGS_EDITOR_SH_USE_THEME_DEFAULT.getDefaultValue();
- return Preferences.getSharedPreferences().getBoolean(
- FileManagerSettings.SETTINGS_EDITOR_SH_USE_THEME_DEFAULT.getId(),
- defaultValue.booleanValue());
- }
-
- /**
* Method that returns the user-defined color scheme
*
* @return int[] The user-defined color scheme
* @hide
*/
ButtonItem mSave;
+ /**
+ * @hide
+ */
+ ButtonItem mPrint;
// No suggestions status
/**
private View mOptionsAnchorView;
+ private Typeface mHexTypeface;
+
private final Object mExecSync = new Object();
/**
*/
Handler mHandler;
- private static final char[] VALID_NON_PRINTABLE_CHARS = {' ', '\t', '\r', '\n'};
-
/**
* @hide
*/
String mHexLineSeparator;
+ private boolean mHexDump;
+
/**
* Intent extra parameter for the path of the file to open.
*/
this.mHandler = new Handler();
+ // Load typeface for hex editor
+ mHexTypeface = Typeface.createFromAsset(getAssets(), "fonts/Courier-Prime.ttf");
+
+ // Save hexdump user preference
+ mHexDump = Preferences.getSharedPreferences().getBoolean(
+ FileManagerSettings.SETTINGS_EDITOR_HEXDUMP.getId(),
+ ((Boolean)FileManagerSettings.SETTINGS_EDITOR_HEXDUMP.
+ getDefaultValue()).booleanValue());
+
// Register the broadcast receiver
IntentFilter filter = new IntentFilter();
filter.addAction(FileManagerSettings.INTENT_THEME_CHANGED);
private void initTitleActionBar() {
//Configure the action bar options
getActionBar().setBackgroundDrawable(
- getResources().getDrawable(R.drawable.bg_holo_titlebar));
+ getResources().getDrawable(R.drawable.bg_material_titlebar));
getActionBar().setDisplayOptions(
- ActionBar.DISPLAY_SHOW_CUSTOM | ActionBar.DISPLAY_SHOW_HOME);
+ ActionBar.DISPLAY_SHOW_CUSTOM);
getActionBar().setDisplayHomeAsUpEnabled(true);
View customTitle = getLayoutInflater().inflate(R.layout.simple_customtitle, null, false);
this.mTitle = (TextView)customTitle.findViewById(R.id.customtitle_title);
this.mTitle.setText(R.string.editor);
this.mTitle.setContentDescription(getString(R.string.editor));
- this.mSave = (ButtonItem)customTitle.findViewById(R.id.ab_button1);
- this.mSave.setImageResource(R.drawable.ic_holo_light_save);
+
+ this.mSave = (ButtonItem)customTitle.findViewById(R.id.ab_button0);
+ this.mSave.setImageResource(R.drawable.ic_material_light_save);
this.mSave.setContentDescription(getString(R.string.actionbar_button_save_cd));
this.mSave.setVisibility(View.GONE);
+ this.mPrint = (ButtonItem)customTitle.findViewById(R.id.ab_button1);
+ this.mPrint.setImageResource(R.drawable.ic_material_light_print);
+ this.mPrint.setContentDescription(getString(R.string.actionbar_button_print_cd));
+ this.mPrint.setVisibility(View.VISIBLE);
+
ButtonItem configuration = (ButtonItem)customTitle.findViewById(R.id.ab_button2);
- configuration.setImageResource(R.drawable.ic_holo_light_overflow);
+ configuration.setImageResource(R.drawable.ic_material_light_overflow);
configuration.setContentDescription(getString(R.string.actionbar_button_overflow_cd));
View status = findViewById(R.id.editor_status);
*/
private void showOverflowPopUp(View anchor) {
SimpleMenuListAdapter adapter =
- new HighlightedSimpleMenuListAdapter(this, R.menu.editor);
+ new HighlightedSimpleMenuListAdapter(this, R.menu.editor, true);
MenuItem noSuggestions = adapter.getMenu().findItem(R.id.mnu_no_suggestions);
if (noSuggestions != null) {
if (this.mBinary) {
*/
public void onActionBarItemClick(View view) {
switch (view.getId()) {
- case R.id.ab_button1:
+ case R.id.ab_button0:
// Save the file
checkAndWrite();
break;
+ case R.id.ab_button1:
+ // Print the file
+ StringBuilder sb = mBinary
+ ? ((HexDumpAdapter)mBinaryEditor.getAdapter()).toStringDocument()
+ : new StringBuilder(mEditor.getText().toString());
+ PrintActionPolicy.printStringDocument(this, mFso, sb);
+ break;
+
case R.id.ab_button2:
// Show overflow menu
showOverflowPopUp(this.mOptionsAnchorView);
this.mReadOnly = false;
// Read the intent and check that is has a valid request
- String path = uriToPath(this, getIntent().getData());
- if (path == null || path.length() == 0) {
- DialogHelper.showToast(
- this, R.string.editor_invalid_file_msg, Toast.LENGTH_SHORT);
- return;
- }
+ Intent fileIntent = getIntent();
+ if (fileIntent.getData().getScheme().equals("content")) {
+ asyncReadContentURI(fileIntent.getData());
+ } else {
+ // File Scheme URI's
+ String path = uriToPath(this, getIntent().getData());
+ if (path == null || path.length() == 0) {
+ DialogHelper.showToast(
+ this, R.string.editor_invalid_file_msg, Toast.LENGTH_SHORT);
+ return;
+ }
- // Set the title of the dialog
- File f = new File(path);
- this.mTitle.setText(f.getName());
+ // Set the title of the dialog
+ File f = new File(path);
+ this.mTitle.setText(f.getName());
- // Check that the file exists (the real file, not the symlink)
- try {
- this.mFso = CommandHelper.getFileInfo(this, path, true, null);
- if (this.mFso == null) {
+ // Check that the file exists (the real file, not the symlink)
+ try {
+ this.mFso = CommandHelper.getFileInfo(this, path, true, null);
+ if (this.mFso == null) {
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to get file reference", e); //$NON-NLS-1$
+ }
+
+ // Check that we can handle the length of the file (by device)
+ if (this.mMaxFileSize < this.mFso.getSize()) {
DialogHelper.showToast(
- this, R.string.editor_file_not_found_msg, Toast.LENGTH_SHORT);
+ this, R.string.editor_file_exceed_size_msg, Toast.LENGTH_SHORT);
return;
}
- } catch (Exception e) {
- Log.e(TAG, "Failed to get file reference", e); //$NON-NLS-1$
- DialogHelper.showToast(
- this, R.string.editor_file_not_found_msg, Toast.LENGTH_SHORT);
- return;
- }
- // Check that we can handle the length of the file (by device)
- if (this.mMaxFileSize < this.mFso.getSize()) {
- DialogHelper.showToast(
- this, R.string.editor_file_exceed_size_msg, Toast.LENGTH_SHORT);
- return;
- }
+ // Get the syntax highlight processor
+ SyntaxHighlightFactory shpFactory =
+ SyntaxHighlightFactory.getDefaultFactory(new ResourcesResolver());
+ this.mSyntaxHighlightProcessor = shpFactory.getSyntaxHighlightProcessor(f);
+ if (this.mSyntaxHighlightProcessor != null) {
+ this.mSyntaxHighlightProcessor.initialize();
+ }
- // Get the syntax highlight processor
- SyntaxHighlightFactory shpFactory =
- SyntaxHighlightFactory.getDefaultFactory(new ResourcesResolver());
- this.mSyntaxHighlightProcessor = shpFactory.getSyntaxHighlightProcessor(f);
- if (this.mSyntaxHighlightProcessor != null) {
- this.mSyntaxHighlightProcessor.initialize();
+ // Check that we have read access
+ try {
+ FileHelper.ensureReadAccess(
+ ConsoleBuilder.getConsole(this),
+ this.mFso,
+ null);
+
+ // Read the file in background
+ asyncRead();
+
+ } catch (Exception ex) {
+ ExceptionUtil.translateException(
+ this, ex, false, true, new OnRelaunchCommandResult() {
+ @Override
+ public void onSuccess() {
+ // Read the file in background
+ asyncRead();
+ }
+
+ @Override
+ public void onFailed(Throwable cause) {
+ finish();
+ }
+
+ @Override
+ public void onCancelled() {
+ finish();
+ }
+ });
+ }
}
+ }
- // Check that we have read access
- try {
- FileHelper.ensureReadAccess(
- ConsoleBuilder.getConsole(this),
- this.mFso,
- null);
+ /**
+ * Method that does the read of a content uri in the background
+ * @hide
+ */
+ void asyncReadContentURI(Uri uri) {
+ // Do the load of the file
+ AsyncTask<Uri, Integer, Boolean> mReadTask =
+ new AsyncTask<Uri, Integer, Boolean>() {
+
+ private Exception mCause;
+ private boolean changeToBinaryMode;
+ private boolean changeToDisplaying;
+ private String tempText;
+
+ @Override
+ protected void onPreExecute() {
+ // Show the progress
+ this.changeToBinaryMode = false;
+ this.changeToDisplaying = false;
+ doProgress(true, 0);
+ }
- // Read the file in background
- asyncRead();
+ @Override
+ protected Boolean doInBackground(Uri... params) {
- } catch (Exception ex) {
- ExceptionUtil.translateException(
- this, ex, false, true, new OnRelaunchCommandResult() {
- @Override
- public void onSuccess() {
- // Read the file in background
- asyncRead();
- }
+ // Only one argument (the file to open)
+ Uri fso = params[0];
+ this.mCause = null;
- @Override
- public void onFailed(Throwable cause) {
- finish();
- }
+ // Read the file in an async listener
+ try {
- @Override
- public void onCancelled() {
- finish();
- }
- });
- }
+ publishProgress(Integer.valueOf(0));
+
+ InputStream is = getContentResolver().openInputStream(fso);
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ int readBytes;
+ byte[] buffer = new byte[1024];
+ try {
+ while ((readBytes = is.read(buffer, 0, buffer.length)) != -1) {
+ baos.write(buffer, 0, readBytes);
+ publishProgress(
+ Integer.valueOf((readBytes * 100) / buffer.length));
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ return Boolean.FALSE;
+ }
+
+ // 100%
+ publishProgress(new Integer(100));
+ tempText = new String(baos.toByteArray(), "UTF-8");
+ Log.i(TAG, "Bytes read: " + baos.toByteArray().length); //$NON-NLS-1$
+
+ // 100%
+ this.changeToDisplaying = true;
+ publishProgress(new Integer(0));
+
+ } catch (Exception e) {
+ this.mCause = e;
+ return Boolean.FALSE;
+ }
+
+ return Boolean.TRUE;
+
+ }
+
+ @Override
+ protected void onProgressUpdate(Integer... values) {
+ // Do progress
+ doProgress(true, values[0].intValue());
+ }
+
+ @Override
+ protected void onPostExecute(Boolean result) {
+ final EditorActivity activity = EditorActivity.this;
+ // Is error?
+ if (!result.booleanValue()) {
+ if (this.mCause != null) {
+ ExceptionUtil.translateException(activity, this.mCause);
+ activity.mEditor.setEnabled(false);
+ }
+ } else {
+ // Now we have the buffer, set the text of the editor
+ activity.mEditor.setText(
+ tempText, BufferType.EDITABLE);
+
+ // Highlight editor text syntax
+ if (activity.mSyntaxHighlight &&
+ activity.mSyntaxHighlightProcessor != null) {
+ try {
+ activity.mSyntaxHighlightProcessor.process(
+ activity.mEditor.getText());
+ } catch (Exception ex) {
+ // An error in a syntax library, should not break down app.
+ Log.e(TAG, "Syntax highlight failed.", ex); //$NON-NLS-1$
+ }
+ }
+
+ setDirty(false);
+ activity.mEditor.setEnabled(!activity.mReadOnly);
+
+ // Notify read-only mode
+ if (activity.mReadOnly) {
+ DialogHelper.showToast(
+ activity,
+ R.string.editor_read_only_mode,
+ Toast.LENGTH_SHORT);
+ }
+ }
+
+ doProgress(false, 0);
+ }
+
+ @Override
+ protected void onCancelled() {
+ // Hide the progress
+ doProgress(false, 0);
+ }
+
+ /**
+ * Method that update the progress status
+ *
+ * @param visible If the progress bar need to be hidden
+ * @param progress The progress
+ */
+ private void doProgress(boolean visible, int progress) {
+ final EditorActivity activity = EditorActivity.this;
+
+ // Show the progress bar
+ activity.mProgressBar.setProgress(progress);
+ activity.mProgress.setVisibility(visible ? View.VISIBLE : View.GONE);
+
+ if (this.changeToBinaryMode) {
+ mWordWrapView.setVisibility(View.GONE);
+ mNoWordWrapView.setVisibility(View.GONE);
+ mBinaryEditor.setVisibility(View.VISIBLE);
+
+ // Show hex dumping text
+ activity.mProgressBarMsg.setText(R.string.dumping_message);
+ this.changeToBinaryMode = false;
+ }
+ else if (this.changeToDisplaying) {
+ activity.mProgressBarMsg.setText(R.string.displaying_message);
+ this.changeToDisplaying = false;
+ }
+ }
+ };
+ mReadTask.execute(uri);
}
/**
try {
while (true) {
// Configure the reader
- this.mReader = new AsyncReader();
+ this.mReader = new AsyncReader(true);
this.mReader.mReadFso = fso;
this.mReader.mListener = new OnProgressListener() {
@Override
};
// Execute the command (read the file)
- CommandHelper.read(activity, fso.getFullPath(), this.mReader, null);
+ CommandHelper.read(activity, fso.getFullPath(), this.mReader,
+ null);
// Wait for
synchronized (this.mReader.mSync) {
// Now we have the byte array with all the data. is a binary file?
// Then dump them byte array to hex dump string (only if users settings
// to dump file)
- boolean hexDump =
- Preferences.getSharedPreferences().getBoolean(
- FileManagerSettings.SETTINGS_EDITOR_HEXDUMP.getId(),
- ((Boolean)FileManagerSettings.SETTINGS_EDITOR_HEXDUMP.
- getDefaultValue()).booleanValue());
- if (activity.mBinary && hexDump) {
+ if (activity.mBinary && mHexDump) {
// we do not use the Hexdump helper class, because we need to show the
// progress of the dump process
final String data = toHexPrintableString(toHexDump(
}
Log.i(TAG, "Bytes read: " + data.length()); //$NON-NLS-1$
} else {
- final String data = new String(this.mReader.mByteBuffer.toByteArray());
+ String data;
+ if (this.mReader.mDetectedEncoding != null) {
+ data = new String(this.mReader.mByteBuffer.toByteArray(),
+ this.mReader.mDetectedEncoding);
+ } else {
+ data = new String(this.mReader.mByteBuffer.toByteArray());
+ }
this.mReader.mBuffer = new SpannableStringBuilder(data);
Log.i(TAG, "Bytes read: " + data.getBytes().length); //$NON-NLS-1$
}
}
} else {
// Now we have the buffer, set the text of the editor
- if (activity.mBinary) {
+ if (activity.mBinary && mHexDump) {
HexDumpAdapter adapter = new HexDumpAdapter(EditorActivity.this,
this.mReader.mBinaryBuffer);
mBinaryEditor.setAdapter(adapter);
}
/**
- * Method that check if a character is valid printable character
- *
- * @param c The character to check
- * @return boolean If the character is printable
- * @hide
- */
- static boolean isPrintableCharacter(char c) {
- int cc = VALID_NON_PRINTABLE_CHARS.length;
- for (int i = 0; i < cc; i++) {
- if (c == VALID_NON_PRINTABLE_CHARS[i]) {
- return true;
- }
- }
- return TextUtils.isGraphic(c);
- }
-
- /**
* Method that applies the current theme to the activity
* @hide
*/
//- ActionBar
theme.setTitlebarDrawable(this, getActionBar(), "titlebar_drawable"); //$NON-NLS-1$
View v = getActionBar().getCustomView().findViewById(R.id.customtitle_title);
- theme.setTextColor(this, (TextView)v, "text_color"); //$NON-NLS-1$
- v = findViewById(R.id.ab_button1);
+ theme.setTextColor(this, (TextView)v, "action_bar_text_color"); //$NON-NLS-1$
+ v = findViewById(R.id.ab_button0);
theme.setImageDrawable(this, (ImageView)v, "ab_save_drawable"); //$NON-NLS-1$
+ v = findViewById(R.id.ab_button1);
+ theme.setImageDrawable(this, (ImageView)v, "ab_print_drawable"); //$NON-NLS-1$
v = findViewById(R.id.ab_button2);
theme.setImageDrawable(this, (ImageView)v, "ab_overflow_drawable"); //$NON-NLS-1$
//- View