aboutsummaryrefslogtreecommitdiffstats
path: root/src/com/cyanogenmod/filemanager/console
diff options
context:
space:
mode:
authorjruesga <jorge@ruesga.com>2012-10-26 03:08:49 +0200
committerjruesga <jorge@ruesga.com>2012-10-26 03:08:49 +0200
commit6be595d8eeba247a9fb9614a2c314d1e1a184f3d (patch)
treea013a200bca62336f9c3553005c14f76163f66f5 /src/com/cyanogenmod/filemanager/console
parent7370b7164125a63dfa8d40aa0de04c24754c6a64 (diff)
downloadandroid_packages_apps_CMFileManager-6be595d8eeba247a9fb9614a2c314d1e1a184f3d.tar.gz
android_packages_apps_CMFileManager-6be595d8eeba247a9fb9614a2c314d1e1a184f3d.tar.bz2
android_packages_apps_CMFileManager-6be595d8eeba247a9fb9614a2c314d1e1a184f3d.zip
Change application name to 'File Manager' (issue #20)
Full refactoring of package from explorer to filemanager
Diffstat (limited to 'src/com/cyanogenmod/filemanager/console')
-rw-r--r--src/com/cyanogenmod/filemanager/console/CommandNotFoundException.java46
-rw-r--r--src/com/cyanogenmod/filemanager/console/Console.java127
-rw-r--r--src/com/cyanogenmod/filemanager/console/ConsoleAllocException.java45
-rw-r--r--src/com/cyanogenmod/filemanager/console/ConsoleBuilder.java438
-rw-r--r--src/com/cyanogenmod/filemanager/console/ConsoleHolder.java77
-rw-r--r--src/com/cyanogenmod/filemanager/console/ExecutionException.java45
-rw-r--r--src/com/cyanogenmod/filemanager/console/InsufficientPermissionsException.java53
-rw-r--r--src/com/cyanogenmod/filemanager/console/NoSuchFileOrDirectory.java42
-rw-r--r--src/com/cyanogenmod/filemanager/console/OperationTimeoutException.java36
-rw-r--r--src/com/cyanogenmod/filemanager/console/ReadOnlyFilesystemException.java53
-rw-r--r--src/com/cyanogenmod/filemanager/console/RelaunchableException.java116
-rw-r--r--src/com/cyanogenmod/filemanager/console/java/JavaConsole.java255
-rw-r--r--src/com/cyanogenmod/filemanager/console/shell/NonPriviledgeConsole.java66
-rw-r--r--src/com/cyanogenmod/filemanager/console/shell/PrivilegedConsole.java67
-rw-r--r--src/com/cyanogenmod/filemanager/console/shell/ShellConsole.java1233
15 files changed, 2699 insertions, 0 deletions
diff --git a/src/com/cyanogenmod/filemanager/console/CommandNotFoundException.java b/src/com/cyanogenmod/filemanager/console/CommandNotFoundException.java
new file mode 100644
index 00000000..cca7db2a
--- /dev/null
+++ b/src/com/cyanogenmod/filemanager/console/CommandNotFoundException.java
@@ -0,0 +1,46 @@
+/*
+ * 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.filemanager.console;
+
+/**
+ * An exception thrown when the command was not found
+ * in the system.
+ */
+public class CommandNotFoundException extends Exception {
+
+ private static final long serialVersionUID = 4390932226847273273L;
+
+ /**
+ * Constructor of <code>CommandNotFoundException</code>.
+ *
+ * @param detailMessage Message associated to the exception
+ */
+ public CommandNotFoundException(String detailMessage) {
+ super(detailMessage);
+ }
+
+ /**
+ * Constructor of <code>CommandNotFoundException</code>.
+ *
+ * @param detailMessage Message associated to the exception
+ * @param throwable The cause of the exception
+ */
+ public CommandNotFoundException(String detailMessage, Throwable throwable) {
+ super(detailMessage, throwable);
+ }
+
+}
diff --git a/src/com/cyanogenmod/filemanager/console/Console.java b/src/com/cyanogenmod/filemanager/console/Console.java
new file mode 100644
index 00000000..ba28db55
--- /dev/null
+++ b/src/com/cyanogenmod/filemanager/console/Console.java
@@ -0,0 +1,127 @@
+/*
+ * 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.filemanager.console;
+
+import com.cyanogenmod.filemanager.commands.AsyncResultExecutable;
+import com.cyanogenmod.filemanager.commands.Executable;
+import com.cyanogenmod.filemanager.commands.ExecutableFactory;
+import com.cyanogenmod.filemanager.model.Identity;
+import com.cyanogenmod.filemanager.preferences.FileManagerSettings;
+import com.cyanogenmod.filemanager.preferences.Preferences;
+
+/**
+ * This class represents a class for executing commands in the operating system layer,
+ * being the base for all type of consoles (shell, java, ...).
+ */
+public abstract class Console
+ implements AsyncResultExecutable.OnEndListener, AsyncResultExecutable.OnCancelListener {
+
+ private boolean mTrace;
+
+ /**
+ * Constructor of <code>Console</code>
+ */
+ public Console() {
+ super();
+
+ // Get the current trace value
+ reloadTrace();
+ }
+
+ /**
+ * Method that return if the console has to trace his operations
+ *
+ * @return boolean If the console has to trace
+ */
+ public boolean isTrace() {
+ return this.mTrace;
+ }
+
+ /**
+ * Method that reload the status of trace setting
+ */
+ public final void reloadTrace() {
+ this.mTrace = Preferences.getSharedPreferences().getBoolean(
+ FileManagerSettings.SETTINGS_SHOW_TRACES.getId(),
+ ((Boolean)FileManagerSettings.SETTINGS_SHOW_TRACES.getDefaultValue()).booleanValue());
+ }
+
+ /**
+ * Method that returns the identity of the console (the current user).
+ *
+ * @return Identity The current identity of the console
+ */
+ public abstract Identity getIdentity();
+
+ /**
+ * Method that allocates the console.
+ *
+ * @throws ConsoleAllocException If the console can't be allocated
+ */
+ public abstract void alloc() throws ConsoleAllocException;
+
+ /**
+ * Method that deallocates the actual console.
+ */
+ public abstract void dealloc();
+
+ /**
+ * Method that reallocates the console. This method drops the actual console
+ * and create a new one exactly as the current.
+ *
+ * @throws ConsoleAllocException If the console can't be reallocated
+ */
+ public abstract void realloc() throws ConsoleAllocException;
+
+ /**
+ * Method that returns if the console has root privileged.
+ *
+ * @return boolean Indicates if the console has root privileged
+ */
+ public abstract boolean isPrivileged();
+
+ /**
+ * Method that returns if the console is active and allocated.
+ *
+ * @return boolean Indicates if the console is active and allocated
+ */
+ public abstract boolean isActive();
+
+ /**
+ * Method that retrieves the {@link ExecutableFactory} associated with the {@link Console}.
+ *
+ * @return ExecutableFactory The execution program factory
+ */
+ public abstract ExecutableFactory getExecutableFactory();
+
+ /**
+ * Method for execute a command in the operating system layer.
+ *
+ * @param executable The executable command to be executed
+ * @throws ConsoleAllocException If the console is not allocated
+ * @throws InsufficientPermissionsException If an operation requires elevated permissions
+ * @throws NoSuchFileOrDirectory If the file or directory was not found
+ * @throws OperationTimeoutException If the operation exceeded the maximum time of wait
+ * @throws CommandNotFoundException If the executable program was not found
+ * @throws ExecutionException If the operation returns a invalid exit code
+ * @throws ReadOnlyFilesystemException If the operation writes in a read-only filesystem
+ */
+ public abstract void execute(final Executable executable)
+ throws ConsoleAllocException, InsufficientPermissionsException, NoSuchFileOrDirectory,
+ OperationTimeoutException, ExecutionException, CommandNotFoundException,
+ ReadOnlyFilesystemException;
+
+}
diff --git a/src/com/cyanogenmod/filemanager/console/ConsoleAllocException.java b/src/com/cyanogenmod/filemanager/console/ConsoleAllocException.java
new file mode 100644
index 00000000..ade454a6
--- /dev/null
+++ b/src/com/cyanogenmod/filemanager/console/ConsoleAllocException.java
@@ -0,0 +1,45 @@
+/*
+ * 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.filemanager.console;
+
+/**
+ * An exception thrown when an exception occurs allocating a console.
+ */
+public class ConsoleAllocException extends Exception {
+
+ private static final long serialVersionUID = -4907729346065481798L;
+
+ /**
+ * Constructor of <code>ConsoleAllocException</code>.
+ *
+ * @param detailMessage Message associated to the exception
+ */
+ public ConsoleAllocException(String detailMessage) {
+ super(detailMessage);
+ }
+
+ /**
+ * Constructor of <code>ConsoleAllocException</code>.
+ *
+ * @param detailMessage Message associated to the exception
+ * @param throwable The cause of the exception
+ */
+ public ConsoleAllocException(String detailMessage, Throwable throwable) {
+ super(detailMessage, throwable);
+ }
+
+}
diff --git a/src/com/cyanogenmod/filemanager/console/ConsoleBuilder.java b/src/com/cyanogenmod/filemanager/console/ConsoleBuilder.java
new file mode 100644
index 00000000..cd5109f1
--- /dev/null
+++ b/src/com/cyanogenmod/filemanager/console/ConsoleBuilder.java
@@ -0,0 +1,438 @@
+/*
+ * 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.filemanager.console;
+
+import android.content.Context;
+import android.util.Log;
+import android.widget.Toast;
+
+import com.cyanogenmod.filemanager.FileManagerApplication;
+import com.cyanogenmod.filemanager.R;
+import com.cyanogenmod.filemanager.commands.shell.InvalidCommandDefinitionException;
+import com.cyanogenmod.filemanager.console.java.JavaConsole;
+import com.cyanogenmod.filemanager.console.shell.NonPriviledgeConsole;
+import com.cyanogenmod.filemanager.console.shell.PrivilegedConsole;
+import com.cyanogenmod.filemanager.preferences.FileManagerSettings;
+import com.cyanogenmod.filemanager.preferences.Preferences;
+import com.cyanogenmod.filemanager.util.DialogHelper;
+import com.cyanogenmod.filemanager.util.FileHelper;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+/**
+ * Class responsible for creating consoles.
+ */
+public final class ConsoleBuilder {
+
+ private static final String TAG = "ConsoleBuilder"; //$NON-NLS-1$
+
+ private static final Object SYNC = new Object();
+ private static ConsoleHolder sHolder;
+
+ private static final int ROOT_UID = 0;
+
+ /**
+ * Constructor of <code>ConsoleBuilder</code>.
+ */
+ private ConsoleBuilder() {
+ super();
+ }
+
+ /**
+ * Method that returns a console, and creates a new console
+ * if no console is allocated. The console is create if not exists.
+ *
+ * @param context The current context
+ * @return Console An allocated console
+ * @throws FileNotFoundException If the initial directory not exists
+ * @throws IOException If initial directory couldn't be checked
+ * @throws InvalidCommandDefinitionException If the command has an invalid definition
+ * @throws ConsoleAllocException If the console can't be allocated
+ * @throws InsufficientPermissionsException If the console created is not a privileged console
+ */
+ public static Console getConsole(Context context)
+ throws FileNotFoundException, IOException, InvalidCommandDefinitionException,
+ ConsoleAllocException, InsufficientPermissionsException {
+ return getConsole(context, true);
+ }
+
+ /**
+ * Method that returns a console. If {@linkplain "createIfNotExists"} is specified
+ * a new console will be created
+ *
+ * @param context The current context
+ * @param createIfNotExists Indicates that the console should be create if not exists
+ * @return Console An allocated console
+ * @throws FileNotFoundException If the initial directory not exists
+ * @throws IOException If initial directory couldn't be checked
+ * @throws InvalidCommandDefinitionException If the command has an invalid definition
+ * @throws ConsoleAllocException If the console can't be allocated
+ * @throws InsufficientPermissionsException If the console created is not a privileged console
+ */
+ public static Console getConsole(Context context, boolean createIfNotExists)
+ throws FileNotFoundException, IOException, InvalidCommandDefinitionException,
+ ConsoleAllocException, InsufficientPermissionsException {
+
+ //Check if has a console. Otherwise create a new console
+ if (sHolder == null || sHolder.getConsole() == null) {
+ if (!createIfNotExists) {
+ return null;
+ }
+ createDefaultConsole(context);
+ }
+ return sHolder.getConsole();
+ }
+
+ /**
+ * Method that changes the current console to a non-privileged console.
+ *
+ * @param context The current context
+ * @return boolean If the operation was successfully
+ */
+ public static boolean changeToNonPrivilegedConsole(Context context) {
+
+ //Check the current console
+ if (sHolder.getConsole() instanceof NonPriviledgeConsole) {
+ //The current console is non-privileged. Not needed
+ return true;
+ }
+
+ //Create the console
+ ConsoleHolder holder = null;
+ try {
+ //Create the console, destroy the current console, and marks as current
+ holder = new ConsoleHolder(
+ createNonPrivilegedConsole(context, FileHelper.ROOT_DIRECTORY));
+ destroyConsole();
+ sHolder = holder;
+ return true;
+
+ } catch (Throwable e) {
+ if (holder != null) {
+ holder.dispose();
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Method that changes the current console to a privileged console.
+ *
+ * @param context The current context
+ * @return boolean If the operation was successfully
+ */
+ public static boolean changeToPrivilegedConsole(Context context) {
+
+ //Destroy and create the new console
+ if (sHolder.getConsole() instanceof PrivilegedConsole) {
+ //The current console is privileged. Not needed
+ return true;
+ }
+
+ //Create the console
+ ConsoleHolder holder = null;
+ try {
+ //Create the console, destroy the current console, and marks as current
+ holder = new ConsoleHolder(
+ createAndCheckPrivilegedConsole(context, FileHelper.ROOT_DIRECTORY));
+ destroyConsole();
+ sHolder = holder;
+
+ // Change also the background console to privileged
+ FileManagerApplication.changeBackgroundConsoleToPriviligedConsole();
+
+ return sHolder.getConsole() instanceof PrivilegedConsole;
+
+ } catch (Throwable e) {
+ destroyConsole();
+ if (holder != null) {
+ holder.dispose();
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Method that returns a console, and creates a new console if no
+ * console is allocated or if the settings preferences has changed.
+ *
+ * @param context The current context
+ * @return Console An allocated console
+ * @throws FileNotFoundException If the initial directory not exists
+ * @throws IOException If initial directory couldn't be checked
+ * @throws InvalidCommandDefinitionException If the command has an invalid definition
+ * @throws ConsoleAllocException If the console can't be allocated
+ * @throws InsufficientPermissionsException If the console created is not a privileged console
+ */
+ //IMP! This must be invoked from the main activity creation
+ public static Console createDefaultConsole(Context context)
+ throws FileNotFoundException, IOException, InvalidCommandDefinitionException,
+ ConsoleAllocException, InsufficientPermissionsException {
+ //Gets superuser mode settings
+ boolean superuserMode = FileManagerApplication.isSuperuserMode();
+ boolean advancedMode = FileManagerApplication.isAdvancedMode();
+ if (superuserMode && !advancedMode) {
+ try {
+ Preferences.savePreference(
+ FileManagerSettings.SETTINGS_SUPERUSER_MODE, Boolean.FALSE, true);
+ } catch (Throwable ex) {
+ Log.w(TAG, "can't save console preference", ex); //$NON-NLS-1$
+ }
+ superuserMode = false;
+ }
+ return createDefaultConsole(context, superuserMode, advancedMode);
+ }
+
+ /**
+ * Method that returns a console, and creates a new console if no
+ * console is allocated or if the settings preferences has changed.
+ *
+ * @param context The current context
+ * @param superuserMode If create with a superuser mode console
+ * @param advancedMode If create with a advanced mode console
+ * @return Console An allocated console
+ * @throws FileNotFoundException If the initial directory not exists
+ * @throws IOException If initial directory couldn't be checked
+ * @throws InvalidCommandDefinitionException If the command has an invalid definition
+ * @throws ConsoleAllocException If the console can't be allocated
+ * @throws InsufficientPermissionsException If the console created is not a privileged console
+ */
+ //IMP! This must be invoked from the main activity creation
+ public static Console createDefaultConsole(Context context,
+ boolean superuserMode, boolean advancedMode)
+ throws FileNotFoundException, IOException, InvalidCommandDefinitionException,
+ ConsoleAllocException, InsufficientPermissionsException {
+
+ synchronized (ConsoleBuilder.SYNC) {
+ //Check if console settings has changed
+ if (sHolder != null) {
+ if (
+ (sHolder.getConsole() instanceof NonPriviledgeConsole && superuserMode)
+ || (sHolder.getConsole() instanceof PrivilegedConsole && !superuserMode)) {
+ //Deallocate actual console
+ sHolder.dispose();
+ sHolder = null;
+ }
+ }
+
+ //Is there a console allocated
+ if (sHolder == null) {
+ sHolder = (superuserMode)
+ ? new ConsoleHolder(
+ createAndCheckPrivilegedConsole(
+ context, FileHelper.ROOT_DIRECTORY))
+ : new ConsoleHolder(
+ createNonPrivilegedConsole(context, FileHelper.ROOT_DIRECTORY));
+ if (superuserMode) {
+ // Change also the background console to privileged
+ FileManagerApplication.changeBackgroundConsoleToPriviligedConsole();
+ }
+ }
+ return sHolder.getConsole();
+ }
+ }
+
+ /**
+ * Method that destroy the current console.
+ */
+ public static void destroyConsole() {
+ try {
+ if (sHolder != null) {
+ sHolder.dispose();
+ }
+ } catch (Exception e) {
+ /**NON BLOCK**/
+ }
+ sHolder = null;
+ }
+
+ /**
+ * Method that creates a new non privileged console.
+ *
+ * @param context The current context
+ * @param initialDirectory The initial directory of the console
+ * @return Console The non privileged console
+ * @throws FileNotFoundException If the initial directory not exists
+ * @throws IOException If initial directory couldn't be checked
+ * @throws InvalidCommandDefinitionException If the command has an invalid definition
+ * @throws ConsoleAllocException If the console can't be allocated
+ * @see NonPriviledgeConsole
+ */
+ public static Console createNonPrivilegedConsole(Context context, String initialDirectory)
+ throws FileNotFoundException, IOException,
+ InvalidCommandDefinitionException, ConsoleAllocException {
+
+ int bufferSize = context.getResources().getInteger(R.integer.buffer_size);
+
+ // Is rooted? Then create a shell console
+ if (FileManagerApplication.isDeviceRooted()) {
+ NonPriviledgeConsole console = new NonPriviledgeConsole(initialDirectory);
+ console.setBufferSize(bufferSize);
+ console.alloc();
+ return console;
+ }
+
+ // No rooted. Then create a java console
+ JavaConsole console = new JavaConsole(context, initialDirectory, bufferSize);
+ console.alloc();
+ return console;
+ }
+
+ /**
+ * Method that creates a new privileged console. If the allocation of the
+ * privileged console fails, the a non privileged console
+ *
+ * @param context The current context
+ * @param initialDirectory The initial directory of the console
+ * @return Console The privileged console
+ * @throws FileNotFoundException If the initial directory not exists
+ * @throws IOException If initial directory couldn't be checked
+ * @throws InvalidCommandDefinitionException If the command has an invalid definition
+ * @throws ConsoleAllocException If the console can't be allocated
+ * @throws InsufficientPermissionsException If the console created is not a privileged console
+ * @see PrivilegedConsole
+ */
+ public static Console createPrivilegedConsole(Context context, String initialDirectory)
+ throws FileNotFoundException, IOException, InvalidCommandDefinitionException,
+ ConsoleAllocException, InsufficientPermissionsException {
+ PrivilegedConsole console = new PrivilegedConsole(initialDirectory);
+ console.setBufferSize(context.getResources().getInteger(R.integer.buffer_size));
+ console.alloc();
+ if (console.getIdentity().getUser().getId() != ROOT_UID) {
+ //The console is not a privileged console
+ try {
+ console.dealloc();
+ } catch (Throwable ex) {
+ /**NON BLOCK**/
+ }
+ throw new InsufficientPermissionsException(null);
+ }
+ return console;
+ }
+
+ /**
+ * Method that creates a new privileged console. If the allocation of the
+ * privileged console fails, the a non privileged console
+ *
+ * @param context The current context
+ * @param initialDirectory The initial directory of the console
+ * @return Console The privileged console
+ * @throws FileNotFoundException If the initial directory not exists
+ * @throws IOException If initial directory couldn't be checked
+ * @throws InvalidCommandDefinitionException If the command has an invalid definition
+ * @throws ConsoleAllocException If the console can't be allocated
+ * @throws InsufficientPermissionsException If the console created is not a privileged console
+ * @see PrivilegedConsole
+ */
+ public static Console createAndCheckPrivilegedConsole(Context context, String initialDirectory)
+ throws FileNotFoundException, IOException, InvalidCommandDefinitionException,
+ ConsoleAllocException, InsufficientPermissionsException {
+ return createAndCheckPrivilegedConsole(context, initialDirectory, true);
+ }
+
+ /**
+ * Method that creates a new privileged console. If the allocation of the
+ * privileged console fails, the a non privileged console
+ *
+ * @param context The current context
+ * @param initialDirectory The initial directory of the console
+ * @param silent Indicates that no message have to be displayed
+ * @return Console The privileged console
+ * @throws FileNotFoundException If the initial directory not exists
+ * @throws IOException If initial directory couldn't be checked
+ * @throws InvalidCommandDefinitionException If the command has an invalid definition
+ * @throws ConsoleAllocException If the console can't be allocated
+ * @throws InsufficientPermissionsException If the console created is not a privileged console
+ * @see PrivilegedConsole
+ */
+ public static Console createAndCheckPrivilegedConsole(
+ Context context, String initialDirectory, boolean silent)
+ throws FileNotFoundException, IOException, InvalidCommandDefinitionException,
+ ConsoleAllocException, InsufficientPermissionsException {
+ try {
+ PrivilegedConsole console = new PrivilegedConsole(initialDirectory);
+ console.setBufferSize(context.getResources().getInteger(R.integer.buffer_size));
+ console.alloc();
+ if (console.getIdentity().getUser().getId() != ROOT_UID) {
+ //The console is not a privileged console
+ try {
+ console.dealloc();
+ } catch (Throwable ex) {
+ /**NON BLOCK**/
+ }
+ throw new InsufficientPermissionsException(null);
+ }
+ return console;
+ } catch (ConsoleAllocException caEx) {
+ //Show a message with the problem?
+ Log.w(TAG, context.getString(R.string.msgs_privileged_console_alloc_failed), caEx);
+ if (!silent) {
+ try {
+ DialogHelper.showToast(context,
+ R.string.msgs_privileged_console_alloc_failed, Toast.LENGTH_LONG);
+ } catch (Exception ex) {
+ Log.e(TAG, "can't show toast", ex); //$NON-NLS-1$
+ }
+ }
+
+ boolean advancedMode = FileManagerApplication.isAdvancedMode();
+ if (advancedMode) {
+ //Save settings
+ try {
+ Preferences.savePreference(
+ FileManagerSettings.SETTINGS_SUPERUSER_MODE, Boolean.FALSE, true);
+ } catch (Exception ex) {
+ Log.e(TAG,
+ String.format("Failed to save %s property", //$NON-NLS-1$
+ FileManagerSettings.SETTINGS_SUPERUSER_MODE.getId()), ex);
+ }
+
+ //Create the non-privileged console
+ return createNonPrivilegedConsole(context, initialDirectory);
+ }
+
+ // Rethrow the exception
+ throw caEx;
+ }
+ }
+
+ /**
+ * Method that returns if the current console is a privileged console
+ *
+ * @return boolean If the current console is a privileged console
+ */
+ public static boolean isAlloc() {
+ if (sHolder != null && sHolder.getConsole() != null) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Method that returns if the current console is a privileged console
+ *
+ * @return boolean If the current console is a privileged console
+ */
+ public static boolean isPrivileged() {
+ if (sHolder != null && sHolder.getConsole() instanceof PrivilegedConsole) {
+ return true;
+ }
+ return false;
+ }
+
+}
diff --git a/src/com/cyanogenmod/filemanager/console/ConsoleHolder.java b/src/com/cyanogenmod/filemanager/console/ConsoleHolder.java
new file mode 100644
index 00000000..cb3e8686
--- /dev/null
+++ b/src/com/cyanogenmod/filemanager/console/ConsoleHolder.java
@@ -0,0 +1,77 @@
+/*
+ * 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.filemanager.console;
+
+/**
+ * A class that holds a console.
+ */
+public class ConsoleHolder {
+
+ private final Console mConsole;
+ private boolean mDispose;
+
+ /**
+ * Constructor of <code>ConsoleHolder</code>.
+ *
+ * @param console An allocated console to be holden
+ */
+ public ConsoleHolder(Console console) {
+ super();
+ this.mConsole = console;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void finalize() throws Throwable {
+ dispose();
+ super.finalize();
+ }
+
+ /**
+ * Method that returns the console.
+ *
+ * @return The console
+ */
+ public Console getConsole() {
+ return this.mConsole;
+ }
+
+ /**
+ * Method that returns if the console is disposed.
+ *
+ * @return boolean If the console is disposed
+ */
+ public boolean isDispose() {
+ return this.mDispose;
+ }
+
+ /**
+ * Method that dispose the console.
+ */
+ public void dispose() {
+ try {
+ if (this.mConsole != null) {
+ this.mConsole.dealloc();
+ }
+ } catch (Exception e) {
+ /**NON BLOCK**/
+ }
+ }
+
+}
diff --git a/src/com/cyanogenmod/filemanager/console/ExecutionException.java b/src/com/cyanogenmod/filemanager/console/ExecutionException.java
new file mode 100644
index 00000000..e05d35e2
--- /dev/null
+++ b/src/com/cyanogenmod/filemanager/console/ExecutionException.java
@@ -0,0 +1,45 @@
+/*
+ * 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.filemanager.console;
+
+/**
+ * An exception thrown when an operation invocation fails.
+ */
+public class ExecutionException extends Exception {
+
+ private static final long serialVersionUID = 5900809383615958749L;
+
+ /**
+ * Constructor of <code>ExecutionException</code>.
+ *
+ * @param detailMessage Message associated to the exception
+ */
+ public ExecutionException(String detailMessage) {
+ super(detailMessage);
+ }
+
+ /**
+ * Constructor of <code>ExecutionException</code>.
+ *
+ * @param detailMessage Message associated to the exception
+ * @param throwable The cause of the exception
+ */
+ public ExecutionException(String detailMessage, Throwable throwable) {
+ super(detailMessage, throwable);
+ }
+
+}
diff --git a/src/com/cyanogenmod/filemanager/console/InsufficientPermissionsException.java b/src/com/cyanogenmod/filemanager/console/InsufficientPermissionsException.java
new file mode 100644
index 00000000..614e5609
--- /dev/null
+++ b/src/com/cyanogenmod/filemanager/console/InsufficientPermissionsException.java
@@ -0,0 +1,53 @@
+/*
+ * 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.filemanager.console;
+
+import com.cyanogenmod.filemanager.R;
+import com.cyanogenmod.filemanager.commands.SyncResultExecutable;
+
+/**
+ * An exception thrown when an operation required elevated permissions.
+ */
+public class InsufficientPermissionsException extends RelaunchableException {
+
+ private static final long serialVersionUID = -5350536343872073589L;
+
+ /**
+ * Constructor of <code>InsufficientPermissionsException</code>.
+ */
+ public InsufficientPermissionsException() {
+ super(null);
+ }
+
+ /**
+ * Constructor of <code>InsufficientPermissionsException</code>.
+ *
+ * @param executable The executable that should be re-executed
+ */
+ public InsufficientPermissionsException(SyncResultExecutable executable) {
+ super(executable);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int getQuestionResourceId() {
+ return R.string.advise_insufficient_permissions;
+ }
+
+}
diff --git a/src/com/cyanogenmod/filemanager/console/NoSuchFileOrDirectory.java b/src/com/cyanogenmod/filemanager/console/NoSuchFileOrDirectory.java
new file mode 100644
index 00000000..a2e87c7b
--- /dev/null
+++ b/src/com/cyanogenmod/filemanager/console/NoSuchFileOrDirectory.java
@@ -0,0 +1,42 @@
+/*
+ * 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.filemanager.console;
+
+/**
+ * An exception thrown when the file or directory is not found.
+ */
+public class NoSuchFileOrDirectory extends Exception {
+
+ private static final long serialVersionUID = 8601894104043734066L;
+
+ /**
+ * Constructor of <code>NoSuchFileOrDirectory</code>.
+ */
+ public NoSuchFileOrDirectory() {
+ super();
+ }
+
+ /**
+ * Constructor of <code>NoSuchFileOrDirectory</code>.
+ *
+ * @param src The file or directory not found
+ */
+ public NoSuchFileOrDirectory(String src) {
+ super(src);
+ }
+
+}
diff --git a/src/com/cyanogenmod/filemanager/console/OperationTimeoutException.java b/src/com/cyanogenmod/filemanager/console/OperationTimeoutException.java
new file mode 100644
index 00000000..c549fda0
--- /dev/null
+++ b/src/com/cyanogenmod/filemanager/console/OperationTimeoutException.java
@@ -0,0 +1,36 @@
+/*
+ * 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.filemanager.console;
+
+/**
+ * An exception thrown when the operation exceeds the timeout.
+ */
+public class OperationTimeoutException extends Exception {
+
+ private static final long serialVersionUID = -6478720898011407262L;
+
+ /**
+ * Constructor of <code>OperationTimeoutException</code>.
+ *
+ * @param timeout Maximum time to complete operation
+ * @param command Executed command
+ */
+ public OperationTimeoutException(long timeout, String command) {
+ super(String.format("(%d) %s", Long.valueOf(timeout), command)); //$NON-NLS-1$
+ }
+
+}
diff --git a/src/com/cyanogenmod/filemanager/console/ReadOnlyFilesystemException.java b/src/com/cyanogenmod/filemanager/console/ReadOnlyFilesystemException.java
new file mode 100644
index 00000000..6e26146b
--- /dev/null
+++ b/src/com/cyanogenmod/filemanager/console/ReadOnlyFilesystemException.java
@@ -0,0 +1,53 @@
+/*
+ * 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.filemanager.console;
+
+import com.cyanogenmod.filemanager.model.MountPoint;
+
+/**
+ * An exception thrown when an operation is writing in a read-only filesystem.
+ */
+public class ReadOnlyFilesystemException extends Exception {
+
+ private static final long serialVersionUID = -9105723411032174730L;
+
+ private final MountPoint mMountPoint;
+
+ /**
+ * Constructor of <code>ReadOnlyFilesystemException</code>.
+ *
+ * @param mp The read-only mount point that causes the {@link ReadOnlyFilesystemException}
+ */
+ public ReadOnlyFilesystemException(MountPoint mp) {
+ super(mp.getMountPoint());
+ this.mMountPoint = mp;
+ }
+
+ /**
+ * Method that returns the read-only mount point that
+ * causes the {@link ReadOnlyFilesystemException}.
+ *
+ * @return MountPoint The read-only mount point that causes
+ * the {@link ReadOnlyFilesystemException}
+ */
+ public MountPoint getMountPoint() {
+ return this.mMountPoint;
+ }
+
+
+
+}
diff --git a/src/com/cyanogenmod/filemanager/console/RelaunchableException.java b/src/com/cyanogenmod/filemanager/console/RelaunchableException.java
new file mode 100644
index 00000000..db5ece7b
--- /dev/null
+++ b/src/com/cyanogenmod/filemanager/console/RelaunchableException.java
@@ -0,0 +1,116 @@
+/*
+ * 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.filemanager.console;
+
+import android.os.AsyncTask;
+
+import com.cyanogenmod.filemanager.commands.SyncResultExecutable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * An exception that determines that an operation should be re-executed.
+ */
+public abstract class RelaunchableException extends Exception {
+
+ private static final long serialVersionUID = -3897597978154453512L;
+
+ private final List<SyncResultExecutable> mExecutables;
+ private AsyncTask<Object, Integer, Boolean> mTask;
+
+ /**
+ * Constructor of <code>RelaunchableException</code>.
+ *
+ * @param executable The executable that should be re-executed
+ */
+ public RelaunchableException(SyncResultExecutable executable) {
+ super();
+ this.mExecutables = new ArrayList<SyncResultExecutable>();
+ addExecutable(executable);
+ }
+
+ /**
+ * Constructor of <code>RelaunchableException</code>.
+ *
+ * @param detailMessage Message associated to the exception
+ * @param executable The executable that should be re-executed
+ */
+ public RelaunchableException(String detailMessage, SyncResultExecutable executable) {
+ super(detailMessage);
+ this.mExecutables = new ArrayList<SyncResultExecutable>();
+ addExecutable(executable);
+ }
+
+ /**
+ * Constructor of <code>RelaunchableException</code>.
+ *
+ * @param detailMessage Message associated to the exception
+ * @param throwable The cause of the exception
+ * @param executable The executable that should be re-executed
+ */
+ public RelaunchableException(
+ String detailMessage, Throwable throwable, SyncResultExecutable executable) {
+ super(detailMessage, throwable);
+ this.mExecutables = new ArrayList<SyncResultExecutable>();
+ addExecutable(executable);
+ }
+
+ /**
+ * Method that returns the executable that should be re-executed.
+ *
+ * @return SyncResultExecutable The list of executable that should be re-executed
+ */
+ public List<SyncResultExecutable> getExecutables() {
+ return this.mExecutables;
+ }
+
+ /**
+ * Method that add a new executable to the queue of command to be re-executed.
+ *
+ * @param executable The executable to add
+ */
+ public void addExecutable(SyncResultExecutable executable) {
+ this.mExecutables.add(executable);
+ }
+
+ /**
+ * Method that returns the task to execute when the re-execution ends.
+ *
+ * @return AsyncTask<Object, Integer, Boolean> The task to execute when the re-execution ends
+ */
+ public AsyncTask<Object, Integer, Boolean> getTask() {
+ return this.mTask;
+ }
+
+ /**
+ * Method that set the task to execute when the re-execution ends.
+ *
+ * @param task The task to execute when the re-execution ends
+ */
+ public void setTask(AsyncTask<Object, Integer, Boolean> task) {
+ this.mTask = task;
+ }
+
+ /**
+ * Method that returns he resource identifier of the question to translate to the user.
+ *
+ * @return int The resource identifier of the question to translate to the user.
+ */
+ public abstract int getQuestionResourceId();
+
+}
diff --git a/src/com/cyanogenmod/filemanager/console/java/JavaConsole.java b/src/com/cyanogenmod/filemanager/console/java/JavaConsole.java
new file mode 100644
index 00000000..df04d4a5
--- /dev/null
+++ b/src/com/cyanogenmod/filemanager/console/java/JavaConsole.java
@@ -0,0 +1,255 @@
+/*
+ * 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.filemanager.console.java;
+
+import android.content.Context;
+import android.os.storage.StorageVolume;
+import android.util.Log;
+
+import com.cyanogenmod.filemanager.commands.ChangeCurrentDirExecutable;
+import com.cyanogenmod.filemanager.commands.Executable;
+import com.cyanogenmod.filemanager.commands.ExecutableFactory;
+import com.cyanogenmod.filemanager.commands.SIGNAL;
+import com.cyanogenmod.filemanager.commands.java.JavaExecutableFactory;
+import com.cyanogenmod.filemanager.commands.java.Program;
+import com.cyanogenmod.filemanager.console.CommandNotFoundException;
+import com.cyanogenmod.filemanager.console.Console;
+import com.cyanogenmod.filemanager.console.ConsoleAllocException;
+import com.cyanogenmod.filemanager.console.ExecutionException;
+import com.cyanogenmod.filemanager.console.InsufficientPermissionsException;
+import com.cyanogenmod.filemanager.console.NoSuchFileOrDirectory;
+import com.cyanogenmod.filemanager.console.OperationTimeoutException;
+import com.cyanogenmod.filemanager.console.ReadOnlyFilesystemException;
+import com.cyanogenmod.filemanager.model.Identity;
+import com.cyanogenmod.filemanager.util.StorageHelper;
+
+/**
+ * An implementation of a {@link Console} based on a java implementation.<br/>
+ * <br/>
+ * This console is a non-privileged console an many of the functionality is not implemented
+ * because can't be obtain from java api.
+ */
+public final class JavaConsole extends Console {
+
+ private static final String TAG = "JavaConsole"; //$NON-NLS-1$
+
+ private boolean mActive;
+ private String mCurrentDir;
+
+ private final Context mCtx;
+ private final int mBufferSize;
+
+ /**
+ * Constructor of <code>JavaConsole</code>
+ *
+ * @param ctx The current context
+ * @param initialDir The initial directory
+ * @param bufferSize The buffer size
+ */
+ public JavaConsole(Context ctx, String initialDir, int bufferSize) {
+ super();
+ this.mCtx = ctx;
+ this.mBufferSize = bufferSize;
+ this.mCurrentDir = initialDir;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void alloc() throws ConsoleAllocException {
+ try {
+ if (isTrace()) {
+ Log.v(TAG, "Allocating Java console"); //$NON-NLS-1$
+ }
+
+ //Retrieve the current directory from the first storage volume
+ StorageVolume[] vols = StorageHelper.getStorageVolumes(this.mCtx);
+ if (vols == null || vols.length == 0) {
+ throw new ConsoleAllocException("Can't stat any directory"); //$NON-NLS-1$
+ }
+
+ // Test to change to current directory
+ ChangeCurrentDirExecutable currentDirCmd =
+ getExecutableFactory().
+ newCreator().createChangeCurrentDirExecutable(this.mCurrentDir);
+ execute(currentDirCmd);
+
+ // Tested. Is not active
+ this.mCurrentDir = vols[0].getPath();
+ this.mActive = true;
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to allocate Java console", e); //$NON-NLS-1$
+ throw new ConsoleAllocException("failed to build console", e); //$NON-NLS-1$
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void dealloc() {
+ if (isTrace()) {
+ Log.v(TAG, "Deallocating Java console"); //$NON-NLS-1$
+ }
+ this.mActive = true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void realloc() throws ConsoleAllocException {
+ dealloc();
+ alloc();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public ExecutableFactory getExecutableFactory() {
+ return new JavaExecutableFactory(this);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Identity getIdentity() {
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isPrivileged() {
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isActive() {
+ return this.mActive;
+ }
+
+ /**
+ * Method that returns the current directory of the console
+ *
+ * @return String The current directory
+ */
+ public String getCurrentDir() {
+ return this.mCurrentDir;
+ }
+
+ /**
+ * Method that sets the current directory of the console
+ *
+ * @param currentDir The current directory
+ */
+ public void setCurrentDir(String currentDir) {
+ this.mCurrentDir = currentDir;
+ }
+
+ /**
+ * Method that returns the current context
+ *
+ * @return Context The current context
+ */
+ public Context getCtx() {
+ return this.mCtx;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public synchronized void execute(Executable executable) throws ConsoleAllocException,
+ InsufficientPermissionsException, NoSuchFileOrDirectory,
+ OperationTimeoutException, ExecutionException,
+ CommandNotFoundException, ReadOnlyFilesystemException {
+ // Check that the program is a java program
+ try {
+ Program p = (Program)executable;
+ p.isTrace();
+ } catch (Throwable e) {
+ Log.e(TAG, String.format("Failed to resolve program: %s", //$NON-NLS-1$
+ executable.getClass().toString()), e);
+ throw new CommandNotFoundException("executable is not a program", e); //$NON-NLS-1$
+ }
+
+ //Auditing program execution
+ if (isTrace()) {
+ Log.v(TAG, String.format("Executing program: %s", //$NON-NLS-1$
+ executable.getClass().toString()));
+ }
+
+ // Execute the program
+ final Program program = (Program)executable;
+ program.setTrace(isTrace());
+ program.setBufferSize(this.mBufferSize);
+ if (program.isAsynchronous()) {
+ // Execute in a thread
+ Thread t = new Thread() {
+ @Override
+ public void run() {
+ try {
+ program.execute();
+ } catch (Exception e) {
+ // Program must use onException to communicate exceptions
+ Log.v(TAG,
+ String.format("Async execute failed program: %s", //$NON-NLS-1$
+ program.getClass().toString()));
+ }
+ }
+ };
+ t.start();
+
+ } else {
+ // Synchronous execution
+ program.execute();
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean onCancel() {
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean onSendSignal(SIGNAL signal) {
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean onEnd() {
+ return false;
+ }
+
+} \ No newline at end of file
diff --git a/src/com/cyanogenmod/filemanager/console/shell/NonPriviledgeConsole.java b/src/com/cyanogenmod/filemanager/console/shell/NonPriviledgeConsole.java
new file mode 100644
index 00000000..aec764f8
--- /dev/null
+++ b/src/com/cyanogenmod/filemanager/console/shell/NonPriviledgeConsole.java
@@ -0,0 +1,66 @@
+/*
+ * 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.filemanager.console.shell;
+
+import com.cyanogenmod.filemanager.commands.shell.BashShell;
+import com.cyanogenmod.filemanager.commands.shell.InvalidCommandDefinitionException;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+/**
+ * A class that represents a non privileged shell console.
+ *
+ * @see ShellConsole
+ * @see BashShell
+ */
+public class NonPriviledgeConsole extends ShellConsole {
+
+ /**
+ * Constructor of <code>NonPriviledgeConsole</code>.
+ *
+ * @param initialDirectory The initial directory of the shell
+ * @throws FileNotFoundException If the initial directory not exists
+ * @throws IOException If initial directory couldn't be checked
+ * @throws InvalidCommandDefinitionException If the command has an invalid definition
+ */
+ public NonPriviledgeConsole(String initialDirectory)
+ throws FileNotFoundException, IOException, InvalidCommandDefinitionException {
+ super(new BashShell(), initialDirectory);
+ }
+
+ /**
+ * Constructor of <code>NonPriviledgeConsole</code>.
+ *
+ * @throws FileNotFoundException If the default initial directory not exists
+ * @throws IOException If initial directory couldn't be checked
+ * @throws InvalidCommandDefinitionException If the command has an invalid definition
+ */
+ public NonPriviledgeConsole()
+ throws FileNotFoundException, IOException, InvalidCommandDefinitionException {
+ super(new BashShell());
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isPrivileged() {
+ return false;
+ }
+
+}
diff --git a/src/com/cyanogenmod/filemanager/console/shell/PrivilegedConsole.java b/src/com/cyanogenmod/filemanager/console/shell/PrivilegedConsole.java
new file mode 100644
index 00000000..4e5d3088
--- /dev/null
+++ b/src/com/cyanogenmod/filemanager/console/shell/PrivilegedConsole.java
@@ -0,0 +1,67 @@
+/*
+ * 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.filemanager.console.shell;
+
+import com.cyanogenmod.filemanager.commands.shell.InvalidCommandDefinitionException;
+import com.cyanogenmod.filemanager.commands.shell.SuperuserShell;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+
+/**
+ * A class that represents a privileged shell console.
+ *
+ * @see ShellConsole
+ * @see SuperuserShell
+ */
+public class PrivilegedConsole extends ShellConsole {
+
+ /**
+ * Constructor of <code>PrivilegedConsole</code>.
+ *
+ * @param initialDirectory The initial directory of the shell
+ * @throws FileNotFoundException If the initial directory not exists
+ * @throws IOException If initial directory couldn't be checked
+ * @throws InvalidCommandDefinitionException If the command has an invalid definition
+ */
+ public PrivilegedConsole(String initialDirectory)
+ throws FileNotFoundException, IOException, InvalidCommandDefinitionException {
+ super(new SuperuserShell(), initialDirectory);
+ }
+
+ /**
+ * Constructor of <code>PrivilegedConsole</code>.
+ *
+ * @throws FileNotFoundException If the default initial directory not exists
+ * @throws IOException If initial directory couldn't be checked
+ * @throws InvalidCommandDefinitionException If the command has an invalid definition
+ */
+ public PrivilegedConsole()
+ throws FileNotFoundException, IOException, InvalidCommandDefinitionException {
+ super(new SuperuserShell());
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isPrivileged() {
+ return true;
+ }
+
+}
diff --git a/src/com/cyanogenmod/filemanager/console/shell/ShellConsole.java b/src/com/cyanogenmod/filemanager/console/shell/ShellConsole.java
new file mode 100644
index 00000000..b7ddf990
--- /dev/null
+++ b/src/com/cyanogenmod/filemanager/console/shell/ShellConsole.java
@@ -0,0 +1,1233 @@
+/*
+ * 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.filemanager.console.shell;
+
+import android.util.Log;
+
+import com.cyanogenmod.filemanager.FileManagerApplication;
+import com.cyanogenmod.filemanager.commands.AsyncResultExecutable;
+import com.cyanogenmod.filemanager.commands.Executable;
+import com.cyanogenmod.filemanager.commands.ExecutableFactory;
+import com.cyanogenmod.filemanager.commands.GroupsExecutable;
+import com.cyanogenmod.filemanager.commands.IdentityExecutable;
+import com.cyanogenmod.filemanager.commands.ProcessIdExecutable;
+import com.cyanogenmod.filemanager.commands.SIGNAL;
+import com.cyanogenmod.filemanager.commands.shell.AsyncResultProgram;
+import com.cyanogenmod.filemanager.commands.shell.Command;
+import com.cyanogenmod.filemanager.commands.shell.InvalidCommandDefinitionException;
+import com.cyanogenmod.filemanager.commands.shell.Program;
+import com.cyanogenmod.filemanager.commands.shell.Shell;
+import com.cyanogenmod.filemanager.commands.shell.ShellExecutableFactory;
+import com.cyanogenmod.filemanager.commands.shell.SyncResultProgram;
+import com.cyanogenmod.filemanager.console.CommandNotFoundException;
+import com.cyanogenmod.filemanager.console.Console;
+import com.cyanogenmod.filemanager.console.ConsoleAllocException;
+import com.cyanogenmod.filemanager.console.ExecutionException;
+import com.cyanogenmod.filemanager.console.InsufficientPermissionsException;
+import com.cyanogenmod.filemanager.console.NoSuchFileOrDirectory;
+import com.cyanogenmod.filemanager.console.OperationTimeoutException;
+import com.cyanogenmod.filemanager.console.ReadOnlyFilesystemException;
+import com.cyanogenmod.filemanager.model.Identity;
+import com.cyanogenmod.filemanager.preferences.FileManagerSettings;
+import com.cyanogenmod.filemanager.preferences.Preferences;
+import com.cyanogenmod.filemanager.util.CommandHelper;
+import com.cyanogenmod.filemanager.util.FileHelper;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.SecureRandom;
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * An implementation of a {@link Console} based in the execution of shell commands.<br/>
+ * <br/>
+ * This class holds a <code>shell bash</code> program associated with the application, being
+ * a wrapper to execute all other programs (like shell does in linux), capturing the
+ * output (stdin and stderr) and the exit code of the program executed.
+ */
+public abstract class ShellConsole extends Console implements Program.ProgramListener {
+
+ private static final String TAG = "ShellConsole"; //$NON-NLS-1$
+
+ // A timeout of 5 seconds should be enough for no-debugging environments
+ private static final long DEFAULT_TIMEOUT =
+ FileManagerApplication.isDebuggable() ? 20000L : 5000L;
+
+ private static final int DEFAULT_BUFFER = 512;
+
+ //Shell References
+ private final Shell mShell;
+ private final String mInitialDirectory;
+ private Identity mIdentity;
+
+ //Process References
+ private final Object mSync = new Object();
+ /**
+ * @hide
+ */
+ final Object mPartialSync = new Object();
+ /**
+ * @hide
+ */
+ boolean mActive = false;
+ private boolean mFinished = true;
+ private Process mProc = null;
+ /**
+ * @hide
+ */
+ Program mActiveCommand = null;
+ /**
+ * @hide
+ */
+ boolean mCancelled;
+ /**
+ * @hide
+ */
+ boolean mStarted;
+
+ //Buffers
+ private InputStream mIn = null;
+ private InputStream mErr = null;
+ private OutputStream mOut = null;
+ /**
+ * @hide
+ */
+ StringBuffer mSbIn = null;
+ /**
+ * @hide
+ */
+ StringBuffer mSbErr = null;
+
+ private final SecureRandom mRandom;
+ private String mStartControlPattern;
+ private String mEndControlPattern;
+
+ /**
+ * @hide
+ */
+ int mBufferSize;
+
+ private final ShellExecutableFactory mExecutableFactory;
+
+ /**
+ * Constructor of <code>ShellConsole</code>.
+ *
+ * @param shell The shell used to execute commands
+ * @throws FileNotFoundException If the default initial directory not exists
+ * @throws IOException If initial directory couldn't be resolved
+ */
+ public ShellConsole(Shell shell) throws FileNotFoundException, IOException {
+ this(shell, Preferences.getSharedPreferences().getString(
+ FileManagerSettings.SETTINGS_INITIAL_DIR.getId(),
+ (String)FileManagerSettings.SETTINGS_INITIAL_DIR.getDefaultValue()));
+ }
+
+ /**
+ * Constructor of <code>ShellConsole</code>.
+ *
+ * @param shell The shell used to execute commands
+ * @param initialDirectory The initial directory of the shell
+ * @throws FileNotFoundException If the initial directory not exists
+ * @throws IOException If initial directory couldn't be resolved
+ */
+ public ShellConsole(Shell shell, String initialDirectory)
+ throws FileNotFoundException, IOException {
+ super();
+ this.mShell = shell;
+ this.mExecutableFactory = new ShellExecutableFactory(this);
+
+ this.mBufferSize = DEFAULT_BUFFER;
+
+ //Resolve and checks the initial directory
+ File f = new File(initialDirectory);
+ while (FileHelper.isSymlink(f)) {
+ f = FileHelper.resolveSymlink(f);
+ }
+ if (!f.exists() || !f.isDirectory()) {
+ throw new FileNotFoundException(f.toString());
+ }
+ this.mInitialDirectory = initialDirectory;
+
+ //Restart the buffers
+ this.mSbIn = new StringBuffer();
+ this.mSbErr = new StringBuffer();
+
+ //Generate an aleatory secure random generator
+ try {
+ this.mRandom = SecureRandom.getInstance("SHA1PRNG"); //$NON-NLS-1$
+ } catch (Exception ex) {
+ throw new IOException(ex);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public ExecutableFactory getExecutableFactory() {
+ return this.mExecutableFactory;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Identity getIdentity() {
+ return this.mIdentity;
+ }
+
+ /**
+ * Method that returns the buffer size
+ *
+ * @return int The buffer size
+ */
+ public int getBufferSize() {
+ return this.mBufferSize;
+ }
+
+ /**
+ * Method that sets the buffer size
+ *
+ * @param bufferSize the The buffer size
+ */
+ public void setBufferSize(int bufferSize) {
+ this.mBufferSize = bufferSize;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public final boolean isActive() {
+ return this.mActive;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public final void alloc() throws ConsoleAllocException {
+ try {
+ //Create command string
+ List<String> cmd = new ArrayList<String>();
+ cmd.add(this.mShell.getCommand());
+ if (this.mShell.getArguments() != null && this.mShell.getArguments().length() > 0) {
+ cmd.add(this.mShell.getArguments());
+ }
+
+ //Create the process
+ Runtime rt = Runtime.getRuntime();
+ this.mProc =
+ rt.exec(
+ cmd.toArray(new String[cmd.size()]),
+ null,
+ new File(this.mInitialDirectory));
+ synchronized (this.mSync) {
+ this.mActive = true;
+ }
+ if (isTrace()) {
+ Log.v(TAG,
+ String.format("Create console %s, command: %s, args: %s", //$NON-NLS-1$
+ this.mShell.getId(),
+ this.mShell.getCommand(),
+ this.mShell.getArguments()));
+ }
+
+ //Allocate buffers
+ this.mIn = this.mProc.getInputStream();
+ this.mErr = this.mProc.getErrorStream();
+ this.mOut = this.mProc.getOutputStream();
+ if (this.mIn == null || this.mErr == null || this.mOut == null) {
+ try {
+ dealloc();
+ } catch (Throwable ex) {
+ /**NON BLOCK**/
+ }
+ throw new ConsoleAllocException("Console buffer allocation error."); //$NON-NLS-1$
+ }
+
+ //Starts a thread for extract output, and check timeout
+ createStdInThread(this.mIn);
+ createStdErrThread(this.mErr);
+
+ //Wait for thread start
+ Thread.sleep(50L);
+
+ //Check if process its active
+ checkIfProcessExits();
+ synchronized (this.mSync) {
+ if (!this.mActive) {
+ throw new ConsoleAllocException("Shell not started."); //$NON-NLS-1$
+ }
+ }
+
+ // Retrieve the PID of the shell
+ ProcessIdExecutable processIdCmd =
+ getExecutableFactory().
+ newCreator().createShellProcessIdExecutable();
+ execute(processIdCmd);
+ Integer pid = processIdCmd.getResult();
+ if (pid == null) {
+ throw new ConsoleAllocException(
+ "can't retrieve the PID of the shell."); //$NON-NLS-1$
+ }
+ this.mShell.setPid(pid.intValue());
+
+ //Retrieve identity
+ IdentityExecutable identityCmd =
+ getExecutableFactory().newCreator().createIdentityExecutable();
+ execute(identityCmd);
+ this.mIdentity = identityCmd.getResult();
+ if (this.mIdentity.getGroups().size() == 0) {
+ //Try with groups
+ GroupsExecutable groupsCmd =
+ getExecutableFactory().newCreator().createGroupsExecutable();
+ execute(groupsCmd);
+ this.mIdentity.setGroups(groupsCmd.getResult());
+ }
+
+ } catch (Exception ex) {
+ try {
+ dealloc();
+ } catch (Throwable ex2) {
+ /**NON BLOCK**/
+ }
+ throw new ConsoleAllocException("Console allocation error.", ex); //$NON-NLS-1$
+ }
+
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public final void dealloc() {
+ synchronized (this.mSync) {
+ if (this.mActive) {
+ this.mActive = false;
+ this.mFinished = true;
+
+ //Close buffers
+ try {
+ if (this.mIn != null) {
+ this.mIn.close();
+ }
+ } catch (Throwable ex) {
+ /**NON BLOCK**/
+ }
+ try {
+ if (this.mErr != null) {
+ this.mErr.close();
+ }
+ } catch (Throwable ex) {
+ /**NON BLOCK**/
+ }
+ try {
+ if (this.mOut != null) {
+ this.mOut.close();
+ }
+ } catch (Throwable ex) {
+ /**NON BLOCK**/
+ }
+ try {
+ this.mProc.destroy();
+ } catch (Throwable e) {/**NON BLOCK**/}
+ this.mIn = null;
+ this.mErr = null;
+ this.mOut = null;
+ this.mSbIn = null;
+ this.mSbErr = null;
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public final void realloc() throws ConsoleAllocException {
+ dealloc();
+ alloc();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public final synchronized void execute(final Executable executable)
+ throws ConsoleAllocException, InsufficientPermissionsException,
+ CommandNotFoundException, NoSuchFileOrDirectory,
+ OperationTimeoutException, ExecutionException, ReadOnlyFilesystemException {
+
+ //Is a program?
+ if (!(executable instanceof Program)) {
+ throw new CommandNotFoundException("executable not instanceof Program"); //$NON-NLS-1$
+ }
+
+ //Asynchronous or synchronous execution?
+ final Program program = (Program)executable;
+ if (executable instanceof AsyncResultExecutable) {
+ Thread asyncThread = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ //Synchronous execution (but asynchronous running in a thread)
+ //This way syncExecute is locked until this thread ends
+ try {
+ if (ShellConsole.this.syncExecute(program, true)) {
+ ShellConsole.this.syncExecute(program, false);
+ }
+ } catch (Exception ex) {
+ if (((AsyncResultExecutable)executable).getAsyncResultListener() != null) {
+ ((AsyncResultExecutable)executable).
+ getAsyncResultListener().onException(ex);
+ } else {
+ //Capture exception
+ Log.e(TAG, "Fail asynchronous execution", ex); //$NON-NLS-1$
+ }
+ }
+ }
+ });
+ asyncThread.start();
+ } else {
+ //Synchronous execution (2 tries with 1 reallocation)
+ if (syncExecute(program, true)) {
+ syncExecute(program, false);
+ }
+ }
+ }
+
+ /**
+ * Method for execute a program command in the operating system layer in a synchronous way.
+ *
+ * @param program The program to execute
+ * @param reallocate If the console must be reallocated on i/o error
+ * @return boolean If the console was reallocated
+ * @throws ConsoleAllocException If the console is not allocated
+ * @throws InsufficientPermissionsException If an operation requires elevated permissions
+ * @throws CommandNotFoundException If the command was not found
+ * @throws NoSuchFileOrDirectory If the file or directory was not found
+ * @throws OperationTimeoutException If the operation exceeded the maximum time of wait
+ * @throws ExecutionException If the operation returns a invalid exit code
+ * @throws ReadOnlyFilesystemException If the operation writes in a read-only filesystem
+ * @hide
+ */
+ synchronized boolean syncExecute(final Program program, boolean reallocate)
+ throws ConsoleAllocException, InsufficientPermissionsException,
+ CommandNotFoundException, NoSuchFileOrDirectory,
+ OperationTimeoutException, ExecutionException, ReadOnlyFilesystemException {
+
+ try {
+ //Check the console status before send command
+ checkConsole();
+
+ synchronized (this.mSync) {
+ if (!this.mActive) {
+ throw new ConsoleAllocException("No console allocated"); //$NON-NLS-1$
+ }
+ }
+
+ //Saves the active command reference
+ this.mActiveCommand = program;
+
+ //Reset the buffers
+ this.mStarted = false;
+ this.mCancelled = false;
+ this.mSbIn = new StringBuffer();
+ this.mSbErr = new StringBuffer();
+
+ //Random start/end identifiers
+ String startId1 =
+ String.format("/#%d#/", Long.valueOf(this.mRandom.nextLong())); //$NON-NLS-1$
+ String startId2 =
+ String.format("/#%d#/", Long.valueOf(this.mRandom.nextLong())); //$NON-NLS-1$
+ String endId1 =
+ String.format("/#%d#/", Long.valueOf(this.mRandom.nextLong())); //$NON-NLS-1$
+ String endId2 =
+ String.format("/#%d#/", Long.valueOf(this.mRandom.nextLong())); //$NON-NLS-1$
+
+ //Create command string
+ String cmd = program.getCommand();
+ String args = program.getArguments();
+
+ //Audit command
+ if (isTrace()) {
+ Log.v(TAG,
+ String.format("%s-%s, command: %s, args: %s", //$NON-NLS-1$
+ ShellConsole.this.mShell.getId(),
+ program.getId(),
+ cmd,
+ args));
+ }
+
+ //Is asynchronous program? Then set asynchronous
+ program.setProgramListener(this);
+ if (program instanceof AsyncResultProgram) {
+ ((AsyncResultProgram)program).setOnCancelListener(this);
+ ((AsyncResultProgram)program).setOnEndListener(this);
+ }
+
+ //Send the command + a control code with exit code
+ //The process has finished where control control code is present.
+ //This control code is unique in every invocation and is secure random
+ //generated (control code 1 + exit code + control code 2)
+ try {
+ boolean hasEndControl = (!(program instanceof AsyncResultProgram) ||
+ (program instanceof AsyncResultProgram &&
+ ((AsyncResultProgram)program).isExpectEnd()));
+
+ this.mStartControlPattern = startId1 + "\\d{1,3}" + startId2 + "\\n"; //$NON-NLS-1$ //$NON-NLS-2$
+ this.mEndControlPattern = endId1 + "\\d{1,3}" + endId2; //$NON-NLS-1$
+ String startCmd =
+ Command.getStartCodeCommandInfo(
+ FileManagerApplication.getInstance().getResources());
+ startCmd = String.format(
+ startCmd, "'" + startId1 +//$NON-NLS-1$
+ "'", "'" + startId2 + "'"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ String endCmd =
+ Command.getExitCodeCommandInfo(
+ FileManagerApplication.getInstance().getResources());
+ endCmd = String.format(
+ endCmd, "'" + endId1 + //$NON-NLS-1$
+ "'", "'" + endId2 + "'"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ StringBuilder sb = new StringBuilder()
+ .append(startCmd)
+ .append(" ") //$NON-NLS-1$
+ .append(cmd)
+ .append(" ") //$NON-NLS-1$
+ .append(args);
+ if (hasEndControl) {
+ sb = sb.append(" ") //$NON-NLS-1$
+ .append(endCmd);
+ }
+ sb.append(FileHelper.NEWLINE);
+ this.mOut.write(sb.toString().getBytes());
+ } catch (InvalidCommandDefinitionException icdEx) {
+ throw new CommandNotFoundException(
+ "ExitCodeCommandInfo not found", icdEx); //$NON-NLS-1$
+ }
+
+ //Now, wait for buffers to be filled
+ synchronized (this.mSync) {
+ if (program instanceof AsyncResultProgram) {
+ this.mSync.wait();
+ } else {
+ this.mSync.wait(DEFAULT_TIMEOUT);
+ if (!this.mFinished) {
+ throw new OperationTimeoutException(DEFAULT_TIMEOUT, cmd);
+ }
+ }
+ }
+
+ //End partial results?
+ if (program instanceof AsyncResultProgram) {
+ synchronized (this.mPartialSync) {
+ ((AsyncResultProgram)program).onRequestEndParsePartialResult(this.mCancelled);
+ }
+ }
+
+ //Retrieve exit code
+ int exitCode = getExitCode(this.mSbIn);
+ if (program instanceof AsyncResultProgram) {
+ synchronized (this.mPartialSync) {
+ ((AsyncResultProgram)program).onRequestExitCode(exitCode);
+ }
+ }
+ if (isTrace()) {
+ Log.v(TAG,
+ String.format("%s-%s, command: %s, exitCode: %s", //$NON-NLS-1$
+ ShellConsole.this.mShell.getId(),
+ program.getId(),
+ cmd,
+ String.valueOf(exitCode)));
+ }
+
+ //Check if invocation was successfully or not
+ if (!program.isIgnoreShellStdErrCheck()) {
+ this.mShell.checkStdErr(this.mActiveCommand, exitCode, this.mSbErr.toString());
+ }
+ this.mShell.checkExitCode(exitCode);
+ program.checkExitCode(exitCode);
+ program.checkStdErr(exitCode, this.mSbErr.toString());
+
+ //Parse the result? Only if not partial results
+ if (program instanceof SyncResultProgram) {
+ try {
+ ((SyncResultProgram)program).parse(
+ this.mSbIn.toString(), this.mSbErr.toString());
+ } catch (ParseException pEx) {
+ throw new ExecutionException(
+ "SyncResultProgram parse failed", pEx); //$NON-NLS-1$
+ }
+ }
+
+ //Invocation finished. Now program.getResult() has the result of
+ //the operation, if any exists
+
+ } catch (IOException ioEx) {
+ if (reallocate) {
+ realloc();
+ return true;
+ }
+ throw new ExecutionException("Console allocation error.", ioEx); //$NON-NLS-1$
+
+ } catch (InterruptedException ioEx) {
+ if (reallocate) {
+ realloc();
+ return true;
+ }
+ throw new ExecutionException("Console allocation error.", ioEx); //$NON-NLS-1$
+
+ } finally {
+ //Dereference the active command
+ this.mActiveCommand = null;
+ }
+
+ //Operation complete
+ return false;
+ }
+
+ /**
+ * Method that creates the standard input thread for read program response.
+ *
+ * @param in The standard input buffer
+ * @return Thread The standard input thread
+ */
+ private Thread createStdInThread(final InputStream in) {
+ Thread t = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ int read = 0;
+
+ try {
+ while (ShellConsole.this.mActive) {
+ //Read only one byte with active wait
+ final int r = in.read();
+ 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);
+ if (!ShellConsole.this.mStarted) {
+ ShellConsole.this.mStarted =
+ isCommandStarted(ShellConsole.this.mSbIn);
+ if (ShellConsole.this.mStarted) {
+ sb = new StringBuffer(ShellConsole.this.mSbIn.toString());
+ if (async) {
+ synchronized (ShellConsole.this.mPartialSync) {
+ ((AsyncResultProgram)ShellConsole.
+ this.mActiveCommand).
+ onRequestStartParsePartialResult();
+ }
+ }
+ } else {
+ sb.append(ShellConsole.this.mSbIn.toString());
+ }
+ } else {
+ sb.append((char)r);
+ }
+
+ //Notify asynchronous partial data
+ if (ShellConsole.this.mStarted && async) {
+ AsyncResultProgram program =
+ ((AsyncResultProgram)ShellConsole.this.mActiveCommand);
+ 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;
+ while (in.available() > 0 && count < 10) {
+ count++;
+ int available = Math.min(in.available(),
+ ShellConsole.this.mBufferSize);
+ 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;
+
+ final String s = new String(data, 0, read);
+ ShellConsole.this.mSbIn.append(s);
+ if (!ShellConsole.this.mStarted) {
+ ShellConsole.this.mStarted =
+ isCommandStarted(ShellConsole.this.mSbIn);
+ if (ShellConsole.this.mStarted) {
+ sb = new StringBuffer(ShellConsole.this.mSbIn.toString());
+ if (async) {
+ synchronized (ShellConsole.this.mPartialSync) {
+ ((AsyncResultProgram)ShellConsole.
+ this.mActiveCommand).
+ onRequestStartParsePartialResult();
+ }
+ }
+ } else {
+ sb.append(ShellConsole.this.mSbIn.toString());
+ }
+ } else {
+ sb.append(s);
+ }
+
+ //Check if the command has finished (and extract the control)
+ boolean finished = isCommandFinished(ShellConsole.this.mSbIn, sb);
+
+ //Notify asynchronous partial data
+ if (async) {
+ AsyncResultProgram program =
+ ((AsyncResultProgram)ShellConsole.this.mActiveCommand);
+ 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);
+ } catch (Throwable ex) {
+ /**NON BLOCK**/
+ }
+ }
+
+ //Asynchronous programs can cause a lot of output, control buffers
+ //for a low memory footprint
+ if (async) {
+ trimBuffer(ShellConsole.this.mSbIn);
+ trimBuffer(ShellConsole.this.mSbErr);
+ }
+
+ //Check if process has exited
+ checkIfProcessExits();
+ }
+ } catch (Exception ioEx) {
+ notifyProcessExit(ioEx);
+ }
+ }
+ });
+ t.setName(String.format("%s", "stdin")); //$NON-NLS-1$//$NON-NLS-2$
+ t.start();
+ return t;
+ }
+
+ /**
+ * 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
+ * @return Thread The standard error thread
+ */
+ private Thread createStdErrThread(final InputStream err) {
+ Thread t = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ int read = 0;
+
+ try {
+ while (ShellConsole.this.mActive) {
+ //Read only one byte with active wait
+ int r = err.read();
+ 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 && 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
+ //or maximum loop count is rebased
+ int count = 0;
+ while (err.available() > 0 && count < 10) {
+ count++;
+ int available = Math.min(err.available(),
+ ShellConsole.this.mBufferSize);
+ 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;
+
+ String s = new String(data, 0, read);
+ ShellConsole.this.mSbErr.append(s);
+ sb.append(s);
+
+ //Notify asynchronous partial data
+ if (async) {
+ AsyncResultProgram program =
+ ((AsyncResultProgram)ShellConsole.this.mActiveCommand);
+ program.parsePartialErrResult(s);
+ }
+ toStdErr(sb.toString());
+
+ //Wait for buffer to be filled
+ try {
+ Thread.sleep(50L);
+ } catch (Throwable ex) {
+ /**NON BLOCK**/
+ }
+ }
+
+ //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) {
+ trimBuffer(ShellConsole.this.mSbIn);
+ trimBuffer(ShellConsole.this.mSbErr);
+ }
+ }
+ } catch (Exception ioEx) {
+ notifyProcessExit(ioEx);
+ }
+ }
+ });
+ t.setName(String.format("%s", "stderr")); //$NON-NLS-1$//$NON-NLS-2$
+ t.start();
+ return t;
+ }
+
+ /**
+ * 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.
+ *
+ * @throws ConsoleAllocException If the console can't be reallocated
+ */
+ private void checkConsole() throws ConsoleAllocException {
+ try {
+ //Test write something to the buffer
+ this.mOut.write(FileHelper.NEWLINE.getBytes());
+ this.mOut.write(FileHelper.NEWLINE.getBytes());
+ } catch (IOException ioex) {
+ //Something is wrong with the buffers. Reallocate console.
+ Log.w(TAG,
+ "Something is wrong with the console buffers. Reallocate console.", //$NON-NLS-1$
+ ioex);
+
+ //Reallocate the damage console
+ realloc();
+ }
+ }
+
+ /**
+ * Method that verifies if the process had exited.
+ * @hide
+ */
+ void checkIfProcessExits() {
+ try {
+ if (this.mProc != null) {
+ synchronized (ShellConsole.this.mSync) {
+ this.mProc.exitValue();
+ }
+ this.mActive = false; //Exited
+ }
+ } catch (IllegalThreadStateException itsEx) {
+ //Not exited
+ }
+ }
+
+ /**
+ * Method that notifies the ending of the process.
+ *
+ * @param ex The exception, only if the process exit with a exception.
+ * Otherwise null
+ * @hide
+ */
+ void notifyProcessExit(Exception ex) {
+ synchronized (ShellConsole.this.mSync) {
+ if (this.mActive) {
+ this.mSync.notify();
+ this.mActive = false;
+ this.mFinished = true;
+ if (ex != null) {
+ Log.w(TAG, "Exits with exception", ex); //$NON-NLS-1$
+ }
+ }
+ }
+ }
+
+ /**
+ * Method that notifies the ending of the command execution.
+ * @hide
+ */
+ void notifyProcessFinished() {
+ synchronized (ShellConsole.this.mSync) {
+ if (this.mActive) {
+ this.mSync.notify();
+ this.mFinished = true;
+ }
+ }
+ }
+
+ /**
+ * Method that returns if the command has started by checking the
+ * standard input buffer. This method also removes the control start command
+ * from the buffer, if it's present, leaving in the buffer the new data bytes.
+ *
+ * @param stdin The standard in buffer
+ * @return boolean If the command has started
+ * @hide
+ */
+ boolean isCommandStarted(StringBuffer stdin) {
+ if (stdin == null) return false;
+ Pattern pattern = Pattern.compile(this.mStartControlPattern);
+ Matcher matcher = pattern.matcher(stdin.toString());
+ if (matcher.find()) {
+ stdin.replace(0, matcher.end(), ""); //$NON-NLS-1$
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Method that returns if the command has finished by checking the
+ * standard input buffer.
+ *
+ * @param stdin The standard in buffer
+ * @return boolean If the command has finished
+ * @hide
+ */
+ boolean isCommandFinished(StringBuffer stdin, StringBuffer partial) {
+ Pattern pattern = Pattern.compile(this.mEndControlPattern);
+ if (stdin == null) return false;
+ Matcher matcher = pattern.matcher(stdin.toString());
+ boolean ret = matcher.find();
+ // Remove partial
+ if (ret && partial != null) {
+ matcher = pattern.matcher(partial.toString());
+ if (matcher.find()) {
+ partial.replace(matcher.start(), matcher.end(), ""); //$NON-NLS-1$
+ }
+ }
+ return ret;
+ }
+
+ /**
+ * Method that returns the exit code of the last executed command.
+ *
+ * @param stdin The standard in buffer
+ * @return int The exit code of the last executed command
+ */
+ private int getExitCode(StringBuffer stdin) {
+ // If process was cancelled, don't expect a exit code.
+ // Returns always 143 code
+ if (this.mCancelled) {
+ return 143;
+ }
+
+ // Parse the stdin seeking exit code pattern
+ String txt = stdin.toString();
+ Pattern pattern = Pattern.compile(this.mEndControlPattern);
+ Matcher matcher = pattern.matcher(txt);
+ if (matcher.find()) {
+ this.mSbIn = new StringBuffer(txt.substring(0, matcher.start()));
+ String exitTxt = matcher.group();
+ return Integer.parseInt(
+ exitTxt.substring(
+ exitTxt.indexOf("#/") + 2, //$NON-NLS-1$
+ exitTxt.indexOf("/#", 2))); //$NON-NLS-1$
+ }
+ return 255;
+ }
+
+ /**
+ * Method that trim a buffer, let in the buffer some
+ * text to ensure that the exit code is in there.
+ *
+ * @param sb The buffer to trim
+ * @hide
+ */
+ @SuppressWarnings("static-method") void trimBuffer(StringBuffer sb) {
+ final int bufferSize = 200;
+ if (sb.length() > bufferSize) {
+ sb.delete(0, sb.length() - bufferSize);
+ }
+ }
+
+ /**
+ * Method that kill the current command.
+ *
+ * @return boolean If the program was killed
+ * @hide
+ */
+ private boolean killCurrentCommand() {
+ synchronized (this.mSync) {
+ //Is synchronous program? Otherwise it can't be cancelled
+ if (!(this.mActiveCommand instanceof AsyncResultProgram)) {
+ return false;
+ }
+ // Check background console
+ try {
+ FileManagerApplication.getBackgroundConsole();
+ } catch (Exception e) {
+ Log.w(TAG, "There is not background console. Not allowed.", e); //$NON-NLS-1$
+ return false;
+ }
+
+ final AsyncResultProgram program = (AsyncResultProgram)this.mActiveCommand;
+ if (program.getCommand() != null) {
+ try {
+ if (program.isCancellable()) {
+ //Get the PID in background
+ Integer pid =
+ CommandHelper.getProcessId(
+ null,
+ this.mShell.getPid(),
+ program.getCommand(),
+ FileManagerApplication.getBackgroundConsole());
+ if (pid != null) {
+ CommandHelper.sendSignal(
+ null,
+ pid.intValue(),
+ FileManagerApplication.getBackgroundConsole());
+ try {
+ //Wait for process kill
+ Thread.sleep(100L);
+ } catch (Throwable ex) {
+ /**NON BLOCK**/
+ }
+ this.mCancelled = true;
+ notifyProcessFinished();
+ this.mSync.notify();
+ return this.mCancelled;
+ }
+ }
+ } catch (Throwable ex) {
+ Log.w(TAG,
+ String.format("Unable to kill current program: %s", //$NON-NLS-1$
+ program.getCommand()), ex);
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Method that send a signal to the current command.
+ *
+ * @param SIGNAL The signal to send
+ * @return boolean If the signal was sent
+ * @hide
+ */
+ private boolean sendSignalToCurrentCommand(SIGNAL signal) {
+ synchronized (this.mSync) {
+ //Is synchronous program? Otherwise it can't be cancelled
+ if (!(this.mActiveCommand instanceof AsyncResultProgram)) {
+ return false;
+ }
+ // Check background console
+ try {
+ FileManagerApplication.getBackgroundConsole();
+ } catch (Exception e) {
+ Log.w(TAG, "There is not background console. Not allowed.", e); //$NON-NLS-1$
+ return false;
+ }
+
+ final AsyncResultProgram program = (AsyncResultProgram)this.mActiveCommand;
+ if (program.getCommand() != null) {
+ try {
+ if (program.isCancellable()) {
+ try {
+ //Get the PID in background
+ Integer pid =
+ CommandHelper.getProcessId(
+ null,
+ this.mShell.getPid(),
+ program.getCommand(),
+ FileManagerApplication.getBackgroundConsole());
+ if (pid != null) {
+ CommandHelper.sendSignal(
+ null,
+ pid.intValue(),
+ signal,
+ FileManagerApplication.getBackgroundConsole());
+ try {
+ //Wait for process kill
+ Thread.sleep(100L);
+ } catch (Throwable ex) {
+ /**NON BLOCK**/
+ }
+ return true;
+ }
+ } finally {
+ // It's finished
+ this.mCancelled = true;
+ notifyProcessFinished();
+ this.mSync.notify();
+ }
+ }
+ } catch (Throwable ex) {
+ Log.w(TAG,
+ String.format("Unable to send signal to current program: %s", //$NON-NLS-1$
+ program.getCommand()), ex);
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean onEnd() {
+ //Kill the current command on end request
+ return killCurrentCommand();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean onSendSignal(SIGNAL signal) {
+ //Send a signal to the current command on end request
+ return sendSignalToCurrentCommand(signal);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean onCancel() {
+ //Kill the current command on cancel request
+ return killCurrentCommand();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean onRequestWrite(
+ byte[] data, int offset, int byteCount) throws ExecutionException {
+ try {
+ // Method that write to the stdin the data requested by the program
+ if (this.mOut != null) {
+ this.mOut.write(data, offset, byteCount);
+ this.mOut.flush();
+ Thread.yield();
+ return true;
+ }
+ } catch (Exception ex) {
+ String msg = String.format("Unable to write data to program: %s", //$NON-NLS-1$
+ this.mActiveCommand.getCommand());
+ Log.e(TAG, msg, ex);
+ throw new ExecutionException(msg, ex);
+ }
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public OutputStream getOutputStream() {
+ return this.mOut;
+ }
+
+}