OSDN Git Service

CMFM: Fix relaunchables exceptions
[android-x86/packages-apps-CMFileManager.git] / src / com / cyanogenmod / filemanager / util / ExceptionUtil.java
1 /*
2  * Copyright (C) 2012 The CyanogenMod Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package com.cyanogenmod.filemanager.util;
18
19 import android.app.Activity;
20 import android.app.AlertDialog;
21 import android.content.ActivityNotFoundException;
22 import android.content.Context;
23 import android.content.DialogInterface;
24 import android.os.AsyncTask;
25 import android.util.Log;
26 import android.widget.Toast;
27
28 import com.cyanogenmod.filemanager.FileManagerApplication;
29 import com.cyanogenmod.filemanager.R;
30 import com.cyanogenmod.filemanager.commands.SyncResultExecutable;
31 import com.cyanogenmod.filemanager.commands.shell.InvalidCommandDefinitionException;
32 import com.cyanogenmod.filemanager.console.CommandNotFoundException;
33 import com.cyanogenmod.filemanager.console.ConsoleAllocException;
34 import com.cyanogenmod.filemanager.console.ConsoleBuilder;
35 import com.cyanogenmod.filemanager.console.ExecutionException;
36 import com.cyanogenmod.filemanager.console.InsufficientPermissionsException;
37 import com.cyanogenmod.filemanager.console.NoSuchFileOrDirectory;
38 import com.cyanogenmod.filemanager.console.OperationTimeoutException;
39 import com.cyanogenmod.filemanager.console.ReadOnlyFilesystemException;
40 import com.cyanogenmod.filemanager.console.RelaunchableException;
41 import com.cyanogenmod.filemanager.preferences.AccessMode;
42
43 import java.io.FileNotFoundException;
44 import java.io.IOException;
45 import java.io.PrintWriter;
46 import java.io.StringWriter;
47 import java.text.ParseException;
48 import java.util.List;
49
50 /**
51  * A helper class with useful methods for deal with exceptions.
52  */
53 public final class ExceptionUtil {
54
55     /**
56      * An interface to communicate events related with the result of a command relaunch.
57      */
58     public interface OnRelaunchCommandResult {
59         /**
60          * Method invoked when the relaunch operation was success
61          */
62         void onSuccess();
63
64         /**
65          * Method invoked when the relaunch operation was cancelled by the user
66          */
67         void onCancelled();
68
69         /**
70          * Method invoked when the relaunch operation was failed
71          *
72          * @param cause The cause of the failed operation
73          */
74         void onFailed(Throwable cause);
75     }
76
77     /**
78      * Constructor of <code>ExceptionUtil</code>.
79      */
80     private ExceptionUtil() {
81         super();
82     }
83
84     //Definition of known exceptions and his representation mode and resource identifiers
85     private static final Class<?>[] KNOWN_EXCEPTIONS = {
86                                                 FileNotFoundException.class,
87                                                 IOException.class,
88                                                 InvalidCommandDefinitionException.class,
89                                                 ConsoleAllocException.class,
90                                                 NoSuchFileOrDirectory.class,
91                                                 ReadOnlyFilesystemException.class,
92                                                 InsufficientPermissionsException.class,
93                                                 CommandNotFoundException.class,
94                                                 OperationTimeoutException.class,
95                                                 ExecutionException.class,
96                                                 ParseException.class,
97                                                 ActivityNotFoundException.class
98                                                       };
99     private static final int[] KNOWN_EXCEPTIONS_IDS = {
100                                                 R.string.msgs_file_not_found,
101                                                 R.string.msgs_io_failed,
102                                                 R.string.msgs_command_not_found,
103                                                 R.string.msgs_console_alloc_failure,
104                                                 R.string.msgs_file_not_found,
105                                                 R.string.msgs_read_only_filesystem,
106                                                 R.string.msgs_insufficient_permissions,
107                                                 R.string.msgs_command_not_found,
108                                                 R.string.msgs_operation_timeout,
109                                                 R.string.msgs_operation_failure,
110                                                 R.string.msgs_operation_failure,
111                                                 R.string.msgs_not_registered_app
112                                                      };
113     private static final boolean[] KNOWN_EXCEPTIONS_TOAST = {
114                                                             false,
115                                                             false,
116                                                             false,
117                                                             false,
118                                                             false,
119                                                             true,
120                                                             true,
121                                                             false,
122                                                             true,
123                                                             true,
124                                                             true,
125                                                             false
126                                                             };
127
128     /**
129      * Method that attach a asynchronous task for executing when exception need
130      * to be re-executed.
131      *
132      * @param ex The exception
133      * @param task The task
134      * @see RelaunchableException
135      */
136     public static void attachAsyncTask(Throwable ex, AsyncTask<Object, Integer, Boolean> task) {
137         if (ex instanceof RelaunchableException) {
138             ((RelaunchableException)ex).setTask(task);
139         }
140     }
141
142     /**
143      * Method that captures and translate an exception, showing a
144      * toast or a alert, according to the importance.
145      *
146      * @param context The current context
147      * @param ex The exception
148      */
149     public static synchronized void translateException(
150             final Context context, Throwable ex) {
151         translateException(context, ex, false, true);
152     }
153
154     /**
155      * Method that captures and translate an exception, showing a
156      * toast or a alert, according to the importance.
157      *
158      * @param context The current context.
159      * @param ex The exception
160      * @param quiet Don't show UI messages
161      * @param askUser Ask the user when if the exception could be relaunched with other privileged
162      */
163     public static synchronized void translateException(
164             final Context context, final Throwable ex,
165             final boolean quiet, final boolean askUser) {
166         translateException(context, ex, quiet, askUser, null);
167     }
168
169     /**
170      * Method that captures and translate an exception, showing a
171      * toast or a alert, according to the importance.
172      *
173      * @param context The current context.
174      * @param ex The exception
175      * @param quiet Don't show UI messages
176      * @param askUser Ask the user when if the exception could be relaunched with other privileged
177      * @param listener The listener where return the relaunch result
178      */
179     public static synchronized void translateException(
180             final Context context, final Throwable ex,
181             final boolean quiet, final boolean askUser,
182             final OnRelaunchCommandResult listener) {
183
184         //Get the appropriate message for the exception
185         int msgResId = R.string.msgs_unknown;
186         boolean toast = true;
187         int cc = KNOWN_EXCEPTIONS.length;
188         for (int i = 0; i < cc; i++) {
189             if (KNOWN_EXCEPTIONS[i].getCanonicalName().compareTo(
190                     ex.getClass().getCanonicalName()) == 0) {
191                 msgResId = KNOWN_EXCEPTIONS_IDS[i];
192                 toast = KNOWN_EXCEPTIONS_TOAST[i];
193                 break;
194             }
195         }
196
197         //Check exceptions that can be asked to user
198         if (ex instanceof RelaunchableException && askUser) {
199             ((Activity)context).runOnUiThread(new Runnable() {
200                 @Override
201                 public void run() {
202                     askUser(context, (RelaunchableException)ex, quiet, listener);
203                 }
204             });
205             return;
206         }
207
208         //Audit the exception
209         Log.e(context.getClass().getSimpleName(), "Error detected", ex); //$NON-NLS-1$
210
211         //Build the alert
212         final int fMsgResId = msgResId;
213         final boolean fToast = toast;
214         if (!quiet) {
215             ((Activity)context).runOnUiThread(new Runnable() {
216                 @Override
217                 public void run() {
218                     try {
219                         if (fToast) {
220                             DialogHelper.showToast(context, fMsgResId, Toast.LENGTH_SHORT);
221                         } else {
222                             AlertDialog dialog =
223                                     DialogHelper.createErrorDialog(
224                                             context, R.string.error_title, fMsgResId);
225                             DialogHelper.delegateDialogShow(context, dialog);
226                         }
227                     } catch (Exception e) {
228                         Log.e(context.getClass().getSimpleName(),
229                                 "ExceptionUtil. Failed to show dialog", ex); //$NON-NLS-1$
230                     }
231                 }
232             });
233         }
234     }
235
236     /**
237      * Method that ask the user for an operation and re-execution of the command.
238      *
239      * @param context The current context
240      * @param relaunchable The exception that contains the command that must be re-executed.
241      * @param listener The listener where return the relaunch result
242      * @hide
243      */
244     static void askUser(
245             final Context context,
246             final RelaunchableException relaunchable,
247             final boolean quiet,
248             final OnRelaunchCommandResult listener) {
249
250         //Is privileged?
251         boolean isPrivileged = false;
252         try {
253             isPrivileged = ConsoleBuilder.getConsole(context).isPrivileged();
254         } catch (Throwable ex) {
255             /**NON BLOCK**/
256         }
257
258         // If console is privileged there is not need to change
259         // If we are in a ChRooted environment, resolve the error without doing anymore
260         if (relaunchable instanceof InsufficientPermissionsException &&
261                 (isPrivileged ||
262                  FileManagerApplication.getAccessMode().compareTo(AccessMode.SAFE) == 0)) {
263             translateException(
264                     context, relaunchable, quiet, false, null);
265
266             // Operation failed
267             if (listener != null) {
268                 listener.onFailed(relaunchable);
269             }
270             return;
271         }
272
273         //Create a yes/no dialog and ask the user
274         AlertDialog alert = DialogHelper.createYesNoDialog(
275                     context,
276                     R.string.confirm_operation,
277                     relaunchable.getQuestionResourceId(),
278                     new DialogInterface.OnClickListener() {
279                         @Override
280                         public void onClick(DialogInterface dialog, int which) {
281                             if (which == DialogInterface.BUTTON_POSITIVE) {
282                                 //Run the executable again
283                                 try {
284                                     //Prepare the system before re-launch the command
285                                     prepare(context, relaunchable);
286
287                                     //Re-execute the command
288                                     List<SyncResultExecutable> executables =
289                                             relaunchable.getExecutables();
290                                     int cc = executables.size();
291                                     for (int i = 0; i < cc; i++) {
292                                         SyncResultExecutable executable = executables.get(i);
293                                         Object result = CommandHelper.reexecute(
294                                                 context, executable, null);
295                                         if (relaunchable.getTask() != null) {
296                                             relaunchable.getTask().execute(result);
297                                         }
298                                     }
299
300                                     // Operation complete
301                                     if (listener != null) {
302                                         listener.onSuccess();
303                                     }
304
305                                 } catch (Throwable ex) {
306                                     //Capture the exception, this time in quiet mode, if the
307                                     //exception is the same
308                                     boolean ask =
309                                             ex.getClass().getName().compareTo(
310                                                     relaunchable.getClass().getName()) == 0;
311                                     translateException(
312                                             context, ex, quiet, !ask, listener);
313
314                                     // Operation failed
315                                     if (listener != null) {
316                                         listener.onFailed(ex);
317                                     }
318                                 }
319                             } else {
320                                 // Operation cancelled
321                                 if (listener != null) {
322                                     listener.onCancelled();
323                                 }
324                             }
325                         }
326                     });
327         DialogHelper.delegateDialogShow(context, alert);
328     }
329
330     /**
331      * Method that prepares the system for re-execute the command.
332      *
333      * @param context The current context
334      * @param relaunchable The {@link RelaunchableException} reference
335      * @hide
336      */
337     static void prepare(final Context context, final RelaunchableException relaunchable) {
338         //- This exception need change the console before re-execute
339         if (relaunchable instanceof InsufficientPermissionsException) {
340             ConsoleBuilder.changeToPrivilegedConsole(context);
341         }
342     }
343
344     /**
345      * Method that prints the exception to an string
346      *
347      * @param cause The exception
348      * @return String The stack trace in an string
349      */
350     public static String toStackTrace(Exception cause) {
351         StringWriter sw = new StringWriter();
352         PrintWriter pw = new PrintWriter(sw);
353         try {
354             cause.printStackTrace(pw);
355             return sw.toString();
356         } finally {
357             try {
358                 pw.close();
359             } catch (Exception e) {/**NON BLOCK**/}
360         }
361     }
362 }