/*
* 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.util;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.DialogInterface;
import android.os.AsyncTask;
import android.util.Log;
import android.widget.Toast;
import com.cyanogenmod.filemanager.FileManagerApplication;
import com.cyanogenmod.filemanager.R;
import com.cyanogenmod.filemanager.commands.SyncResultExecutable;
import com.cyanogenmod.filemanager.commands.shell.InvalidCommandDefinitionException;
import com.cyanogenmod.filemanager.console.CommandNotFoundException;
import com.cyanogenmod.filemanager.console.ConsoleAllocException;
import com.cyanogenmod.filemanager.console.ConsoleBuilder;
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.console.RelaunchableException;
import com.cyanogenmod.filemanager.preferences.AccessMode;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.text.ParseException;
import java.util.List;
/**
* A helper class with useful methods for deal with exceptions.
*/
public final class ExceptionUtil {
/**
* An interface to communicate events related with the result of a command relaunch.
*/
public interface OnRelaunchCommandResult {
/**
* Method invoked when the relaunch operation was success
*/
void onSuccess();
/**
* Method invoked when the relaunch operation was cancelled by the user
*/
void onCancelled();
/**
* Method invoked when the relaunch operation was failed
*
* @param cause The cause of the failed operation
*/
void onFailed(Throwable cause);
}
/**
* Constructor of <code>ExceptionUtil</code>.
*/
private ExceptionUtil() {
super();
}
//Definition of known exceptions and his representation mode and resource identifiers
private static final Class<?>[] KNOWN_EXCEPTIONS = {
FileNotFoundException.class,
IOException.class,
InvalidCommandDefinitionException.class,
ConsoleAllocException.class,
NoSuchFileOrDirectory.class,
ReadOnlyFilesystemException.class,
InsufficientPermissionsException.class,
CommandNotFoundException.class,
OperationTimeoutException.class,
ExecutionException.class,
ParseException.class,
ActivityNotFoundException.class
};
private static final int[] KNOWN_EXCEPTIONS_IDS = {
R.string.msgs_file_not_found,
R.string.msgs_io_failed,
R.string.msgs_command_not_found,
R.string.msgs_console_alloc_failure,
R.string.msgs_file_not_found,
R.string.msgs_read_only_filesystem,
R.string.msgs_insufficient_permissions,
R.string.msgs_command_not_found,
R.string.msgs_operation_timeout,
R.string.msgs_operation_failure,
R.string.msgs_operation_failure,
R.string.msgs_not_registered_app
};
private static final boolean[] KNOWN_EXCEPTIONS_TOAST = {
false,
false,
false,
false,
false,
true,
true,
false,
true,
true,
true,
false
};
/**
* Method that attach a asynchronous task for executing when exception need
* to be re-executed.
*
* @param ex The exception
* @param task The task
* @see RelaunchableException
*/
public static void attachAsyncTask(Throwable ex, AsyncTask<Object, Integer, Boolean> task) {
if (ex instanceof RelaunchableException) {
((RelaunchableException)ex).setTask(task);
}
}
/**
* Method that captures and translate an exception, showing a
* toast or a alert, according to the importance.
*
* @param context The current context
* @param ex The exception
*/
public static synchronized void translateException(
final Context context, Throwable ex) {
translateException(context, ex, false, true);
}
/**
* Method that captures and translate an exception, showing a
* toast or a alert, according to the importance.
*
* @param context The current context.
* @param ex The exception
* @param quiet Don't show UI messages
* @param askUser Ask the user when if the exception could be relaunched with other privileged
*/
public static synchronized void translateException(
final Context context, final Throwable ex,
final boolean quiet, final boolean askUser) {
translateException(context, ex, quiet, askUser, null);
}
/**
* Method that captures and translate an exception, showing a
* toast or a alert, according to the importance.
*
* @param context The current context.
* @param ex The exception
* @param quiet Don't show UI messages
* @param askUser Ask the user when if the exception could be relaunched with other privileged
* @param listener The listener where return the relaunch result
*/
public static synchronized void translateException(
final Context context, final Throwable ex,
final boolean quiet, final boolean askUser,
final OnRelaunchCommandResult listener) {
//Get the appropriate message for the exception
int msgResId = R.string.msgs_unknown;
boolean toast = true;
int cc = KNOWN_EXCEPTIONS.length;
for (int i = 0; i < cc; i++) {
if (KNOWN_EXCEPTIONS[i].getCanonicalName().compareTo(
ex.getClass().getCanonicalName()) == 0) {
msgResId = KNOWN_EXCEPTIONS_IDS[i];
toast = KNOWN_EXCEPTIONS_TOAST[i];
break;
}
}
//Check exceptions that can be asked to user
if (ex instanceof RelaunchableException && askUser) {
((Activity)context).runOnUiThread(new Runnable() {
@Override
public void run() {
askUser(context, (RelaunchableException)ex, quiet, listener);
}
});
return;
}
//Audit the exception
Log.e(context.getClass().getSimpleName(), "Error detected", ex); //$NON-NLS-1$
//Build the alert
final int fMsgResId = msgResId;
final boolean fToast = toast;
if (!quiet) {
((Activity)context).runOnUiThread(new Runnable() {
@Override
public void run() {
try {
if (fToast) {
DialogHelper.showToast(context, fMsgResId, Toast.LENGTH_SHORT);
} else {
AlertDialog dialog =
DialogHelper.createErrorDialog(
context, R.string.error_title, fMsgResId);
DialogHelper.delegateDialogShow(context, dialog);
}
} catch (Exception e) {
Log.e(context.getClass().getSimpleName(),
"ExceptionUtil. Failed to show dialog", ex); //$NON-NLS-1$
}
}
});
}
}
/**
* Method that ask the user for an operation and re-execution of the command.
*
* @param context The current context
* @param relaunchable The exception that contains the command that must be re-executed.
* @param listener The listener where return the relaunch result
* @hide
*/
static void askUser(
final Context context,
final RelaunchableException relaunchable,
final boolean quiet,
final OnRelaunchCommandResult listener) {
//Is privileged?
boolean isPrivileged = false;
try {
isPrivileged = ConsoleBuilder.getConsole(context).isPrivileged();
} catch (Throwable ex) {
/**NON BLOCK**/
}
// If console is privileged there is not need to change
// If we are in a ChRooted environment, resolve the error without doing anymore
if (relaunchable instanceof InsufficientPermissionsException &&
(isPrivileged ||
FileManagerApplication.getAccessMode().compareTo(AccessMode.SAFE) == 0)) {
translateException(
context, relaunchable, quiet, false, null);
// Operation failed
if (listener != null) {
listener.onFailed(relaunchable);
}
return;
}
//Create a yes/no dialog and ask the user
AlertDialog alert = DialogHelper.createYesNoDialog(
context,
R.string.confirm_operation,
relaunchable.getQuestionResourceId(),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (which == DialogInterface.BUTTON_POSITIVE) {
//Run the executable again
try {
//Prepare the system before re-launch the command
prepare(context, relaunchable);
//Re-execute the command
List<SyncResultExecutable> executables =
relaunchable.getExecutables();
int cc = executables.size();
for (int i = 0; i < cc; i++) {
SyncResultExecutable executable = executables.get(i);
Object result = CommandHelper.reexecute(
context, executable, null);
if (relaunchable.getTask() != null) {
relaunchable.getTask().execute(result);
}
}
// Operation complete
if (listener != null) {
listener.onSuccess();
}
} catch (Throwable ex) {
//Capture the exception, this time in quiet mode, if the
//exception is the same
boolean ask =
ex.getClass().getName().compareTo(
relaunchable.getClass().getName()) == 0;
translateException(
context, ex, quiet, !ask, listener);
// Operation failed
if (listener != null) {
listener.onFailed(ex);
}
}
} else {
// Operation cancelled
if (listener != null) {
listener.onCancelled();
}
}
}
});
DialogHelper.delegateDialogShow(context, alert);
}
/**
* Method that prepares the system for re-execute the command.
*
* @param context The current context
* @param relaunchable The {@link RelaunchableException} reference
* @hide
*/
static void prepare(final Context context, final RelaunchableException relaunchable) {
//- This exception need change the console before re-execute
if (relaunchable instanceof InsufficientPermissionsException) {
ConsoleBuilder.changeToPrivilegedConsole(context);
}
}
/**
* Method that prints the exception to an string
*
* @param cause The exception
* @return String The stack trace in an string
*/
public static String toStackTrace(Exception cause) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
try {
cause.printStackTrace(pw);
return sw.toString();
} finally {
try {
pw.close();
} catch (Exception e) {/**NON BLOCK**/}
}
}
}