aboutsummaryrefslogtreecommitdiffstats
path: root/src/com/cyanogenmod/filemanager/util/ExceptionUtil.java
blob: 7b7ce745f677001a4d1ee9b920d25a30533e39ce (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
/*
 * 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.AuthenticationFailedException;
import com.cyanogenmod.filemanager.console.CancelledOperationException;
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,
                                                AuthenticationFailedException.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,
                                                0
                                                     };
    private static final boolean[] KNOWN_EXCEPTIONS_TOAST = {
                                                            false,
                                                            false,
                                                            false,
                                                            false,
                                                            false,
                                                            true,
                                                            true,
                                                            false,
                                                            true,
                                                            true,
                                                            true,
                                                            false,
                                                            true
                                                            };

    /**
     * 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) {

        // Is cancellable?
        if (ex instanceof CancelledOperationException) {
            return;
        }

        //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 {
                        String msg = null;
                        if (fMsgResId > 0) {
                            msg = context.getString(fMsgResId);
                        } else {
                            msg = ex.getMessage();
                        }
                        if (fToast) {
                            DialogHelper.showToast(context, msg, Toast.LENGTH_SHORT);
                        } else {
                            AlertDialog dialog =
                                    DialogHelper.createErrorDialog(
                                            context, R.string.error_title, msg);
                            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
        final DialogInterface.OnClickListener clickListener =
                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);
                            AsyncTask<Object, Integer, Boolean> task = relaunchable.getTask();
                            if (task != null && task.getStatus() != AsyncTask.Status.RUNNING) {
                                task.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();
                    }
                }
            }
        };

        AlertDialog alert = DialogHelper.createYesNoDialog(
                    context,
                    R.string.confirm_operation,
                    relaunchable.getQuestionResourceId(),
                    clickListener);

        alert.setOnDismissListener(new DialogInterface.OnDismissListener() {
            @Override
            public void onDismiss(DialogInterface dialog) {
                // 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**/}
        }
    }
}