aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--AndroidManifest.xml12
-rw-r--r--res/drawable-hdpi/ic_holo_light_save.pngbin0 -> 1428 bytes
-rw-r--r--res/drawable-hdpi/ic_launcher_editor.pngbin0 -> 3890 bytes
-rw-r--r--res/drawable-mdpi/ic_holo_light_save.pngbin0 -> 1379 bytes
-rw-r--r--res/drawable-mdpi/ic_launcher_editor.pngbin0 -> 2126 bytes
-rw-r--r--res/drawable-xhdpi/ic_holo_light_save.pngbin0 -> 1576 bytes
-rw-r--r--res/drawable-xhdpi/ic_launcher_editor.pngbin0 -> 5464 bytes
-rw-r--r--res/layout/editor.xml76
-rw-r--r--res/values/dimen.xml3
-rw-r--r--res/values/overlay.xml8
-rw-r--r--res/values/strings.xml17
-rw-r--r--src/com/cyanogenmod/explorer/activities/EditorActivity.java645
-rw-r--r--src/com/cyanogenmod/explorer/console/shell/ShellConsole.java113
-rw-r--r--src/com/cyanogenmod/explorer/ui/dialogs/AssociationsDialog.java16
-rw-r--r--src/com/cyanogenmod/explorer/ui/policy/IntentsActionPolicy.java90
-rw-r--r--src/com/cyanogenmod/explorer/util/CommandHelper.java2
16 files changed, 942 insertions, 40 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 09b016f9..18c2aeb6 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -131,6 +131,18 @@
</intent-filter>
</activity>
+ <activity
+ android:name=".activities.EditorActivity"
+ android:label="@string/editor"
+ android:icon="@drawable/ic_launcher_editor"
+ android:exported="false">
+ <intent-filter>
+ <action android:name="android.intent.action.EDIT" />
+ <category android:name="com.cyanogenmod.explorer.category.INTERNAL_VIEWER" />
+ <category android:name="com.cyanogenmod.explorer.category.EDITOR" />
+ </intent-filter>
+ </activity>
+
</application>
</manifest>
diff --git a/res/drawable-hdpi/ic_holo_light_save.png b/res/drawable-hdpi/ic_holo_light_save.png
new file mode 100644
index 00000000..07c2817b
--- /dev/null
+++ b/res/drawable-hdpi/ic_holo_light_save.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_launcher_editor.png b/res/drawable-hdpi/ic_launcher_editor.png
new file mode 100644
index 00000000..4321cad2
--- /dev/null
+++ b/res/drawable-hdpi/ic_launcher_editor.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_holo_light_save.png b/res/drawable-mdpi/ic_holo_light_save.png
new file mode 100644
index 00000000..d12760bb
--- /dev/null
+++ b/res/drawable-mdpi/ic_holo_light_save.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_launcher_editor.png b/res/drawable-mdpi/ic_launcher_editor.png
new file mode 100644
index 00000000..9e44ba63
--- /dev/null
+++ b/res/drawable-mdpi/ic_launcher_editor.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_holo_light_save.png b/res/drawable-xhdpi/ic_holo_light_save.png
new file mode 100644
index 00000000..9460b391
--- /dev/null
+++ b/res/drawable-xhdpi/ic_holo_light_save.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_launcher_editor.png b/res/drawable-xhdpi/ic_launcher_editor.png
new file mode 100644
index 00000000..8a0ea907
--- /dev/null
+++ b/res/drawable-xhdpi/ic_launcher_editor.png
Binary files differ
diff --git a/res/layout/editor.xml b/res/layout/editor.xml
new file mode 100644
index 00000000..b10ead5d
--- /dev/null
+++ b/res/layout/editor.xml
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ** Copyright (C) 2012 The CyanogenMod Project
+ **
+ ** Licensed under the Apache License, Version 2.0 (the "License");
+ ** you may not use this file except in compliance with the License.
+ ** You may obtain a copy of the License at
+ **
+ ** http://www.apache.org/licenses/LICENSE-2.0
+ **
+ ** Unless required by applicable law or agreed to in writing, software
+ ** distributed under the License is distributed on an "AS IS" BASIS,
+ ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ** See the License for the specific language governing permissions and
+ ** limitations under the License. -->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <ScrollView
+ android:id="@+id/editor_scroller"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentRight="true"
+ android:layout_alignParentBottom="true"
+ android:layout_margin="@dimen/default_margin"
+ android:scrollbars="vertical"
+ android:fillViewport="true">
+
+ <EditText
+ android:id="@+id/editor"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:singleLine="false"
+ android:maxLength="@integer/editor_max_file_size"
+ android:gravity="top|left"
+ android:cursorVisible="true"
+ android:background="@null"
+ android:inputType="textMultiLine|textImeMultiLine"
+ android:text="@null"
+ android:textAppearance="@style/secondary_text_appearance" />
+
+ </ScrollView>
+
+
+ <RelativeLayout
+ android:id="@+id/editor_progress"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/default_progress_height"
+ android:maxWidth="400dip"
+ android:layout_margin="@dimen/extra_large_margin"
+ android:layout_centerHorizontal="true"
+ android:layout_alignParentBottom="true"
+ android:visibility="gone">
+
+ <ProgressBar
+ android:id="@+id/editor_progress_bar"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ style="@android:style/Widget.ProgressBar.Horizontal"
+ android:indeterminate="false" />
+
+ <TextView
+ android:id="@+id/editor_progress_msg"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:textAppearance="@style/primary_text_appearance"
+ android:text="@string/loading_message"
+ android:gravity="center_vertical|center_horizontal"
+ android:contentDescription="@null" />
+
+ </RelativeLayout>
+
+</RelativeLayout> \ No newline at end of file
diff --git a/res/values/dimen.xml b/res/values/dimen.xml
index be6a3b45..0796c483 100644
--- a/res/values/dimen.xml
+++ b/res/values/dimen.xml
@@ -98,4 +98,7 @@
<!-- The console height -->
<dimen name="console_height">250dp</dimen>
+ <!-- The progress height -->
+ <dimen name="default_progress_height">32dp</dimen>
+
</resources> \ No newline at end of file
diff --git a/res/values/overlay.xml b/res/values/overlay.xml
index f32e9b35..3727e02f 100644
--- a/res/values/overlay.xml
+++ b/res/values/overlay.xml
@@ -16,9 +16,8 @@
-->
<!--
- This file contains values that could be overlayed. This allow
- configure special values for each device. Use overlay building folder on device tree
- for overlay this values
+ This file contains values that could be overlayed. This allow configure special values
+ for each device. Use overlay building folder on device tree for overlay this values
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android">
@@ -46,4 +45,7 @@
<!-- The number of characters per line to show in the console dialog -->
<integer name="console_max_chars_per_line">150</integer>
+ <!-- The max limit of file size that the internal editor can open. Default: 4Mb -->
+ <integer name="editor_max_file_size">4194304</integer>
+
</resources> \ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
index f4f0654b..e33ff93d 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -76,7 +76,7 @@
is to switch to non-privileged mode.\n\nApply this change?</string>
<!-- A console couldn't be created. Without a console, the application won't work -->
- <string name="msgs_cant_create_console">The application is unable to create a console and
+ <string name="msgs_cant_create_console">The application is unable to create a console and
will not work without a console.</string>
<!-- The message shown when a allocation of a privileged console fails, and a non
privileged is allocated -->
@@ -179,6 +179,8 @@
<string name="actionbar_button_overflow_cd">More options</string>
<!-- ActionBar Buttons * Storage volumes -->
<string name="actionbar_button_storage_cd">Storage volumes</string>
+ <!-- ActionBar Buttons * Save -->
+ <string name="actionbar_button_save_cd">Save</string>
<!-- Navigation View * Sort * Sort by name (ascending) -->
<string name="sort_by_name_asc">By name &#x25B2;</string>
@@ -351,6 +353,19 @@
<!-- Picker Activity * Dialog title -->
<string name="picker_title">Pick a file</string>
+ <!-- Editor * Editor activity title -->
+ <string name="editor">Editor</string>
+ <!-- Editor * Invalid file message -->
+ <string name="editor_invalid_file_msg">Invalid file.</string>
+ <!-- Editor * File not found message -->
+ <string name="editor_file_not_found_msg">File not found.</string>
+ <!-- Editor * File size exceed the limit -->
+ <string name="editor_file_exceed_size_msg">The file is too big to be open inside this device.</string>
+ <!-- Editor * Editor is dirty, ask the user -->
+ <string name="editor_dirty_ask">There are unsaved changes.\n\nExit without saving?</string>
+ <!-- Editor * Save operation success -->
+ <string name="editor_successfully_saved">The file was successfully saved.</string>
+
<!-- Bookmarks * Bookmarks activity title -->
<string name="bookmarks">Bookmarks</string>
<!-- Bookmarks * Bookmark name * Home -->
diff --git a/src/com/cyanogenmod/explorer/activities/EditorActivity.java b/src/com/cyanogenmod/explorer/activities/EditorActivity.java
new file mode 100644
index 00000000..f8c0ce3a
--- /dev/null
+++ b/src/com/cyanogenmod/explorer/activities/EditorActivity.java
@@ -0,0 +1,645 @@
+/*
+ * Copyright (C) 2012 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.cyanogenmod.explorer.activities;
+
+import android.app.ActionBar;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.EditText;
+import android.widget.ProgressBar;
+import android.widget.ScrollView;
+import android.widget.TextView;
+import android.widget.TextView.BufferType;
+import android.widget.Toast;
+
+import com.cyanogenmod.explorer.R;
+import com.cyanogenmod.explorer.commands.AsyncResultListener;
+import com.cyanogenmod.explorer.commands.WriteExecutable;
+import com.cyanogenmod.explorer.console.ConsoleBuilder;
+import com.cyanogenmod.explorer.model.FileSystemObject;
+import com.cyanogenmod.explorer.ui.widgets.ButtonItem;
+import com.cyanogenmod.explorer.util.CommandHelper;
+import com.cyanogenmod.explorer.util.DialogHelper;
+import com.cyanogenmod.explorer.util.ExceptionUtil;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.OutputStream;
+
+/**
+ * An internal activity for view and edit files.
+ */
+public class EditorActivity extends Activity implements TextWatcher {
+
+ private static final String TAG = "EditorActivity"; //$NON-NLS-1$
+
+ private static boolean DEBUG = false;
+
+ /**
+ * Internal interface to notify progress update
+ */
+ private interface OnProgressListener {
+ void onProgress(int progress);
+ }
+
+ /**
+ * An internal listener for read a file
+ */
+ @SuppressWarnings("hiding")
+ private class AsyncReader implements AsyncResultListener {
+
+ final Object mSync = new Object();
+ StringBuilder mBuffer = new StringBuilder();
+ Exception mCause;
+ long mSize;
+ FileSystemObject mFso;
+ OnProgressListener mListener;
+
+ /**
+ * Constructor of <code>AsyncReader</code>. For enclosing access.
+ */
+ public AsyncReader() {
+ super();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onAsyncStart() {
+ this.mBuffer = new StringBuilder();
+ this.mSize = 0;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onAsyncEnd(boolean cancelled) {/**NON BLOCK**/}
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onAsyncExitCode(int exitCode) {
+ synchronized (this.mSync) {
+ this.mSync.notify();
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onPartialResult(Object result) {
+ try {
+ byte[] partial = (byte[])result;
+ this.mBuffer.append(new String(partial));
+ this.mSize += this.mBuffer.length();
+ if (this.mListener != null && this.mFso != null) {
+ int progress = 0;
+ if (this.mFso.getSize() != 0) {
+ progress = (int)((this.mSize*100) / this.mFso.getSize());
+ }
+ this.mListener.onProgress(progress);
+ }
+ } catch (Exception e) {
+ this.mCause = e;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onException(Exception cause) {
+ this.mCause = cause;
+ }
+ }
+
+ /**
+ * An internal listener for write a file
+ */
+ private class AsyncWriter implements AsyncResultListener {
+
+ Exception mCause;
+
+ /**
+ * Constructor of <code>AsyncWriter</code>. For enclosing access.
+ */
+ public AsyncWriter() {
+ super();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onAsyncStart() {/**NON BLOCK**/}
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onAsyncEnd(boolean cancelled) {/**NON BLOCK**/}
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onAsyncExitCode(int exitCode) {/**NON BLOCK**/}
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onPartialResult(Object result) {/**NON BLOCK**/}
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onException(Exception cause) {
+ this.mCause = cause;
+ }
+ }
+
+ private FileSystemObject mFso;
+
+ private int mBufferSize;
+ private int mMaxFileSize;
+
+ /**
+ * @hide
+ */
+ boolean mDirty;
+ /**
+ * @hide
+ */
+ boolean mReadOnly;
+
+ /**
+ * @hide
+ */
+ TextView mTitle;
+ /**
+ * @hide
+ */
+ ScrollView mScroll;
+ /**
+ * @hide
+ */
+ EditText mEditor;
+ /**
+ * @hide
+ */
+ View mProgress;
+ /**
+ * @hide
+ */
+ ProgressBar mProgressBar;
+ /**
+ * @hide
+ */
+ ButtonItem mSave;
+
+ /**
+ * Intent extra parameter for the path of the file to open.
+ */
+ public static final String EXTRA_OPEN_FILE = "extra_open_file"; //$NON-NLS-1$
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void onCreate(Bundle state) {
+ if (DEBUG) {
+ Log.d(TAG, "EditorActivity.onCreate"); //$NON-NLS-1$
+ }
+
+ //Request features
+ setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
+
+ //Set the main layout of the activity
+ setContentView(R.layout.editor);
+
+ // Get the limit vars
+ this.mBufferSize =
+ getApplicationContext().getResources().getInteger(R.integer.buffer_size);
+ this.mMaxFileSize =
+ getApplicationContext().getResources().getInteger(R.integer.editor_max_file_size);
+
+ //Initialize
+ initTitleActionBar();
+ initLayout();
+ initializeConsole();
+ readFile();
+
+ //Save state
+ super.onCreate(state);
+ }
+
+ /**
+ * Method that initializes the titlebar of the activity.
+ */
+ private void initTitleActionBar() {
+ //Configure the action bar options
+ getActionBar().setBackgroundDrawable(
+ getResources().getDrawable(R.drawable.bg_holo_titlebar));
+ getActionBar().setDisplayOptions(
+ ActionBar.DISPLAY_SHOW_CUSTOM | ActionBar.DISPLAY_SHOW_HOME);
+ 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.setContentDescription(getString(R.string.actionbar_button_save_cd));
+ this.mSave.setVisibility(View.INVISIBLE);
+
+ getActionBar().setCustomView(customTitle);
+ }
+
+ /**
+ * Method that initializes the layout and components of the activity.
+ */
+ private void initLayout() {
+ this.mEditor = (EditText)findViewById(R.id.editor);
+ this.mEditor.setText(null);
+ this.mEditor.addTextChangedListener(this);
+
+ this.mScroll = (ScrollView)findViewById(R.id.editor_scroller);
+ this.mScroll.setEnabled(false);
+
+ this.mProgress = findViewById(R.id.editor_progress);
+ this.mProgressBar = (ProgressBar)findViewById(R.id.editor_progress_bar);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_BACK) {
+ checkDirtyState();
+ return false;
+ }
+ return super.onKeyUp(keyCode, event);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ if ((getActionBar().getDisplayOptions() & ActionBar.DISPLAY_HOME_AS_UP)
+ == ActionBar.DISPLAY_HOME_AS_UP) {
+ checkDirtyState();
+ }
+ return true;
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+ }
+
+ /**
+ * Method invoked when an action item is clicked.
+ *
+ * @param view The button pushed
+ */
+ public void onActionBarItemClick(View view) {
+ switch (view.getId()) {
+ case R.id.ab_button1:
+ // Save the file
+ writeFile();
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ /**
+ * Method that initializes a console
+ */
+ private boolean initializeConsole() {
+ try {
+ // Is there a console allocate
+ if (!ConsoleBuilder.isAlloc()) {
+ // Create a console
+ ConsoleBuilder.getConsole(this, true);
+ }
+ // There is a console allocated. Use it.
+ return true;
+ } catch (Throwable _throw) {
+ // Capture the exception
+ ExceptionUtil.translateException(this, _throw, false, true);
+ }
+ return false;
+ }
+
+ /**
+ * Method that reads the requested file
+ */
+ private void readFile() {
+ // For now editor is not dirty and editable.
+ setDirty(false);
+
+ // Check for a valid action
+ String action = getIntent().getAction();
+ if (action == null ||
+ (action.compareTo(Intent.ACTION_VIEW) != 0) &&
+ (action.compareTo(Intent.ACTION_EDIT) != 0)) {
+ DialogHelper.showToast(
+ this, R.string.editor_invalid_file_msg, Toast.LENGTH_SHORT);
+ return;
+ }
+ this.mReadOnly = (action.compareTo(Intent.ACTION_EDIT) == 0);
+
+ // Read the intent and check that is has a valid request
+ String path = getIntent().getData().getPath();
+ 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());
+
+ // Check that we have access to the file (the real file, not the symlink)
+ try {
+ this.mFso = CommandHelper.getFileInfo(this, path, true, null);
+ if (this.mFso == null) {
+ DialogHelper.showToast(
+ this, R.string.editor_file_not_found_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;
+ }
+
+ // Do the load of the
+ AsyncTask<FileSystemObject, Integer, Boolean> mOpenTask =
+ new AsyncTask<FileSystemObject, Integer, Boolean>() {
+
+ private Exception mCause;
+ private AsyncReader mReader;
+
+ @Override
+ protected void onPreExecute() {
+ // Show the progress
+ doProgress(true, 0);
+ }
+
+ @Override
+ protected Boolean doInBackground(FileSystemObject... params) {
+ // Only one argument (the file to open)
+ FileSystemObject fso = params[0];
+ this.mCause = null;
+
+ // Read the file in an async listener
+ try {
+ // Configure the reader
+ this.mReader = new AsyncReader();
+ this.mReader.mFso = fso;
+ this.mReader.mListener = new OnProgressListener() {
+ @Override
+ @SuppressWarnings("synthetic-access")
+ public void onProgress(int progress) {
+ publishProgress(Integer.valueOf(progress));
+ }
+ };
+
+ // Execute the command (read the file)
+ CommandHelper.read(
+ EditorActivity.this, fso.getFullPath(), this.mReader, null);
+
+ // Wait for
+ synchronized (this.mReader.mSync) {
+ this.mReader.mSync.wait();
+ }
+
+ // 100%
+ doProgress(true, 100);
+
+ // Check if the read was successfully
+ if (this.mReader.mCause != null) {
+ this.mCause = this.mReader.mCause;
+ return Boolean.FALSE;
+ }
+
+ } 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) {
+ // Hide the progress
+ doProgress(false, 0);
+
+ // Is error?
+ if (!result.booleanValue()) {
+ if (this.mCause != null) {
+ ExceptionUtil.translateException(EditorActivity.this, this.mCause);
+ }
+ } else {
+ // Now we have the buffer, set the text of the editor
+ EditorActivity.this.mEditor.setText(
+ this.mReader.mBuffer, BufferType.EDITABLE);
+ this.mReader.mBuffer = null; //Cleanup
+ setDirty(false);
+ EditorActivity.this.mScroll.setEnabled(EditorActivity.this.mReadOnly);
+ }
+ }
+
+ @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) {
+ // Show the progress bar
+ EditorActivity.this.mProgressBar.setProgress(progress);
+ EditorActivity.this.mProgress.setVisibility(
+ visible ? View.VISIBLE : View.GONE);
+ }
+ };
+ mOpenTask.execute(this.mFso);
+ }
+
+ /**
+ * Method that reads the requested file.
+ */
+ private void writeFile() {
+ try {
+ // Configure the writer
+ AsyncWriter writer = new AsyncWriter();
+
+ // Create the writable command
+ WriteExecutable cmd =
+ CommandHelper.write(this, this.mFso.getFullPath(), writer, null);
+
+ // Obtain access to the buffer (IMP! don't close the buffer here, it's manage
+ // by the command)
+ OutputStream os = cmd.createOutputStream();
+ try {
+ // Retrieve the text from the editor
+ String text = this.mEditor.getText().toString();
+ ByteArrayInputStream bais = new ByteArrayInputStream(text.getBytes());
+ text = null;
+ try {
+ // Buffered write
+ byte[] data = new byte[this.mBufferSize];
+ int read = 0;
+ while ((read = bais.read(data, 0, this.mBufferSize)) != -1) {
+ os.write(data, 0, read);
+ }
+ } finally {
+ try {
+ bais.close();
+ } catch (Exception e) {/**NON BLOCK**/}
+ }
+
+ } finally {
+ // Ok. Data is written or ensure buffer close
+ cmd.end();
+ }
+
+ // Sleep a bit
+ Thread.sleep(150L);
+
+ // Is error?
+ if (writer.mCause != null) {
+ // Something was wrong. The file probably is corrupted
+ DialogHelper.showToast(
+ this, R.string.msgs_operation_failure, Toast.LENGTH_SHORT);
+ } else {
+ // Success. The file was saved
+ DialogHelper.showToast(
+ this, R.string.editor_successfully_saved, Toast.LENGTH_SHORT);
+ setDirty(false);
+ }
+
+ } catch (Exception e) {
+ // Something was wrong, but the file was NOT written
+ DialogHelper.showToast(
+ this, R.string.msgs_operation_failure, Toast.LENGTH_SHORT);
+ return;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void beforeTextChanged(
+ CharSequence s, int start, int count, int after) {/**NON BLOCK**/}
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {/**NON BLOCK**/}
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void afterTextChanged(Editable s) {
+ setDirty(true);
+ }
+
+ /**
+ * Method that sets if the editor is dirty (has changed)
+ *
+ * @param dirty If the editor is dirty
+ * @hide
+ */
+ void setDirty(boolean dirty) {
+ this.mDirty = dirty;
+ this.mSave.setVisibility(dirty ? View.VISIBLE : View.GONE);
+ }
+
+ /**
+ * Check the dirty state of the editor, and ask the user to save the changes
+ * prior to exit.
+ */
+ public void checkDirtyState() {
+ if (this.mDirty) {
+ AlertDialog dlg = DialogHelper.createYesNoDialog(
+ this, R.string.editor_dirty_ask, new OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ if (which == DialogInterface.BUTTON_POSITIVE) {
+ dialog.dismiss();
+ setResult(Activity.RESULT_OK);
+ finish();
+ }
+ }
+ });
+ dlg.show();
+ return;
+ }
+ setResult(Activity.RESULT_OK);
+ finish();
+ }
+
+}
diff --git a/src/com/cyanogenmod/explorer/console/shell/ShellConsole.java b/src/com/cyanogenmod/explorer/console/shell/ShellConsole.java
index aa68c65d..31515be2 100644
--- a/src/com/cyanogenmod/explorer/console/shell/ShellConsole.java
+++ b/src/com/cyanogenmod/explorer/console/shell/ShellConsole.java
@@ -618,6 +618,7 @@ public abstract class ShellConsole extends Console implements Program.ProgramLis
@Override
public void run() {
int read = 0;
+
try {
while (ShellConsole.this.mActive) {
//Read only one byte with active wait
@@ -625,6 +626,12 @@ public abstract class ShellConsole extends Console implements Program.ProgramLis
if (r == -1) {
break;
}
+
+ // Type of command
+ boolean async =
+ ShellConsole.this.mActiveCommand != null &&
+ ShellConsole.this.mActiveCommand instanceof AsyncResultProgram;
+
StringBuffer sb = new StringBuffer();
if (!ShellConsole.this.mCancelled) {
ShellConsole.this.mSbIn.append((char)r);
@@ -633,8 +640,7 @@ public abstract class ShellConsole extends Console implements Program.ProgramLis
isCommandStarted(ShellConsole.this.mSbIn);
if (ShellConsole.this.mStarted) {
sb = new StringBuffer(ShellConsole.this.mSbIn.toString());
- if (ShellConsole.this.mActiveCommand
- instanceof AsyncResultProgram) {
+ if (async) {
synchronized (ShellConsole.this.mPartialSync) {
((AsyncResultProgram)ShellConsole.
this.mActiveCommand).
@@ -649,18 +655,22 @@ public abstract class ShellConsole extends Console implements Program.ProgramLis
}
//Notify asynchronous partial data
- if (ShellConsole.this.mStarted &&
- ShellConsole.this.mActiveCommand != null &&
- ShellConsole.this.mActiveCommand instanceof AsyncResultProgram) {
+ if (ShellConsole.this.mStarted && async) {
AsyncResultProgram program =
((AsyncResultProgram)ShellConsole.this.mActiveCommand);
- program.onRequestParsePartialResult(sb.toString());
+ String partial = sb.toString();
+ program.onRequestParsePartialResult(partial);
+ ShellConsole.this.toStdIn(partial);
// Reset the temp buffer
sb = new StringBuffer();
}
}
+ if (!async) {
+ ShellConsole.this.toStdIn(sb.toString());
+ }
+
//Has more data? Read with available as more as exists
//or maximum loop count is rebased
int count = 0;
@@ -671,6 +681,11 @@ public abstract class ShellConsole extends Console implements Program.ProgramLis
byte[] data = new byte[available];
read = in.read(data);
+ // Type of command
+ async =
+ ShellConsole.this.mActiveCommand != null &&
+ ShellConsole.this.mActiveCommand instanceof AsyncResultProgram;
+
// Exit if active command is cancelled
if (ShellConsole.this.mCancelled) continue;
@@ -681,8 +696,7 @@ public abstract class ShellConsole extends Console implements Program.ProgramLis
isCommandStarted(ShellConsole.this.mSbIn);
if (ShellConsole.this.mStarted) {
sb = new StringBuffer(ShellConsole.this.mSbIn.toString());
- if (ShellConsole.this.mActiveCommand
- instanceof AsyncResultProgram) {
+ if (async) {
synchronized (ShellConsole.this.mPartialSync) {
((AsyncResultProgram)ShellConsole.
this.mActiveCommand).
@@ -700,23 +714,31 @@ public abstract class ShellConsole extends Console implements Program.ProgramLis
boolean finished = isCommandFinished(ShellConsole.this.mSbIn, sb);
//Notify asynchronous partial data
- if (ShellConsole.this.mActiveCommand != null &&
- ShellConsole.this.mActiveCommand
- instanceof AsyncResultProgram) {
+ if (async) {
AsyncResultProgram program =
((AsyncResultProgram)ShellConsole.this.mActiveCommand);
- program.onRequestParsePartialResult(sb.toString());
+ String partial = sb.toString();
+ program.onRequestParsePartialResult(partial);
+ ShellConsole.this.toStdIn(partial);
// Reset the temp buffer
sb = new StringBuffer();
}
if (finished) {
+ if (!async) {
+ ShellConsole.this.toStdIn(sb.toString());
+ }
+
//Notify the end
notifyProcessFinished();
break;
}
+ if (!async) {
+ ShellConsole.this.toStdIn(sb.toString());
+ }
+
//Wait for buffer to be filled
try {
Thread.sleep(50L);
@@ -725,16 +747,9 @@ public abstract class ShellConsole extends Console implements Program.ProgramLis
}
}
- //Audit (if not cancelled)
- if (!ShellConsole.this.mCancelled && isTrace()) {
- Log.v(TAG,
- String.format("stdin: %s", sb.toString())); //$NON-NLS-1$
- }
-
//Asynchronous programs can cause a lot of output, control buffers
//for a low memory footprint
- if (ShellConsole.this.mActiveCommand != null &&
- ShellConsole.this.mActiveCommand instanceof AsyncResultProgram) {
+ if (async) {
trimBuffer(ShellConsole.this.mSbIn);
trimBuffer(ShellConsole.this.mSbErr);
}
@@ -753,6 +768,21 @@ public abstract class ShellConsole extends Console implements Program.ProgramLis
}
/**
+ * Method that echoes the stdin
+ *
+ * @param stdin The buffer of the stdin
+ * @hide
+ */
+ void toStdIn(String stdin) {
+ //Audit (if not cancelled)
+ if (!this.mCancelled && isTrace() && stdin.length() > 0) {
+ Log.v(TAG,
+ String.format(
+ "stdin: %s", stdin)); //$NON-NLS-1$
+ }
+ }
+
+ /**
* Method that creates the standard error thread for read program response.
*
* @param err The standard error buffer
@@ -763,6 +793,7 @@ public abstract class ShellConsole extends Console implements Program.ProgramLis
@Override
public void run() {
int read = 0;
+
try {
while (ShellConsole.this.mActive) {
//Read only one byte with active wait
@@ -770,19 +801,25 @@ public abstract class ShellConsole extends Console implements Program.ProgramLis
if (r == -1) {
break;
}
+
+ // Type of command
+ boolean async =
+ ShellConsole.this.mActiveCommand != null &&
+ ShellConsole.this.mActiveCommand instanceof AsyncResultProgram;
+
StringBuffer sb = new StringBuffer();
if (!ShellConsole.this.mCancelled) {
ShellConsole.this.mSbErr.append((char)r);
sb.append((char)r);
//Notify asynchronous partial data
- if (ShellConsole.this.mStarted &&
- ShellConsole.this.mActiveCommand != null &&
- ShellConsole.this.mActiveCommand instanceof AsyncResultProgram) {
+ if (ShellConsole.this.mStarted && async) {
AsyncResultProgram program =
((AsyncResultProgram)ShellConsole.this.mActiveCommand);
program.parsePartialErrResult(new String(new char[]{(char)r}));
}
+
+ toStdErr(sb.toString());
}
//Has more data? Read with available as more as exists
@@ -795,6 +832,11 @@ public abstract class ShellConsole extends Console implements Program.ProgramLis
byte[] data = new byte[available];
read = err.read(data);
+ // Type of command
+ async =
+ ShellConsole.this.mActiveCommand != null &&
+ ShellConsole.this.mActiveCommand instanceof AsyncResultProgram;
+
// Exit if active command is cancelled
if (ShellConsole.this.mCancelled) continue;
@@ -803,12 +845,12 @@ public abstract class ShellConsole extends Console implements Program.ProgramLis
sb.append(s);
//Notify asynchronous partial data
- if (ShellConsole.this.mActiveCommand != null &&
- ShellConsole.this.mActiveCommand instanceof AsyncResultProgram) {
+ if (async) {
AsyncResultProgram program =
((AsyncResultProgram)ShellConsole.this.mActiveCommand);
program.parsePartialErrResult(s);
}
+ toStdErr(sb.toString());
//Wait for buffer to be filled
try {
@@ -818,12 +860,6 @@ public abstract class ShellConsole extends Console implements Program.ProgramLis
}
}
- //Audit (if not cancelled)
- if (!ShellConsole.this.mCancelled && isTrace()) {
- Log.v(TAG,
- String.format("stderr: %s", sb.toString())); //$NON-NLS-1$
- }
-
//Asynchronous programs can cause a lot of output, control buffers
//for a low memory footprint
if (ShellConsole.this.mActiveCommand != null &&
@@ -843,6 +879,21 @@ public abstract class ShellConsole extends Console implements Program.ProgramLis
}
/**
+ * Method that echoes the stderr
+ *
+ * @param stdin The buffer of the stderr
+ * @hide
+ */
+ void toStdErr(String stderr) {
+ //Audit (if not cancelled)
+ if (!this.mCancelled && isTrace()) {
+ Log.v(TAG,
+ String.format(
+ "stderr: %s", stderr)); //$NON-NLS-1$
+ }
+ }
+
+ /**
* Method that checks the console status and restart the console
* if this is unusable.
*
diff --git a/src/com/cyanogenmod/explorer/ui/dialogs/AssociationsDialog.java b/src/com/cyanogenmod/explorer/ui/dialogs/AssociationsDialog.java
index 67839df2..965f0c00 100644
--- a/src/com/cyanogenmod/explorer/ui/dialogs/AssociationsDialog.java
+++ b/src/com/cyanogenmod/explorer/ui/dialogs/AssociationsDialog.java
@@ -43,6 +43,7 @@ import android.widget.Toast;
import com.cyanogenmod.explorer.ExplorerApplication;
import com.cyanogenmod.explorer.R;
import com.cyanogenmod.explorer.adapters.AssociationsAdapter;
+import com.cyanogenmod.explorer.ui.policy.IntentsActionPolicy;
import com.cyanogenmod.explorer.util.DialogHelper;
import com.cyanogenmod.explorer.util.ExceptionUtil;
@@ -403,7 +404,7 @@ public class AssociationsDialog implements OnItemClickListener {
boolean isPlatformSigned =
ExplorerApplication.isAppPlatformSignature(this.mContext);
if (isPlatformSigned && this.mAllowPreferred) {
- if (filter != null && !isPreferredSelected()) {
+ if (filter != null && !isPreferredSelected() && !isInternalEditor(ri)) {
try {
AssociationsAdapter adapter = (AssociationsAdapter)this.mGrid.getAdapter();
final int cc = adapter.getCount();
@@ -447,4 +448,17 @@ public class AssociationsDialog implements OnItemClickListener {
this.mContext.startActivity(intent);
}
}
+
+ /**
+ * Method that returns if the selected resolve info is about an internal viewer
+ *
+ * @param ri The resolve info
+ * @return boolean If the selected resolve info is about an internal viewer
+ */
+ @SuppressWarnings("static-method")
+ private boolean isInternalEditor(ResolveInfo ri) {
+ return ri.activityInfo.metaData != null &&
+ ri.activityInfo.metaData.getBoolean(
+ IntentsActionPolicy.CATEGORY_INTERNAL_VIEWER, false);
+ }
}
diff --git a/src/com/cyanogenmod/explorer/ui/policy/IntentsActionPolicy.java b/src/com/cyanogenmod/explorer/ui/policy/IntentsActionPolicy.java
index 607f0301..fb32eff9 100644
--- a/src/com/cyanogenmod/explorer/ui/policy/IntentsActionPolicy.java
+++ b/src/com/cyanogenmod/explorer/ui/policy/IntentsActionPolicy.java
@@ -23,19 +23,23 @@ import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
+import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;
import com.cyanogenmod.explorer.R;
import com.cyanogenmod.explorer.activities.ShortcutActivity;
import com.cyanogenmod.explorer.model.FileSystemObject;
+import com.cyanogenmod.explorer.model.RegularFile;
import com.cyanogenmod.explorer.ui.dialogs.AssociationsDialog;
import com.cyanogenmod.explorer.util.DialogHelper;
import com.cyanogenmod.explorer.util.ExceptionUtil;
import com.cyanogenmod.explorer.util.FileHelper;
import com.cyanogenmod.explorer.util.MimeTypeHelper;
+import com.cyanogenmod.explorer.util.MimeTypeHelper.MimeTypeCategory;
import java.io.File;
+import java.util.ArrayList;
import java.util.List;
/**
@@ -48,6 +52,18 @@ public final class IntentsActionPolicy extends ActionsPolicy {
private static boolean DEBUG = false;
/**
+ * Category for all the internal CMExplorer viewers
+ */
+ public static final String CATEGORY_INTERNAL_VIEWER =
+ "com.cyanogenmod.explorer.category.INTERNAL_VIEWER"; //$NON-NLS-1$
+
+ /**
+ * Category for all the CMExplorer editor
+ */
+ public static final String CATEGORY_EDITOR =
+ "com.cyanogenmod.explorer.category.EDITOR"; //$NON-NLS-1$
+
+ /**
* Method that opens a {@link FileSystemObject} with the default registered application
* by the system, or ask the user for select a registered application.
*
@@ -79,6 +95,7 @@ public final class IntentsActionPolicy extends ActionsPolicy {
ctx,
intent,
choose,
+ createInternalIntents(ctx, fso),
R.drawable.ic_holo_light_open,
R.string.associations_dialog_openwith_title,
R.string.associations_dialog_openwith_action,
@@ -115,6 +132,7 @@ public final class IntentsActionPolicy extends ActionsPolicy {
ctx,
intent,
false,
+ null,
R.drawable.ic_holo_light_send,
R.string.associations_dialog_sendwith_title,
R.string.associations_dialog_sendwith_action,
@@ -132,6 +150,7 @@ public final class IntentsActionPolicy extends ActionsPolicy {
* @param intent The intent to resolve
* @param choose If allow the user to select the application to select the registered
* application. If no preferred app or more than one exists the dialog is shown.
+ * @param internals The list of internals intents that can handle the action
* @param icon The icon of the dialog
* @param title The title of the dialog
* @param action The button title of the dialog
@@ -140,7 +159,7 @@ public final class IntentsActionPolicy extends ActionsPolicy {
* @param onDismissListener The dismiss listener
*/
private static void resolveIntent(
- Context ctx, Intent intent, boolean choose,
+ Context ctx, Intent intent, boolean choose, List<Intent> internals,
int icon, int title, int action, boolean allowPreferred,
OnCancelListener onCancelListener, OnDismissListener onDismissListener) {
//Retrieve the activities that can handle the file
@@ -152,6 +171,29 @@ public final class IntentsActionPolicy extends ActionsPolicy {
packageManager.
queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
+ // Add the internal editors
+ int count = 0;
+ if (internals != null) {
+ int cc = internals.size();
+ for (int i = 0; i < cc; i++) {
+ List<ResolveInfo> ris =
+ packageManager.
+ queryIntentActivities(internals.get(i), 0);
+ if (ris.size() > 0) {
+ ResolveInfo ri = ris.get(0);
+ // Mark as internal
+ if (ri.activityInfo.metaData == null) {
+ ri.activityInfo.metaData = new Bundle();
+ ri.activityInfo.metaData.putBoolean(CATEGORY_INTERNAL_VIEWER, true);
+ }
+
+ // Only one result must be matched
+ info.add(count, ri);
+ count++;
+ }
+ }
+ }
+
// Retrieve the preferred activity that can handle the file
final ResolveInfo mPreferredInfo = packageManager.resolveActivity(intent, 0);
@@ -164,8 +206,13 @@ public final class IntentsActionPolicy extends ActionsPolicy {
// Is a simple open and we have an application that can handle the file?
if (!choose &&
((mPreferredInfo != null && mPreferredInfo.match != 0) || info.size() == 1)) {
- ctx.startActivity(intent);
- return;
+ // But not if the only match is the an internal editor
+ ResolveInfo ri = info.get(0);
+ if (ri.activityInfo.metaData == null ||
+ !ri.activityInfo.metaData.getBoolean(CATEGORY_INTERNAL_VIEWER, false)) {
+ ctx.startActivity(intent);
+ return;
+ }
}
// Otherwise, we have to show the open with dialog
@@ -225,4 +272,41 @@ public final class IntentsActionPolicy extends ActionsPolicy {
ctx, R.string.shortcut_creation_failed_msg, Toast.LENGTH_SHORT);
}
}
+
+ /**
+ * This method creates a list of internal activities that could handle the fso.
+ *
+ * @param ctx The current context
+ * @param fso The file system object to open
+ */
+ private static List<Intent> createInternalIntents(Context ctx, FileSystemObject fso) {
+ List<Intent> intents = new ArrayList<Intent>();
+ intents.addAll(createEditorIntent(ctx, fso));
+ return intents;
+ }
+
+ /**
+ * This method creates a list of internal activities for editing files
+ *
+ * @param ctx The current context
+ * @param fso FileSystemObject
+ */
+ private static List<Intent> createEditorIntent(Context ctx, FileSystemObject fso) {
+ List<Intent> intents = new ArrayList<Intent>();
+ MimeTypeCategory category = MimeTypeHelper.getCategory(ctx, fso);
+
+ //- Internal Editor. This editor can handle TEXT and NONE mime categories but
+ // not system files, directories, ..., only regular files (no symlinks)
+ if (fso instanceof RegularFile &&
+ (category.compareTo(MimeTypeCategory.NONE) == 0 ||
+ category.compareTo(MimeTypeCategory.TEXT) == 0)) {
+ Intent editorIntent = new Intent();
+ editorIntent.setAction(Intent.ACTION_EDIT);
+ editorIntent.addCategory(CATEGORY_INTERNAL_VIEWER);
+ editorIntent.addCategory(CATEGORY_EDITOR);
+ intents.add(editorIntent);
+ }
+
+ return intents;
+ }
} \ No newline at end of file
diff --git a/src/com/cyanogenmod/explorer/util/CommandHelper.java b/src/com/cyanogenmod/explorer/util/CommandHelper.java
index e4202f6b..6a704d02 100644
--- a/src/com/cyanogenmod/explorer/util/CommandHelper.java
+++ b/src/com/cyanogenmod/explorer/util/CommandHelper.java
@@ -1669,7 +1669,7 @@ public final class CommandHelper {
}
}
- // If the needed unmount was executed
+ // If the needed unmount was executed
return mountExecuted && leaveDeviceMounted;
}