diff options
author | jruesga <jorge@ruesga.com> | 2012-10-26 03:08:49 +0200 |
---|---|---|
committer | jruesga <jorge@ruesga.com> | 2012-10-26 03:08:49 +0200 |
commit | 6be595d8eeba247a9fb9614a2c314d1e1a184f3d (patch) | |
tree | a013a200bca62336f9c3553005c14f76163f66f5 /src/com/cyanogenmod/filemanager/console | |
parent | 7370b7164125a63dfa8d40aa0de04c24754c6a64 (diff) | |
download | android_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')
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; + } + +} |