public class RequestFileActivity extends UIActivity {

    private static final int REQUEST_PICK_FILE = 203;

    @Override
    public int getContentViewId() {
        return 0;
    }

    @Override
    public View getMainView() {
        return null;
    }

    @Override
    public void create(boolean isNew) {

        PackageManager packageManager = getPackageManager();
        Intent test = new Intent(Intent.ACTION_GET_CONTENT);
        test.setType("application/zip*");
        List<ResolveInfo> list = packageManager.queryIntentActivities(test,
                PackageManager.GET_ACTIVITIES);
        if (list.size() > 0) {
            Intent intent = new Intent();
            intent.setType("application/zip");
            intent.setAction(Intent.ACTION_GET_CONTENT);
            startActivityForResult(intent, REQUEST_PICK_FILE);
        } else {
            Toast.makeText(this, R.string.error_file_manager, Toast.LENGTH_SHORT).show();
            finish();
        }
    }

    @Override
    public void restore(Bundle savedInstanceState) {
    }

    @Override
    public void save(Bundle outState) {
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == REQUEST_PICK_FILE) {
            if (data == null) {
                finish();
                return;
            }

            Uri uri = data.getData();

            String filePath = uri.getPath();
            StoragePlugin sPlugin = (StoragePlugin) getCore().getPlugin(Core.PLUGIN_STORAGE);

            filePath = Files.getPathFromUri(getCore(), uri);

            sPlugin.addFileItemToStore(filePath);

        }
        finish();
    }
} public interface Core extends Serializable {

    public static final String PLUGIN_UI = "UIPlugin";
    public static final String PLUGIN_SUPERUSER = "SuperUserPlugin";
    public static final String PLUGIN_RECOVERY = "RecoveryPlugin";
    public static final String PLUGIN_STORAGE = "StoragePlugin";
    public static final String PLUGIN_REBOOT = "RebootPlugin";
    public static final String PLUGIN_LICENSE = "LicensePlugin";
    public static final String PLUGIN_UPDATE = "UpdatePlugin";

    public interface CoreListener {

        public void pluginStarted(String name);

        public void pluginStopped(String name);

        public void coreMessage(int resId);

        public void coreStarted();

        public void coreStopped();
    }

    public void destroy();

    public boolean isStarted();

    public Context getContext();

    public void setContext(Context context);

    public Plugin getPlugin(String name);

    public Preferences getPreferences();

    public CloudStorage getCloudStorage();

    public void setCloudStorage(int cloudStorage);

    public ONandroid getONandroid();

    public Version getVersion();

    public void moveToInstall();
} public class RebootPlugin extends Plugin {

    private int mSelectedBackup;
    private int mIsSystemApp = -1;

    public RebootPlugin(Core core) {
        super(core, Core.PLUGIN_REBOOT);
    }

    @Override
    public void start() {
        started();
    }

    @Override
    public void stop() {
        stopped();
    }

    public void checkFilesAndMd5() {

        Preferences prefs = getCore().getPreferences();
        final boolean checkExists = prefs.isCheckExists();
        final boolean checkMd5 = prefs.isCheckMD5();

        if (!checkExists && !checkMd5) {
            showRebootDialog();
            return;
        }

        final Context context = getCore().getContext();

        final ProgressDialog pDialog = new ProgressDialog(context);
        pDialog.setIndeterminate(true);
        pDialog.setMessage(context.getResources().getString(R.string.alert_file_checking));
        pDialog.setCancelable(false);
        pDialog.setCanceledOnTouchOutside(false);
        pDialog.show();

        (new Thread() {

            public void run() {

                int size = FileItemStore.size();
                for (int i = 0; i < size; i++) {
                    FileItem item = FileItemStore.getItem(i);
                    String path = item.getPath();
                    final File file = new File(path);

                    ((Activity) context).runOnUiThread(new Runnable() {

                        public void run() {
                            pDialog.setMessage(context.getResources().getString(
                                    R.string.alert_file_exists_checking,
                                    new Object[] { file.getName() }));
                        }
                    });

                    if (checkExists && !file.exists()) {
                        pDialog.dismiss();
                        showAlertOnUIThread(R.string.alert_file_alert,
                                R.string.alert_file_not_exists, new Object[] { file.getName() });
                        return;
                    }

                    if (checkMd5) {

                        ((Activity) context).runOnUiThread(new Runnable() {

                            public void run() {
                                pDialog.setMessage(context.getResources().getString(
                                        R.string.alert_file_md5_checking,
                                        new Object[] { file.getName() }));
                            }
                        });

                        File folder = file.getParentFile();
                        File md5File = new File(folder, file.getName() + ".md5sum");
                        if (md5File.exists()) {
                            String content[] = Files.readMd5File(md5File);
                            if (!file.getName().equals(content[1])) {
                                pDialog.dismiss();
                                showAlertOnUIThread(R.string.alert_file_alert,
                                        R.string.alert_file_incorrect_md5_file,
                                        new Object[] { file.getName() });
                                return;
                            }
                            String md5 = Files.md5(file);
                            if (!md5.equals(content[0])) {
                                pDialog.dismiss();
                                showAlertOnUIThread(R.string.alert_file_alert,
                                        R.string.alert_file_incorrect_md5,
                                        new Object[] { file.getName() });
                                return;
                            }
                        }
                    }
                }

                pDialog.dismiss();
                ((Activity) context).runOnUiThread(new Runnable() {

                    public void run() {
                        showRebootDialog();
                    }
                });
            }
        }).start();
    }

    public void showBackupDialog() {
        showBackupDialog(true, false, false, false, false, true);
    }

    public void showRestoreDialog() {
        Context context = getCore().getContext();

        AlertDialog.Builder alert = new AlertDialog.Builder(context);
        alert.setTitle(R.string.alert_restore_title);

        RecoveryPlugin rPlugin = (RecoveryPlugin) getCore().getPlugin(Core.PLUGIN_RECOVERY);

        final String backupFolder = rPlugin.getBackupDir(false);
        final String[] backups = rPlugin.getBackupList();
        mSelectedBackup = backups.length > 0 ? 0 : -1;

        alert.setSingleChoiceItems(backups, mSelectedBackup, new DialogInterface.OnClickListener() {

            public void onClick(DialogInterface dialog, int which) {
                mSelectedBackup = which;
            }
        });

        alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {

            public void onClick(DialogInterface dialog, int whichButton) {
                dialog.dismiss();

                if (mSelectedBackup >= 0) {
                    reboot(false, false, false, false, null, null, backupFolder
                            + backups[mSelectedBackup]);
                }
            }
        });

        alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {

            public void onClick(DialogInterface dialog, int which) {
                dialog.dismiss();
            }
        });
        alert.show();

    }

    public void showRestoreDialog(final String path) {
        Context context = getCore().getContext();

        AlertDialog.Builder alert = new AlertDialog.Builder(context);
        alert.setTitle(R.string.alert_restore_title);
        alert.setMessage(context.getResources().getString(R.string.alert_restore_message, path));

        RecoveryPlugin rPlugin = (RecoveryPlugin) getCore().getPlugin(Core.PLUGIN_RECOVERY);

        final String backupFolder = rPlugin.getBackupDir(false);

        alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {

            public void onClick(DialogInterface dialog, int whichButton) {
                dialog.dismiss();

                reboot(false, false, false, false, null, null, backupFolder + path);
            }
        });

        alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {

            public void onClick(DialogInterface dialog, int which) {
                dialog.dismiss();
            }
        });
        alert.show();

    }

    public void restore(String path) {
        reboot(false, false, false, false, null, null, path);
    }

    public void simpleReboot() {
        reboot(false, false, false, false, null, null, null, true);
    }

    public void simpleReboot(boolean wipeData, boolean wipeCaches, boolean fixPermissions) {
        FileItemStore.removeItems();
        reboot(false, wipeData, wipeCaches, fixPermissions, null, null, null, false);
    }

    public void fixPermissions() {
        FileItemStore.removeItems();
        reboot(false, false, false, true, null, null, null, false);
    }

    private void showBackupDialog(final boolean removePreferences,
            final boolean wipeSystem, final boolean wipeData, final boolean wipeCaches,
            final boolean fixPermissions, final boolean onlyBackup) {
        final Context context = getCore().getContext();

        Preferences prefs = getCore().getPreferences();
        StoragePlugin sPlugin = (StoragePlugin) getCore().getPlugin(Core.PLUGIN_STORAGE);
        double checkSpace = prefs.getSpaceLeft();
        if (checkSpace > 0) {
            double spaceLeft = sPlugin.getSpaceLeft();
            if (spaceLeft < checkSpace) {
                AlertDialog.Builder alert = new AlertDialog.Builder(context);
                alert.setTitle(R.string.alert_backup_space_title);
                alert.setMessage(context.getResources().getString(
                        R.string.alert_backup_space_message, checkSpace));

                alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {

                    public void onClick(DialogInterface dialog, int whichButton) {
                        dialog.dismiss();

                        reallyShowBackupDialog(removePreferences, wipeSystem, wipeData,
                                wipeCaches, fixPermissions, onlyBackup);
                    }
                });

                alert.setNegativeButton(android.R.string.cancel,
                        new DialogInterface.OnClickListener() {

                            public void onClick(DialogInterface dialog, int which) {
                                dialog.dismiss();
                            }
                        });
                alert.show();
            } else {
                reallyShowBackupDialog(removePreferences, wipeSystem, wipeData,
                        wipeCaches, fixPermissions, onlyBackup);
            }
        } else {
            reallyShowBackupDialog(removePreferences, wipeSystem, wipeData, wipeCaches,
                    fixPermissions, onlyBackup);
        }
    }

    private void reallyShowBackupDialog(boolean removePreferences,
            final boolean wipeSystem, final boolean wipeData, final boolean wipeCaches,
            final boolean fixPermissions, final boolean onlyBackup) {
        Context context = getCore().getContext();

        if (removePreferences) {
            FileItemStore.removeItems();
        }

        final boolean isONandroid = onlyBackup && getCore().getPreferences().isUseONandroid();

        AlertDialog.Builder alert = new AlertDialog.Builder(context);
        alert.setTitle(R.string.alert_backup_title);
        View view = LayoutInflater.from(context).inflate(R.layout.dialog_backup,
                (ViewGroup) ((Activity) context).findViewById(R.id.backup_dialog_layout));
        alert.setView(view);

        if (isONandroid) {
            ((TextView) view.findViewById(R.id.text)).setText(R.string.alert_backup_message_onandroid);
        }

        final CheckBox cbSystem = (CheckBox) view.findViewById(R.id.system);
        final CheckBox cbData = (CheckBox) view.findViewById(R.id.data);
        final CheckBox cbCache = (CheckBox) view.findViewById(R.id.cache);
        final CheckBox cbRecovery = (CheckBox) view.findViewById(R.id.recovery);
        final CheckBox cbBoot = (CheckBox) view.findViewById(R.id.boot);
        final CheckBox cbSecure = (CheckBox) view.findViewById(R.id.androidsecure);
        final CheckBox cbSdext = (CheckBox) view.findViewById(R.id.sdext);
        final EditText input = (EditText) view.findViewById(R.id.backupname);

        input.setText(Strings.getDateAndTime());
        input.selectAll();

        final RecoveryPlugin rPlugin = (RecoveryPlugin) getCore().getPlugin(Core.PLUGIN_RECOVERY);
        if (rPlugin.getRecovery().getId() == RecoveryInfo.RECOVERY_TWRP) {
            if (!Files.hasAndroidSecure()) {
                cbSecure.setVisibility(View.GONE);
            }
            if (!Files.hasSdExt()) {
                cbSdext.setVisibility(View.GONE);
            }
        } else {
            cbSystem.setVisibility(View.GONE);
            cbData.setVisibility(View.GONE);
            cbCache.setVisibility(View.GONE);
            cbRecovery.setVisibility(View.GONE);
            cbBoot.setVisibility(View.GONE);
            cbSecure.setVisibility(View.GONE);
            cbSdext.setVisibility(View.GONE);
        }

        alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {

            public void onClick(DialogInterface dialog, int whichButton) {
                dialog.dismiss();

                String text = input.getText().toString();
                text = text.replaceAll("[^a-zA-Z0-9.-]", "");

                String backupOptions = null;
                if (rPlugin.getRecovery().getId() == RecoveryInfo.RECOVERY_TWRP) {
                    backupOptions = "";
                    if (cbSystem.isChecked()) {
                        backupOptions += "S";
                    }
                    if (cbData.isChecked()) {
                        backupOptions += "D";
                    }
                    if (cbCache.isChecked()) {
                        backupOptions += "C";
                    }
                    if (cbRecovery.isChecked()) {
                        backupOptions += "R";
                    }
                    if (cbBoot.isChecked()) {
                        backupOptions += "B";
                    }
                    if (cbSecure.isChecked()) {
                        backupOptions += "A";
                    }
                    if (cbSdext.isChecked()) {
                        backupOptions += "E";
                    }

                    if ("".equals(backupOptions)) {
                        return;
                    }
                }

                if (isONandroid) {
                    getCore().getONandroid().doBackup(text, backupOptions);
                } else {
                    reboot(wipeSystem, wipeData, wipeCaches, fixPermissions, text,
                            backupOptions, null);
                }
            }
        });

        alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {

            public void onClick(DialogInterface dialog, int which) {
                dialog.dismiss();
            }
        });
        alert.show();
    }

    protected void showRebootDialog() {

        if (FileItemStore.size() == 0) {
            return;
        }

        final Context context = getCore().getContext();

        AlertDialog.Builder alert = new AlertDialog.Builder(context);
        alert.setTitle(R.string.alert_reboot_title);

        final InstallOptionsCursor cursor = new InstallOptionsCursor(getCore());

        alert.setMultiChoiceItems(cursor, cursor.getIsCheckedColumn(), cursor.getLabelColumn(),
                new DialogInterface.OnMultiChoiceClickListener() {

                    @Override
                    public void onClick(DialogInterface dialog, int which, boolean isChecked) {
                        cursor.setOption(which, isChecked);
                    }

                });

        alert.setPositiveButton(R.string.alert_reboot_now, new DialogInterface.OnClickListener() {

            public void onClick(DialogInterface dialog, int whichButton) {
                dialog.dismiss();

                if (cursor.isBackup()) {
                    showBackupDialog(false, cursor.isWipeSystem(), cursor.isWipeData(),
                            cursor.isWipeCaches(), cursor.isFixPermissions(), false);
                } else {
                    reboot(cursor.isWipeSystem(), cursor.isWipeData(), cursor.isWipeCaches(),
                            cursor.isFixPermissions(), null, null, null);
                }

            }
        });

        alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {

            public void onClick(DialogInterface dialog, int which) {
                dialog.dismiss();
            }
        });
        alert.show();
    }

    private void reboot(boolean wipeSystem, boolean wipeData, boolean wipeCaches,
            boolean fixPermissions, String backupFolder,
            String backupOptions, String restore) {
        reboot(wipeSystem, wipeData, wipeCaches, fixPermissions, backupFolder,
                backupOptions, restore, false);
    }

    private void reboot(final boolean wipeSystem, final boolean wipeData, final boolean wipeCaches,
            final boolean fixPermissions, final String backupFolder,
            final String backupOptions, final String restore, final boolean skipCommands) {

        if (wipeSystem && getCore().getPreferences().isShowSystemWipeAlert()) {
            AlertDialog.Builder alert = new AlertDialog.Builder(getCore().getContext());
            alert.setTitle(R.string.alert_wipe_system_title);
            alert.setMessage(R.string.alert_wipe_system_message);

            alert.setPositiveButton(R.string.alert_reboot_now,
                    new DialogInterface.OnClickListener() {

                        public void onClick(DialogInterface dialog, int whichButton) {
                            dialog.dismiss();

                            _reboot(wipeSystem, wipeData, wipeCaches, fixPermissions,
                                    backupFolder, backupOptions, restore, skipCommands);

                        }
                    });

            alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {

                public void onClick(DialogInterface dialog, int which) {
                    dialog.dismiss();
                }
            });
            alert.show();
        } else {
            _reboot(wipeSystem, wipeData, wipeCaches, fixPermissions, backupFolder,
                    backupOptions, restore, skipCommands);
        }

    }

    private void _reboot(boolean wipeSystem, boolean wipeData, boolean wipeCaches,
            boolean fixPermissions, String backupFolder, String backupOptions,
            String restore, boolean skipCommands) {
        try {

            if (fixPermissions) {
                fixPermissions = prepareFixPermissions();
            }

            RecoveryPlugin rPlugin = (RecoveryPlugin) getCore().getPlugin(Core.PLUGIN_RECOVERY);

            boolean isCwmOfficial = rPlugin.getRecovery().getId() == RecoveryInfo.RECOVERY_CWM;

            Process p = null;
            DataOutputStream os = null;

            if (!isCwmOfficial) {
                p = Runtime.getRuntime().exec("su");
                os = new DataOutputStream(p.getOutputStream());

                os.writeBytes("rm -f /cache/recovery/command\n");
                os.writeBytes("rm -f /cache/recovery/extendedcommand\n");
                os.writeBytes("rm -f /cache/recovery/openrecoveryscript\n");
            }

            if (!skipCommands) {

                Preferences prefs = getCore().getPreferences();
                prefs.setToDelete(new String[0]);
                int size = FileItemStore.size();
                List<String> list = new ArrayList<String>();
                for (int i = 0; i < size; i++) {
                    FileItem item = FileItemStore.getItem(i);
                    if (item.isDelete()) {
                        list.add(item.getPath());
                    }
                }
                prefs.setToDelete(
                        list.toArray(new String[list.size()]));

                String file = rPlugin.getCommandsFile();

                String[] commands = rPlugin.getCommands(wipeSystem, wipeData, wipeCaches,
                        fixPermissions, backupFolder, backupOptions, restore);
                if (commands != null) {
                    size = commands.length;
                    for (int i = 0; i < size; i++) {
                        os.writeBytes("echo '" + commands[i] + "' >> /cache/recovery/" + file
                                + "\n");
                    }
                }
            }

            if (!isCwmOfficial) {
                os.writeBytes("/system/bin/touch /cache/recovery/boot\n");
                os.writeBytes("reboot recovery\n");
    
                os.writeBytes("sync\n");
                os.writeBytes("exit\n");
                os.flush();
                p.waitFor();

                Context context = getCore().getContext();

                if (isSystemApp()) {
                    ((PowerManager) context.getSystemService(Activity.POWER_SERVICE))
                            .reboot("recovery");
                } else {
                    Runtime.getRuntime().exec("/system/bin/reboot recovery");
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private boolean prepareFixPermissions() {

        String data = Files.readAssets(getCore().getContext(), "fix_permissions.sh");

        if (data != null
                && Files.writeToFile(data, "/data/data/com.beerbong.zipinst/files/",
                        "fix_permissions.sh")) {

            SuperUserPlugin sPlugin = (SuperUserPlugin) getCore().getPlugin(Core.PLUGIN_SUPERUSER);
            sPlugin.run(
                            "cp /data/data/com.beerbong.zipinst/files/fix_permissions.sh /cache/fix_permissions.sh");

            return true;
        }
        return false;
    }

    private boolean isSystemApp() throws Exception {
        if (mIsSystemApp > -1) {
            return mIsSystemApp == 1;
        }
        Context context = getCore().getContext();
        PackageManager pm = context.getPackageManager();
        PackageInfo info = pm.getPackageInfo("com.beerbong.zipinst", PackageManager.GET_ACTIVITIES);
        ApplicationInfo aInfo = info.applicationInfo;
        String path = aInfo.sourceDir.substring(0, aInfo.sourceDir.lastIndexOf("/"));
        mIsSystemApp = path.contains("system/app") ? 1 : 0;
        return mIsSystemApp == 1;
    }

    private void showAlertOnUIThread(final int titleId, final int messageId,
            final Object[] messageParams) {

        final Activity activity = (Activity) getCore().getContext();

        activity.runOnUiThread(new Runnable() {

            public void run() {
                AlertDialog.Builder alert = new AlertDialog.Builder(activity);
                alert.setTitle(titleId);
                alert.setMessage(activity.getResources().getString(messageId, messageParams));
                alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {

                    public void onClick(DialogInterface dialog, int whichButton) {
                        dialog.dismiss();
                    }
                });
                alert.show();
            }
        });
    }

} public class RecoveryPlugin extends Plugin {

    private SparseArray<RecoveryInfo> mRecoveries = new SparseArray<RecoveryInfo>();
    private int mInstalledRecovery = -1;
    private String mBootBlock;
    private String mRecoveryBlock;

    public RecoveryPlugin(Core core) {
        super (core, Core.PLUGIN_RECOVERY);
    }

    @Override
    public void start() {
        ((CoreImpl) getCore()).setMessage(R.string.reading_recovery_info);

        (new AsyncTask<Void, Void, Void>() {

            @Override
            protected Void doInBackground(Void... params) {

                Core core = getCore();

                mRecoveries.put(RecoveryInfo.RECOVERY_CWM_BASED, new CwmBasedRecovery(core));
                mRecoveries.put(RecoveryInfo.RECOVERY_CWM, new CwmRecovery(core));
                mRecoveries.put(RecoveryInfo.RECOVERY_TWRP, new TwrpRecovery(core));
                mRecoveries.put(RecoveryInfo.RECOVERY_4EXT, new FourExtRecovery(core));

                SuperUserPlugin suPlugin = (SuperUserPlugin) core.getPlugin(Core.PLUGIN_SUPERUSER);
                suPlugin.run("chmod 777 /cache/recovery/");
                suPlugin.run("chmod 777 /cache/recovery/last_log");

                Preferences prefs = getCore().getPreferences();
                mBootBlock = prefs.getBootBlock();
                mRecoveryBlock = prefs.getRecoveryBlock();

                mInstalledRecovery = testLastLog(new File("/cache/recovery/last_log"), suPlugin, true);

                suPlugin.run("chmod -R 777 /data/media/clockworkmod/");
                suPlugin.run("chmod -R 777 /data/media/clockworkmod/backup/");
                String path = mRecoveries.get(RecoveryInfo.RECOVERY_TWRP).getBackupFolder("sdcard", true,
                        false);
                File file = new File(path);
                suPlugin.run("chmod -R 777 " + file.getAbsolutePath());
                if (file.getParentFile() != null) {
                    suPlugin.run("chmod -R 777 " + file.getParentFile().getAbsolutePath());
                }

                if (mRecoveryBlock != null) {
                    prefs.setRecoveryBlock(mRecoveryBlock);
                }
                if (mBootBlock != null) {
                    prefs.setBootBlock(mBootBlock);
                }

                return (Void) null;
            }

            @Override
            protected void onPostExecute(Void result) {

                Core core = getCore();
                final Preferences prefs = core.getPreferences();
                final Context context = core.getContext();

                if (!prefs.existsRecovery()) {
                    if (mInstalledRecovery != -1) {
                        setRecovery(mInstalledRecovery);
                        started();
                    } else {
                        test(mRecoveries.get(RecoveryInfo.RECOVERY_4EXT), true);
                    }
                } else {
                    if (prefs.isAlertOnChangeRecovery()) {
                        if (mInstalledRecovery != -1 && mInstalledRecovery != getRecovery().getId()) {
                            AlertDialog.Builder alert = new AlertDialog.Builder(context);
                            alert.setTitle(R.string.alert_changed_recovery_title);
                            View view = LayoutInflater.from(context).inflate(R.layout.dialog_changed_recovery,
                                    (ViewGroup) ((Activity) context).findViewById(R.id.changed_recovery_dialog_layout));
                            alert.setView(view);

                            TextView text = (TextView) view.findViewById(R.id.text);
                            text.setText(context.getResources().getString(
                                    R.string.alert_changed_recovery_message,
                                    getRecovery().getFullName(context),
                                    mRecoveries.get(mInstalledRecovery).getFullName(context)));

                            final CheckBox cbDontShow = (CheckBox) view.findViewById(R.id.dontshow);
                            cbDontShow.setChecked(!prefs.isAlertOnChangeRecovery());

                            alert.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {

                                public void onClick(DialogInterface dialog, int whichButton) {
                                    dialog.dismiss();

                                    setRecovery(mInstalledRecovery);
                                    started();

                                    prefs.setAlertOnChangeRecovery(!cbDontShow.isChecked());
                                }
                            });

                            alert.setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {

                                public void onClick(DialogInterface dialog, int whichButton) {
                                    dialog.dismiss();

                                    started();

                                    prefs.setAlertOnChangeRecovery(cbDontShow.isChecked());
                                }
                            });

                            alert.show();
                        } else {
                            started();
                        }
                    } else {
                        started();
                    }
                }

            }
        }).execute((Void) null);

    }

    @Override
    public void stop() {
        stopped();
    }

    public boolean hasBootBlock() {
        return mBootBlock != null && !"".equals(mBootBlock);
    }

    public boolean hasRecoveryBlock() {
        return mRecoveryBlock != null && !"".equals(mRecoveryBlock);
    }

    public RecoveryInfo getRecovery() {
        String recovery = getCore().getPreferences().getRecovery();
        for (int i = 0; i < mRecoveries.size(); i++) {
            int key = mRecoveries.keyAt(i);
            RecoveryInfo info = mRecoveries.get(key);
            if (info.getName().equals(recovery)) {
                return info;
            }
        }
        return null;
    }

    public void setRecovery(int id) {
        RecoveryInfo info = mRecoveries.get(id);
        Preferences prefs = getCore().getPreferences();
        prefs.setRecovery(info.getName());
        prefs.setInternalStorage(info.getInternalSdcard());
        prefs.setExternalStorage(info.getExternalSdcard());
    }

    public String getBackupDir(boolean force) {

        RecoveryInfo info = getRecovery();

        Preferences prefs = getCore().getPreferences();
        StoragePlugin sPlugin = (StoragePlugin) getCore().getPlugin(Core.PLUGIN_STORAGE);

        boolean external = sPlugin.hasExternalStorage()
                && prefs.isBackupExternalStorage();
        String sdcard = external ? sPlugin.getExternalStoragePath() : "sdcard";

        return info.getBackupFolder(sdcard, force, external);
    }

    public String[] getBackupList() {

        RecoveryInfo info = getRecovery();

        Preferences prefs = getCore().getPreferences();
        StoragePlugin sPlugin = (StoragePlugin) getCore().getPlugin(Core.PLUGIN_STORAGE);

        boolean external = sPlugin.hasExternalStorage()
                && prefs.isBackupExternalStorage();
        String sdcard = external ? sPlugin.getExternalStoragePath() : "sdcard";

        String folder = info.getBackupFolder(sdcard, true, external);

        List<String> list = new ArrayList<String>();

        if (folder != null && !"".equals(folder)) {
            File f = new File(folder);
            if (f.exists()) {
                File[] fs = f.listFiles();
                for (int i = 0; i < fs.length; i++) {
                    if (fs[i].isDirectory()) {
                        list.add(fs[i].getName());
                    }
                }
            }
        }

        Collections.sort(list, new Comparator<String>() {

            @Override
            public int compare(String s1, String s2) {
                int value = s1.compareTo(s2);
                return -value;
            }
        });

        return list.toArray(new String[list.size()]);
    }

    public String getCommandsFile() {

        RecoveryInfo info = getRecovery();

        return info.getCommandsFile();
    }

    public String[] getCommands(boolean wipeSystem, boolean wipeData, boolean wipeCaches,
            boolean fixPermissions, String backupFolder, String backupOptions, String restore)
            throws Exception {
        List<String> commands = new ArrayList<String>();

        RecoveryInfo info = getRecovery();

        Preferences prefs = getCore().getPreferences();
        String internalStorage = prefs.getInternalStorage();
        while (internalStorage.startsWith("/")) {
            internalStorage = internalStorage.substring(1);
        }

        String externalStorage = prefs.getExternalStorage();
        while (externalStorage.startsWith("/")) {
            externalStorage = externalStorage.substring(1);
        }

        StoragePlugin sPlugin = (StoragePlugin) getCore().getPlugin(Core.PLUGIN_STORAGE);
        boolean external = sPlugin.hasExternalStorage()
                && prefs.isBackupExternalStorage();
        String storage = external ? externalStorage : internalStorage;

        commands.addAll(info.getCommands(storage, external, wipeSystem, wipeData, wipeCaches,
                fixPermissions, backupFolder, backupOptions, restore));

        return commands.toArray(new String[commands.size()]);
    }

    private int testLastLog(File file, SuperUserPlugin suPlugin, boolean retry) {
        int retValue = -1;
        try {
            Scanner scanner = null;
            try {
                scanner = new Scanner(file);
                int lineNumber = 0;
                while ((retValue == -1 || mBootBlock == null || mRecoveryBlock == null) && scanner.hasNext() && lineNumber < 1000) {
                    String line = scanner.nextLine();
                    if (line != null) {
                        if (retValue == -1) {
                            if (line.indexOf("CWM-based Recovery") >= 0) {
                                retValue = RecoveryInfo.RECOVERY_CWM_BASED;
                            } else if (line.indexOf("ClockworkMod Recovery") >= 0) {
                                retValue = RecoveryInfo.RECOVERY_CWM;
                            } else if (line.indexOf("TWRP") >= 0) {
                                retValue = RecoveryInfo.RECOVERY_TWRP;
                            } else if (line.indexOf("4EXT") >= 0) {
                                retValue = RecoveryInfo.RECOVERY_4EXT;
                            }
                        }
                        if (mBootBlock == null) {
                            if (line.indexOf("/boot") >= 0 && line.indexOf("/dev/block/") >= 0) {
                                mBootBlock = line.substring(line.indexOf("/dev/block"));
                                int index = -1;
                                if ((index = mBootBlock.indexOf(" ")) > 0) {
                                    mBootBlock = mBootBlock.substring(0, index);
                                }
                            }
                        }
                        if (mRecoveryBlock == null) {
                            if (line.indexOf("/recovery") >= 0 && line.indexOf("/dev/block/") >= 0) {
                                mRecoveryBlock = line.substring(line.indexOf("/dev/block"));
                                int index = -1;
                                if ((index = mRecoveryBlock.indexOf(" ")) > 0) {
                                    mRecoveryBlock = mRecoveryBlock.substring(0, index);
                                }
                            }
                        }
                    }
                    lineNumber++;
                }
                if (mRecoveryBlock == null) {
                    mRecoveryBlock = "";
                }
                if (mBootBlock == null) {
                    mBootBlock = "";
                }
            } finally {
                if (scanner != null) {
                    scanner.close();
                }
            }
        } catch (FileNotFoundException ex) {
            ex.printStackTrace();
            if (retry) {
                suPlugin.run("cp " + file.getAbsolutePath() + " /sdcard/last_log");
                return testLastLog(new File("/sdcard/last_log"), suPlugin, false);
            }
        }
        return retValue;
    }

    private void test(final RecoveryInfo info, boolean first) {

        final Context context = getCore().getContext();
        final String recoveryName = info.getFullName(context);

        if (first) {
            int recNumber = 0;
            File folder = new File(mRecoveries.get(RecoveryInfo.RECOVERY_CWM_BASED).getFolderPath());
            if (folder.exists()) {
                recNumber++;
            }
            folder = new File(mRecoveries.get(RecoveryInfo.RECOVERY_TWRP).getFolderPath());
            if (folder.exists()) {
                recNumber++;
            }
            folder = new File(mRecoveries.get(RecoveryInfo.RECOVERY_4EXT).getFolderPath());
            if (folder.exists()) {
                recNumber++;
            }
            if (recNumber != 1) {
                if (mInstalledRecovery == -1) {
                    selectRecovery(context);
                    return;
                } else {
                    setRecovery(mInstalledRecovery);
                    Toast.makeText(context,
                            context.getString(R.string.recovery_changed, recoveryName),
                            Toast.LENGTH_LONG).show();
                    started();
                    return;
                }
            }
        }

        if (info.getId() == RecoveryInfo.RECOVERY_CWM_BASED) {
            if (mInstalledRecovery == -1) {
                selectRecovery(context);
            } else {
                setRecovery(mInstalledRecovery);
                Toast.makeText(context,
                        context.getString(R.string.recovery_changed, recoveryName),
                        Toast.LENGTH_LONG).show();
                started();
            }
            return;
        }

        File folder = new File(info.getFolderPath());
        if (folder.exists()) {
            AlertDialog.Builder alert = new AlertDialog.Builder(context);
            alert.setCancelable(false);
            alert.setTitle(R.string.recovery_change_alert_title);
            alert.setMessage(context.getString(R.string.recovery_change_alert_message,
                    recoveryName));
            alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {

                public void onClick(DialogInterface dialog, int whichButton) {
                    dialog.dismiss();
                    setRecovery(info.getId());
                    Toast.makeText(context,
                            context.getString(R.string.recovery_changed, recoveryName),
                            Toast.LENGTH_LONG).show();
                    started();
                }
            });
            alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {

                public void onClick(DialogInterface dialog, int which) {
                    dialog.dismiss();

                    selectRecovery(context);
                }
            });
            alert.show();
        } else {
            switch (info.getId()) {
                case RecoveryInfo.RECOVERY_4EXT:
                    test(mRecoveries.get(RecoveryInfo.RECOVERY_TWRP), false);
                    break;
                case RecoveryInfo.RECOVERY_TWRP:
                    test(mRecoveries.get(RecoveryInfo.RECOVERY_CWM_BASED), false);
                    break;
            }
        }
    }

    private void selectRecovery(Context context) {
        View view = LayoutInflater.from(context).inflate(R.layout.dialog_recovery, null);

        final RadioGroup mGroup = (RadioGroup) view.findViewById(R.id.recovery_radio_group);

        new AlertDialog.Builder(context).setCancelable(false).setTitle(R.string.recovery_alert_title)
                .setMessage(R.string.recovery_alert_summary).setView(view)
                .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {

                    public void onClick(DialogInterface dialog, int whichButton) {

                        int id = mGroup.getCheckedRadioButtonId();
                        int recovery = -1;
                        switch (id) {
                            case R.id.cwm :
                                recovery = RecoveryInfo.RECOVERY_CWM;
                                break;
                            case R.id.cwmbased :
                                recovery = RecoveryInfo.RECOVERY_CWM_BASED;
                                break;
                            case R.id.twrp :
                                recovery = RecoveryInfo.RECOVERY_TWRP;
                                break;
                            case R.id.fourext :
                                recovery = RecoveryInfo.RECOVERY_4EXT;
                                break;
                        }

                        setRecovery(recovery);
                        started();

                        dialog.dismiss();
                    }
                }).show();
    }

    public void installBoot(String path) {
        if (mBootBlock == null) {
            return;
        }
        String command = "dd if=" + path + " of=" + mBootBlock;
        SuperUserPlugin suPlugin = (SuperUserPlugin) getCore().getPlugin(Core.PLUGIN_SUPERUSER);
        suPlugin.run(command);
    }

    public void installRecovery(String path) {
        if (mRecoveryBlock == null) {
            return;
        }
        String command = "dd if=" + path + " of=" + mRecoveryBlock;
        SuperUserPlugin suPlugin = (SuperUserPlugin) getCore().getPlugin(Core.PLUGIN_SUPERUSER);
        suPlugin.run(command);
    }

} public class StoragePlugin extends Plugin {

    public static final String ROOT_ID_PRIMARY_EMULATED = "primary";

    private static boolean sScanned = false;

    private String mInternalStoragePath;
    private String mExternalStoragePath;

    public StoragePlugin(Core core) {
        super(core, Core.PLUGIN_STORAGE);
    }

    @Override
    public void start() {
        ((CoreImpl) getCore()).setMessage(R.string.reading_storage_paths);

        (new AsyncTask<Void, Void, Void>() {

            @Override
            protected Void doInBackground(Void... params) {

                if (!sScanned) {
                    sScanned = true;
                    readMounts();
                    mExternalStoragePath = externalStorage();
                }

                return (Void) null;
            }

            @Override
            protected void onPostExecute(Void result) {
                started();
            }
        }).execute((Void) null);
    }

    @Override
    public void stop() {
        stopped();
    }

    public boolean hasExternalStorage() {
        return mExternalStoragePath != null;
//        return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());
    }

    public boolean isExternalStorage(String path) {
        if (mInternalStoragePath == null) {
            mInternalStoragePath = "/sdcard";
        }
        return !path.startsWith(mInternalStoragePath) && !path.startsWith("/sdcard")
                && !path.startsWith("/mnt/sdcard");
    }

    public String getExternalStoragePath() {
        return mExternalStoragePath;
    }

    public String getInternalStoragePath() {
        return mInternalStoragePath;
    }

    public String getPath(String path) {
        String filePath = new String(path);

        Preferences prefs = getCore().getPreferences();
        String internalStorage = prefs.getInternalStorage();
        String externalStorage = prefs.getExternalStorage();

        String[] internalNames = new String[] {
                mInternalStoragePath,
                "/mnt/sdcard",
                "/storage/sdcard/",
                "/sdcard",
                "/storage/sdcard0",
                "/storage/emulated/0" };
        String[] externalNames = new String[] {
                mExternalStoragePath == null ? " " : mExternalStoragePath,
                "/mnt/extSdCard",
                "/storage/extSdCard/",
                "/extSdCard",
                "/storage/sdcard1",
                "/storage/emulated/1" };
        for (int i = 0; i < internalNames.length; i++) {
            String internalName = internalNames[i];
            String externalName = externalNames[i];
            boolean external = isExternalStorage(filePath);
            if (filePath.endsWith(".sh")) {
                if (!external) {
                    if (filePath.startsWith(internalName)) {
                        if (internalName.endsWith("/")) {
                            filePath = filePath.replace(internalName, "/" + "sdcard" + "/");
                        } else {
                            filePath = filePath.replace(internalName, "/" + "sdcard");
                        }
                        break;
                    }
                }
            } else {
                if (filePath.startsWith(externalName)) {
                    if (externalName.endsWith("/")) {
                        filePath = filePath.replace(externalName, "/" + externalStorage + "/");
                    } else {
                        filePath = filePath.replace(externalName, "/" + externalStorage);
                    }
                    break;
                } else if (filePath.startsWith(internalName)) {
                    if (internalName.endsWith("/")) {
                        filePath = filePath.replace(internalName, "/" + internalStorage + "/");
                    } else {
                        filePath = filePath.replace(internalName, "/" + internalStorage);
                    }
                    break;
                }
            }
        }
        if (filePath.startsWith("//")) {
            filePath = filePath.substring(1);
        }
        return filePath;
    }

    @SuppressWarnings("deprecation")
    public double getSpaceLeft() {
        StatFs stat = new StatFs(Environment.getExternalStorageDirectory().getPath());
        double sdAvailSize = 0;
        if (Build.VERSION.SDK_INT > 17) {
            sdAvailSize = stat.getAvailableBlocksLong() * stat.getBlockSizeLong();
        } else {
            sdAvailSize = stat.getAvailableBlocks() * stat.getBlockSize();
        }
        // One binary gigabyte equals 1,073,741,824 bytes.
        return sdAvailSize / 1073741824;
    }

    public void addFileItemToStore(String filePath) {

        Context context = getCore().getContext();

        if (filePath == null || (!filePath.endsWith(".zip") && !filePath.endsWith(".sh"))) {
            Toast.makeText(context, R.string.error_file_manager_invalid_zip, Toast.LENGTH_SHORT)
                    .show();
            return;
        }

        if (!filePath.endsWith(".zip") && !filePath.endsWith(".sh")) {
            Toast.makeText(context, R.string.error_file_manager_zip, Toast.LENGTH_SHORT).show();
            return;
        }

        String sdcardPath = new String(filePath);

        filePath = getPath(filePath);

        File file = new File(sdcardPath);
        if (!file.exists()) {
            Toast.makeText(context, R.string.error_file_manager_not_found_zip, Toast.LENGTH_LONG)
                    .show();
        } else {

            FileItemStore.addItem(filePath, sdcardPath, false, getCore().getPreferences()
                    .getZipPosition());
        }
    }

    private void readMounts() {

        Core core = getCore();

        ArrayList<String> mounts = new ArrayList<String>();
        ArrayList<String> vold = new ArrayList<String>();

        Scanner scanner = null;
        try {
            scanner = new Scanner(new File("/proc/mounts"));
            while (scanner.hasNext()) {
                String line = scanner.nextLine();
                if (line.startsWith("/dev/block/vold/")) {
                    String[] lineElements = line.split(" ");
                    String element = lineElements[1];

                    mounts.add(element);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (scanner != null) {
                scanner.close();
            }
        }
        boolean addExternal = mounts.size() == 1 && hasExternalStorage();
        if (mounts.size() == 0 || addExternal) {
            mounts.add("/mnt/sdcard");
        }

        File fstab = findFstab(core);
        if (fstab != null) {
            try {
                String path = core.getContext().getExternalCacheDir() + File.separator
                        + fstab.getName();
                SuperUserPlugin suPlugin = (SuperUserPlugin) core.getPlugin(Core.PLUGIN_SUPERUSER);
                suPlugin.run("cp " + fstab.getAbsolutePath() + " " + path);
                suPlugin.run("chmod -R 777 " + path);

                scanner = new Scanner(new File(path));
                while (scanner.hasNext()) {
                    String line = scanner.nextLine();
                    if (line.startsWith("dev_mount")) {
                        String[] lineElements = line.split(" ");
                        String element = lineElements[2];

                        if (element.contains(":")) {
                            element = element.substring(0, element.indexOf(":"));
                        }

                        if (element.toLowerCase().indexOf("usb") < 0) {
                            vold.add(element);
                        }
                    } else if (line.startsWith("/devices/platform")) {
                        String[] lineElements = line.split(" ");
                        String element = lineElements[1];

                        if (element.contains(":")) {
                            element = element.substring(0, element.indexOf(":"));
                        }

                        if (element.toLowerCase().indexOf("usb") < 0) {
                            vold.add(element);
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (scanner != null) {
                    scanner.close();
                }
            }
        }
        if (addExternal && (vold.size() == 1 && hasExternalStorage())) {
            mounts.add(vold.get(0));
        }
        if (vold.size() == 0 || (vold.size() == 1 && hasExternalStorage())) {
            vold.add("/mnt/sdcard");
        }

        for (int i = 0; i < mounts.size(); i++) {
            String mount = mounts.get(i);
            File root = new File(mount);
            if (!vold.contains(mount)
                    || (!root.exists() || !root.isDirectory() || !root.canWrite())) {
                mounts.remove(i--);
            }
        }

        for (int i = 0; i < mounts.size(); i++) {
            String mount = mounts.get(i);
            if (mount.indexOf("sdcard0") >= 0 || mount.equalsIgnoreCase("/mnt/sdcard")
                    || mount.equalsIgnoreCase("/sdcard")) {
                mInternalStoragePath = mount;
//            } else {
//                mExternalStoragePath = mount;
            }
        }

        if (mInternalStoragePath == null) {
            mInternalStoragePath = "/sdcard";
        }
    }

    private String externalStorage() {
        Context context = getCore().getContext();
        String dirPath = null;
        try {
            String[] volumePaths = null;
            ArrayList<String> volumePathsList = null;
            String path = null;
            if (Build.VERSION.SDK_INT >= 14) {
                volumePaths = volumePaths(context);
                if (volumePaths != null) {
                    volumePathsList = new ArrayList<String>();
                    path = Environment.getExternalStorageDirectory().getAbsolutePath();
                }
            }
            try {
                String primaryVolumePath = primaryVolumePath(context);
                int i = volumePaths.length;
                for (int j = 0;; j++)
                    if (j < i) {
                        String volumePath = volumePaths[j];
                        try {
                            if ((volumePath.equals(System.getenv("EMULATED_STORAGE_SOURCE")))
                                    || (volumePath.equals(System.getenv("EXTERNAL_STORAGE")))
                                    || (volumePath.equals(path))
                                    || (volumePath.equals(primaryVolumePath))
                                    || (volumePath.toLowerCase().contains("usb")))
                                continue;
                            volumePathsList.add(volumePath);
                        } catch (Exception ex) {
                            ex.printStackTrace();
                        }
                    } else {
                        if (volumePathsList.size() == 1) {
                            dirPath = (String) volumePathsList.get(0);
                        }
                        return dirPath;
                    }
            } catch (Exception ex) {
                ex.printStackTrace();

            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return dirPath;
    }

    private String[] volumePaths(Context context) {
        try {
            StorageManager localStorageManager = (StorageManager) context
                    .getSystemService("storage");
            return (String[]) (String[]) localStorageManager.getClass()
                    .getMethod("getVolumePaths", new Class[0])
                    .invoke(localStorageManager, new Object[0]);
        } catch (Exception ex) {
            ex.printStackTrace();
            return null;
        }
    }

    private String primaryVolumePath(Context context) {
        try {
            StorageManager localStorageManager = (StorageManager) context
                    .getSystemService("storage");
            Object localObject = localStorageManager.getClass()
                    .getMethod("getPrimaryVolume", new Class[0])
                    .invoke(localStorageManager, new Object[0]);
            return (String) localObject.getClass().getMethod("getPath", new Class[0])
                    .invoke(localObject, new Object[0]);
        } catch (Exception ex) {
            ex.printStackTrace();
            return null;
        }
    }

    private File findFstab(Core core) {

        File file = null;

        file = new File("/system/etc/vold.fstab");
        if (file.exists()) {
            return file;
        }

        SuperUserPlugin suPlugin = (SuperUserPlugin) core.getPlugin(Core.PLUGIN_SUPERUSER);
        CommandResult cm = suPlugin
                .run("grep -ls \"/dev/block/\" * --include=fstab.* --exclude=fstab.goldfish");
        if (cm.getOutString() != null) {
            String[] files = cm.getOutString().split("\n");
            for (int i = 0; i < files.length; i++) {
                file = new File(files[i]);
                if (file.exists()) {
                    return file;
                }
            }
        }

        return null;
    }

} public class DownloadFile {

    public DownloadFile(Core core, String url, String fileName, String md5) {
        this(core, url, fileName, md5, null);
    }

    public DownloadFile(Core core, String url, String fileName, String md5, DownloadTask.OnDownloadFinishListener listener) {

        Context context = core.getContext();

        final ProgressDialog progressDialog = new ProgressDialog(context);

        if (fileName == null) {
            fileName = url.substring(url.lastIndexOf("/") + 1);
            if (fileName.indexOf("?") >= 0) {
                fileName = fileName.substring(0, fileName.indexOf("?"));
            }
            if (!fileName.contains(".zip") && !fileName.contains(".apk")) {
                fileName = "unknown-file-name.zip";
            }
        }

        final DownloadTask downloadFile = new DownloadTask(core, progressDialog, url, fileName, md5, listener);

        progressDialog.setTitle(R.string.download);
        progressDialog.setMessage(context.getResources().getString(R.string.downloading,
                new Object[] { url, core.getPreferences().getDownloadPath() }));
        progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
        progressDialog.setCancelable(false);
        progressDialog.setProgress(0);
        progressDialog.setButton(Dialog.BUTTON_NEGATIVE,
                context.getResources().getString(android.R.string.cancel),
                new DialogInterface.OnClickListener() {

                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        downloadFile.detach();
                        downloadFile.cancel(true);
                    }
                });

        downloadFile.attach(progressDialog);
        progressDialog.show();
        downloadFile.execute();
    }
} public class DownloadTask extends AsyncTask<Void, Integer, Integer> {

    public interface OnDownloadFinishListener {

        public void onDownloadFinish(File file);

        public void onDownloadError(Exception ex);
    }

    public interface DownloadTaskListener extends OnDownloadFinishListener {

        public void setDownloadProgress(int progress);

        public void setDownloadMax(int max);
    }

    private int mScale = 1048576;

    private Core mCore;
    private OnDownloadFinishListener mFinishListener;
    private DownloadTaskListener mTaskListener;
    private ProgressDialog mDialog = null;
    private Context mContext;
    private String mUrl;
    private String mFileName;
    private String mMd5;
    private final WakeLock mWakeLock;

    private boolean mDone = false;

    @SuppressWarnings("deprecation")
    public DownloadTask(Core core, ProgressDialog dialog, String url, String fileName, String md5, OnDownloadFinishListener listener) {
        this.attach(dialog);

        mCore = core;
        mContext = core.getContext();
        mFinishListener = listener;

        File dPath = new File(core.getPreferences().getDownloadPath());
        dPath.mkdirs();

        mUrl = url;
        mFileName = fileName;
        mMd5 = md5;

        PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
        mWakeLock = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, MainActivity.class.getName());

        Downloads.addDownloadTask(this);
    }

    @SuppressWarnings("deprecation")
    public DownloadTask(Core core, String url, String fileName, DownloadTaskListener listener) {

        mCore = core;
        mContext = core.getContext();
        mTaskListener = listener;

        File dPath = new File(core.getPreferences().getDownloadPath());
        dPath.mkdirs();

        mUrl = url;
        mFileName = fileName;

        PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
        mWakeLock = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, MainActivity.class.getName());

        Downloads.addDownloadTask(this);
        
    }

    public void attach(ProgressDialog dialog) {
        mDialog = dialog;
    }

    public void detach() {
        if (mDialog != null) {
            mDialog.dismiss();
        }
        mDialog = null;
        Downloads.removeDownloadTask(this);
    }

    public boolean isDone() {
        return mDone;
    }

    @Override
    protected void onPreExecute() {
        mDone = false;
        if (mDialog != null) {
            mDialog.show();
        }
        mWakeLock.acquire();
    }

    @SuppressWarnings("deprecation")
    @Override
    protected Integer doInBackground(Void... params) {
        Preferences prefs = mCore.getPreferences();
        String downloadPath = prefs.getDownloadPath();
        File destFile = new File(downloadPath, mFileName);


        try {
            String name = mFileName;
            String extension = "";
            int index = mFileName.lastIndexOf(".");
            if (index > 0) {
                extension = mFileName.substring(mFileName.lastIndexOf("."));
                name = mFileName.substring(0, mFileName.lastIndexOf("."));
            }
            int i = 0;
            while (destFile.exists()) {
                i++;
                mFileName = name + "(" + i + ")" + extension;
                destFile = new File(downloadPath, mFileName);
            }
    
            if (mUrl.contains("goo.im")) {
                String login = prefs.getLogin();
                if (login != null && !"".equals(login)) {
                    mUrl = mUrl + "&hash=" + login;
                }
            }
    
            if (mMd5 != null) {
                FileOutputStream fos = null;
                try {
                    fos = new FileOutputStream(new File(downloadPath, mFileName + ".md5sum"));
                    fos.write((mMd5 + " " + mFileName).getBytes());
                } catch (Exception ex) {
                } finally {
                    if (fos != null)
                        try {
                            fos.close();
                        } catch (Exception ex) {
                        }
                }
            }
    
            InputStream is = null;
            OutputStream os = null;
            try {
                URL getUrl = new URL(mUrl);
                URLConnection conn = getUrl.openConnection();
                if (getUrl.toString().contains("goo.im") && !prefs.isLogged()) {
                    conn.connect();
                    publishProgress(-1);
                    is = new BufferedInputStream(conn.getInputStream());
                    os = new FileOutputStream(destFile);
                    byte[] buf = new byte[4096];
                    int nRead = -1;
                    while ((nRead = is.read(buf)) != -1) {
                        if (this.isCancelled())
                            break;
                        os.write(buf, 0, nRead);
                    }
                    try {
                        Thread.sleep(10500);
                    } catch (InterruptedException e) {
                    }
                    getUrl = new URL(mUrl);
                    conn = getUrl.openConnection();
                }
                final int lengthOfFile = conn.getContentLength();
                StatFs stat = new StatFs(downloadPath);
                long availSpace = 0;
                if (Build.VERSION.SDK_INT > 17) {
                    availSpace = stat.getAvailableBlocksLong() * stat.getBlockSizeLong();
                } else {
                    availSpace = stat.getAvailableBlocks() * stat.getBlockSize();
                }
                if (lengthOfFile >= availSpace) {
                    destFile.delete();
                    return 3;
                }
                if (lengthOfFile < 10000000)
                    mScale = 1024; // if less than 10 mb, scale using kb
                publishProgress(0, lengthOfFile);
                conn.connect();
                is = new BufferedInputStream(conn.getInputStream());
                os = new FileOutputStream(destFile);
                byte[] buf = new byte[4096];
                int nRead = -1;
                int totalRead = 0;
                while ((nRead = is.read(buf)) != -1) {
                    if (this.isCancelled())
                        break;
                    os.write(buf, 0, nRead);
                    totalRead += nRead;
                    publishProgress(totalRead, lengthOfFile);
                }
    
                if (isCancelled()) {
                    destFile.delete();
                    return 2;
                }
    
                return 0;
            } catch (final Exception e) {
                e.printStackTrace();
                destFile.delete();
                ((Activity) mContext).runOnUiThread(new Runnable() {

                    @Override
                    public void run() {
                        onCancelled(null);
                        if (mFinishListener != null) {
                            mFinishListener.onDownloadError(e);
                        }
                        if (mTaskListener != null) {
                            mTaskListener.onDownloadError(e);
                        }
                    }
                });
            } finally {
                if (is != null) {
                    try {
                        is.close();
                    } catch (Exception e) {
                    }
                }
                if (os != null) {
                    try {
                        os.flush();
                        os.close();
                    } catch (Exception e) {
                    }
                }
            }
        } catch (final Exception ex) {
            ex.printStackTrace();
            destFile.delete();
            ((Activity) mContext).runOnUiThread(new Runnable() {

                @Override
                public void run() {
                    onCancelled(null);
                    if (mFinishListener != null) {
                        mFinishListener.onDownloadError(ex);
                    }
                    if (mTaskListener != null) {
                        mTaskListener.onDownloadError(ex);
                    }
                }
            });
        }
        return -1;
    }

    @Override
    protected void onCancelled(Integer result) {
        mDone = true;
        if (mDialog != null) {
            mDialog.dismiss();
        }
        mWakeLock.release();
        mWakeLock.acquire(30000);
        if (result == null) {
            Toast.makeText(mContext, R.string.downloading_error, Toast.LENGTH_SHORT).show();
            return;
        }
        switch (result) {
            case 0:
                break;
            case 2:
                Toast.makeText(mContext, R.string.downloading_interrupted, Toast.LENGTH_SHORT)
                        .show();
                break;
            case 3:
                Toast.makeText(mContext, R.string.downloading_nospace, Toast.LENGTH_SHORT).show();
                break;
            default:
                Toast.makeText(mContext, R.string.downloading_error, Toast.LENGTH_SHORT).show();
        }
        Downloads.removeDownloadTask(this);
    }

    @Override
    protected void onPostExecute(Integer result) {
        mDone = true;
        if (mDialog != null) {
            mDialog.dismiss();
        }
        mWakeLock.release();
        mWakeLock.acquire(30000);

        switch (result) {
            case 0:
                String path = mCore.getPreferences().getDownloadPath() + mFileName;
                if (mFinishListener != null) {
                    mFinishListener.onDownloadFinish(new File(path));
                }
                if (mTaskListener != null) {
                    mTaskListener.onDownloadFinish(new File(path));
                }
                if (mFileName.endsWith(".apk")) {
                    Intent intent = new Intent(Intent.ACTION_VIEW);
                    intent.setDataAndType(Uri.fromFile(new File(path)),
                            "application/vnd.android.package-archive");
                    mContext.startActivity(intent);
                } else if (mFileName.endsWith(".zip")) {
                    StoragePlugin sPlugin = (StoragePlugin) mCore
                            .getPlugin(Core.PLUGIN_STORAGE);
                    sPlugin.addFileItemToStore(path);
                }
                break;
            case 2:
                Toast.makeText(mContext, R.string.downloading_interrupted, Toast.LENGTH_SHORT)
                        .show();
                break;
            case 3:
                Toast.makeText(mContext, R.string.downloading_nospace, Toast.LENGTH_SHORT).show();
                break;
            default:
                Toast.makeText(mContext, R.string.downloading_error, Toast.LENGTH_SHORT).show();
        }
        Downloads.removeDownloadTask(this);
    }

    @Override
    protected void onProgressUpdate(Integer... values) {
        if (mDialog != null) {
            if (values[0] == -1) {
                mDialog.setIndeterminate(true);
                return;
            }
            mDialog.setIndeterminate(false);
            if (values.length == 0) {
                return;
            }
            mDialog.setProgress(values[0] / mScale);
            if (values.length == 1) {
                return;
            }
            mDialog.setMax(values[1] / mScale);
        }
        if (mTaskListener != null) {
            if (values[0] == -1) {
                mTaskListener.setDownloadProgress(-1);
                return;
            }
            if (values.length == 0) {
                return;
            }
            mTaskListener.setDownloadProgress(values[0] / mScale);
            if (values.length == 1) {
                return;
            }
            mTaskListener.setDownloadMax(values[1] / mScale);
        }
    }
} public class URLStringReader extends AsyncTask<String, Void, Void> {

    public static interface URLStringReaderListener {

        public void onReadEnd(String buffer);
        public void onReadError(Exception ex);
    };

    private URLStringReaderListener mListener;
    private String mBuffer;
    private Exception mException;
    
    public URLStringReader(URLStringReaderListener listener) {
        mListener = listener;
    }

    @Override
    protected Void doInBackground(String... params) {
        try {
            mBuffer = readString(params[0]);
            return null;
        } catch (Exception ex) {
            ex.printStackTrace();
            mException = ex;
        }
        return null;
    }

    @Override
    protected void onPostExecute(Void result) {
        if (mListener != null) {
            if (mException != null) {
                mListener.onReadError(mException);
            } else {
                mListener.onReadEnd(mBuffer);
            }
        }
    }

    private String readString(String urlStr) throws Exception {
        URL url = new URL(urlStr);
        URLConnection yc = url.openConnection();
        BufferedReader in = null;
        StringBuffer sb = new StringBuffer();
        try {
            in = new BufferedReader(new InputStreamReader(yc.getInputStream()));
            String inputLine;
            while ((inputLine = in.readLine()) != null)
                sb.append(inputLine);
        } finally {
            if (in != null)
                in.close();
        }
        return sb.toString();
    }
} public static interface URLStringReaderListener {

    public void onReadEnd(String buffer);
    public void onReadError(Exception ex);
}; public class Files {

    public static boolean hasAndroidSecure() {
        String sdcard = "sdcard";
        return folderExists("/" + sdcard + "/.android-secure");
    }

    public static boolean hasSdExt() {
        return folderExists("/sd-ext");
    }

    public static String getSBINFolder() {
        if (folderExists("/sbin")) {
            return "/sbin/";
        } else if (folderExists("/system/sbin")) {
            return "/system/sbin/";
        }
        return null;
    }

    private static boolean folderExists(String path) {
        File f = new File(path);
        return f.exists() && f.isDirectory();
    }

    public static String[] readMd5File(File file) {
        try {
            StringBuffer fileData = new StringBuffer(1000);
            BufferedReader reader;

            reader = new BufferedReader(new FileReader(file));
            char[] buf = new char[1024];
            int numRead = 0;
            while ((numRead = reader.read(buf)) != -1) {
                String readData = String.valueOf(buf, 0, numRead);
                fileData.append(readData);
                buf = new char[1024];
            }
            reader.close();
            String content = fileData.toString();
            StringTokenizer st = new StringTokenizer(content, " ");
            return new String[] { st.nextToken(), st.nextToken() };
        } catch (Exception e) {
        }
        return null;
    }

    public static String md5(File file) {
        InputStream is = null;
        try {
            is = new FileInputStream(file);
            MessageDigest digest = MessageDigest.getInstance("MD5");
            byte[] buffer = new byte[8192];
            int read = 0;
            while ((read = is.read(buffer)) > 0) {
                digest.update(buffer, 0, read);
            }
            byte[] md5sum = digest.digest();
            BigInteger bigInt = new BigInteger(1, md5sum);
            String md5 = bigInt.toString(16);
            while (md5.length() < 32) {
                md5 = "0" + md5;
            }
            return md5;
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            try {
                is.close();
            } catch (Exception e) {
            }
        }
    }

    public static boolean writeToFile(String data, String path, String fileName) {

        File folder = new File(path);
        File file = new File(folder, fileName);

        folder.mkdirs();

        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(file);
            fos.write(data.getBytes());
            return true;
        } catch (IOException ex) {
            ex.printStackTrace();
            return false;
        } finally {
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException ex) {
                }
            }
        }
    }

    public static String readAssets(Context contex, String fileName) {
        BufferedReader in = null;
        StringBuilder data = null;
        try {
            data = new StringBuilder(2048);
            char[] buf = new char[2048];
            int nRead = -1;
            in = new BufferedReader(new InputStreamReader(contex.getAssets().open(fileName)));
            while ((nRead = in.read(buf)) != -1) {
                data.append(buf, 0, nRead);
            }
        } catch (IOException ex) {
            ex.printStackTrace();
            return null;
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                }
            }
        }

        if (TextUtils.isEmpty(data)) {
            return null;
        }
        return data.toString();
    }

    public static String[] readAssetsSplit(Context context, String fileName) {
        try {
            return readFileSplit(context.getAssets().open(fileName));
        } catch (IOException ex) {
            ex.printStackTrace();
            return null;
        }
    }

    public static String[] readFileSplit(String fileName) {
        try {
            return readFileSplit(new FileInputStream(fileName));
        } catch (FileNotFoundException ex) {
            ex.printStackTrace();
            return null;
        }
    }

    private static String[] readFileSplit(InputStream is) {
        List<String> data = new ArrayList<String>();
        Scanner scanner = null;
        try {
            scanner = new Scanner(new InputStreamReader(is));
            while (scanner.hasNext()) {
                String line = scanner.nextLine();
                data.add(line);
            }
        } finally {
            if (scanner != null) {
                scanner.close();
            }
        }

        return data.toArray(new String[data.size()]);
    }

    public static String findLineInFile(Context contex, File file, String regExp) {
        Scanner scanner = null;
        try {
            scanner = new Scanner(new InputStreamReader(new FileInputStream(file)));
            while (scanner.hasNext()) {
                String line = scanner.nextLine();
                if (line.matches(regExp)) {
                    return line;
                }
            }
        } catch (IOException ex) {
            ex.printStackTrace();
        } finally {
            if (scanner != null) {
                scanner.close();
            }
        }

        return null;
    }

    public static boolean recursiveDelete(File f) {
        try {
            if (f.isDirectory()) {
                File[] files = f.listFiles();
                for (int i = 0; i < files.length; i++) {
                    if (!recursiveDelete(files[i])) {
                        return false;
                    }
                }
                if (!f.delete()) {
                    return false;
                }
            } else {
                if (!f.delete()) {
                    return false;
                }
            }
        } catch (Exception ignore) {
        }
        return true;
    }

    public static String getPathFromUri(Core core, Uri uri) {
        StoragePlugin sPlugin = (StoragePlugin) core.getPlugin(Core.PLUGIN_STORAGE);
        String filePath = uri.getPath();
        if (!(new File(filePath)).exists()) {
            ContentResolver cr = core.getContext().getContentResolver();
            Cursor cursor = cr.query(uri, null, null, null, null);
            try {
                if (cursor.moveToNext()) {
                    int index = cursor.getColumnIndex(MediaStore.MediaColumns.DATA);
                    if (index >= 0) {
                        filePath = cursor.getString(index);
                    } else if (Build.VERSION.SDK_INT >= 19
                            && uri.toString().startsWith(ContentResolver.SCHEME_CONTENT)) {
                        String newUri = new Uri.Builder()
                                .scheme(ContentResolver.SCHEME_CONTENT)
                                .authority(uri.getAuthority()).appendPath("document")
                                .build().toString();
                        String path = uri.toString();
                        if (path.startsWith(newUri)) {
                            String firstPath = filePath.substring(0, filePath.indexOf(":"));
                            filePath = filePath.substring(filePath.indexOf(":") + 1);
                            String storage = sPlugin.getInternalStoragePath();
                            if (firstPath.indexOf(StoragePlugin.ROOT_ID_PRIMARY_EMULATED) < 0) {
                                storage = sPlugin.getExternalStoragePath();
                            }
                            filePath = storage + "/" + filePath;
                        }

                    }
                }
            } finally {
                cursor.close();
            }
        }
        return filePath;
    }
} public class Strings {

    private static final SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd.HH.mm.ss");

    private static final long K = 1024;
    private static final long M = K * K;
    private static final long G = M * K;
    private static final long T = G * K;

    public static String formatSize(final long value) {
        final long[] dividers = new long[] { T, G, M, K, 1 };
        final String[] units = new String[] { "TB", "GB", "MB", "KB", "B" };
        String result = null;
        for (int i = 0; i < dividers.length; i++) {
            final long divider = dividers[i];
            if (value >= divider) {
                result = format(value, divider, units[i]);
                break;
            }
        }
        return result;
    }

    private static String format(final long value, final long divider, final String unit) {
        final double result = divider > 1 ? (double) value / (double) divider : (double) value;
        return String.format("%.1f %s", Double.valueOf(result), unit);
    }

    public static String formatDate(final long value) {
        return SDF.format(value);
    }

    public static String getDateAndTime() {
        return SDF.format(new Date(System.currentTimeMillis()));
    }

    public static String replace(String original, String starts, String replace) {
        return !original.startsWith(starts) ? original : replace
                + original.substring(starts.length());
    }
} public class SystemProperties {

    private static final int ALARM_ID = 122303221;

    public static String getProperty(String prop) {
        try {
            Process p = Runtime.getRuntime().exec("getprop " + prop);
            p.waitFor();
            BufferedReader input = new BufferedReader(new InputStreamReader(p.getInputStream()));
            String line = input.readLine();
            return "".equals(line) ? null : line;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    
    public static boolean isNetworkAvailable(Context context) {
        ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
        return activeNetworkInfo != null && activeNetworkInfo.isConnected();
    }
    
    public static void setAlarm(Context context, long time, boolean trigger) {

        Intent i = new Intent(context, NotificationAlarm.class);
        i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

        PendingIntent pi = PendingIntent
                .getBroadcast(context, ALARM_ID, i, PendingIntent.FLAG_UPDATE_CURRENT);

        AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
        am.cancel(pi);
        if (time > 0) {
            am.setInexactRepeating(AlarmManager.RTC_WAKEUP, trigger ? 0 : time, time, pi);
        }
    }

    public static boolean alarmExists(Context context) {
        return (PendingIntent.getBroadcast(context, ALARM_ID, new Intent(context,
                NotificationAlarm.class), PendingIntent.FLAG_NO_CREATE) != null);
    }
} public class Preferences {

    public static final String INSTALL_BACKUP = "BACKUP";
    public static final String INSTALL_WIPESYSTEM = "WIPESYSTEM";
    public static final String INSTALL_WIPEDATA = "WIPEDATA";
    public static final String INSTALL_WIPECACHES = "WIPECACHES";
    public static final String INSTALL_FIXPERM = "FIXPERM";
    public static final String[] INSTALL_OPTIONS = {
            INSTALL_BACKUP,
            INSTALL_WIPESYSTEM,
            INSTALL_WIPEDATA,
            INSTALL_WIPECACHES,
            INSTALL_FIXPERM };

    private static final String PROPERTY_FIRST_RUN = "first-run";
    private static final String PROPERTY_INTERNAL_STORAGE = "internal-storage";
    private static final String PROPERTY_EXTERNAL_STORAGE = "external-storage";
    private static final String PROPERTY_FORCE_EXTERNAL_STORAGE = "mountextsdcard";
    private static final String PROPERTY_FORCE_DATA_MEDIA = "forcedatamedia";
    private static final String PROPERTY_BACKUP_EXTERNAL_STORAGE = "backupextsdcard";
    private static final String PROPERTY_RECOVERY = "recovery";
    private static final String PROPERTY_CHANGED_RECOVERY = "changedrecovery";
    private static final String PROPERTY_LIST = "list";
    private static final String PROPERTY_DRAG_AND_DROP = "drag-and-drop";
    private static final String PROPERTY_SHOW_OPTIONS = "show-options";
    private static final String PROPERTY_DARK_THEME = "dark-theme";
    private static final String PROPERTY_CHECK_EXISTS = "check_exists";
    private static final String PROPERTY_CHECK_UPDATES_STARTUP = "check_updates_startup";
    private static final String PROPERTY_DOWNLOAD_PATH = "download_path";
    private static final String PROPERTY_CHECK_MD5 = "check_md5";
    private static final String PROPERTY_OVERRIDE_LIST = "override_list";
    private static final String PROPERTY_AUTOLOAD_LIST = "autoload_list";
    private static final String PROPERTY_ZIP_POSITION = "zip_position";
    private static final String PROPERTY_SPACE_LEFT = "space_left";
    private static final String PROPERTY_SHOW_WIPESYSTEM_ALERT = "wipesystem_alert";
    private static final String PROPERTY_USE_FOLDER = "usefolder";
    private static final String PROPERTY_FOLDER = "folder";
    private static final String PROPERTY_TO_DELETE = "to_delete";
    private static final String PROPERTY_RULES = "rules";
    private static final String PROPERTY_LOGIN = "goologin";
    private static final String PROPERTY_LOGIN_USERNAME = "goologinusername";
    private static final String PROPERTY_CLOUD_STORAGE = "cloudstorage";
    private static final String PROPERTY_DROPBOX_KEY = "dropboxkey";
    private static final String PROPERTY_DROPBOX_SECRET = "dropboxsecret";
    private static final String PROPERTY_LICENSE = "license";
    private static final String PROPERTY_RECOVERY_BLOCK = "recoveryblock";
    private static final String PROPERTY_BOOT_BLOCK = "bootblock";
    private static final String PROPERTY_ENABLE_NOTIFICATIONS = "enable_notifications";
    private static final String PROPERTY_TIME_NOTIFICATIONS = "time_notifications";
    private static final String PROPERTY_USE_ONANDROID = "use_onandroid";

    private static final String DEFAULT_RECOVERY = "cwmbased";
    private static final String DEFAULT_INTERNAL_STORAGE = "emmc";
    private static final String DEFAULT_EXTERNAL_STORAGE = "sdcard";
    private static final String DEFAULT_DOWNLOAD_PATH = "/sdcard/zipinstaller/";
    private static final String DEFAULT_ZIP_POSITION = "last";
    private static final String DEFAULT_TIME_NOTIFICATIONS = "3600000";
    private static final Set<String> DEFAULT_SHOW_OPTIONS = new HashSet<String>();
    private static final String DEFAULT_SPACE_LEFT = "-1";
    private static final int DEFAULT_CLOUD_STORAGE = CloudStorage.STORAGE_NONE;
    private static final boolean DEFAULT_FIRST_RUN = true;
    private static final boolean DEFAULT_CHANGED_RECOVERY = true;
    private static final boolean DEFAULT_FORCE_EXTERNAL_STORAGE = true;
    private static final boolean DEFAULT_FORCE_DATA_MEDIA = VERSION.SDK_INT >= 16;
    private static final boolean DEFAULT_BACKUP_EXTERNAL_STORAGE = false;
    private static final boolean DEFAULT_DRAG_AND_DROP = true;
    private static final boolean DEFAULT_DARK_THEME = false;
    private static final boolean DEFAULT_CHECK_EXISTS = true;
    private static final boolean DEFAULT_CHECK_UPDATES_STARTUP = true;
    private static final boolean DEFAULT_CHECK_MD5 = true;
    private static final boolean DEFAULT_OVERRIDE_LIST = true;
    private static final boolean DEFAULT_AUTOLOAD_LIST = false;
    private static final boolean DEFAULT_ENABLE_NOTIFICATIONS = true;
    private static final boolean DEFAULT_SHOW_WIPESYSTEM_ALERT = true;
    private static final boolean DEFAULT_USE_ONANDROID = false;
    private static final boolean DEFAULT_USE_FOLDER = false;
    private static final String DEFAULT_FOLDER = "/sdcard/download/";

    private Core mCore;
    private Context mContext;
    private SharedPreferences mSettings;

    public Preferences(Core core) {
        this(core.getContext());
        mCore = core;
        mContext = core.getContext();
    }

    public Preferences(Context context) {

        mSettings = PreferenceManager.getDefaultSharedPreferences(context);
        mContext = context;

        DEFAULT_SHOW_OPTIONS.add(INSTALL_BACKUP);
        DEFAULT_SHOW_OPTIONS.add(INSTALL_WIPESYSTEM);
        DEFAULT_SHOW_OPTIONS.add(INSTALL_WIPEDATA);
        DEFAULT_SHOW_OPTIONS.add(INSTALL_WIPECACHES);
        DEFAULT_SHOW_OPTIONS.add(INSTALL_FIXPERM);

        File file = new File(DEFAULT_DOWNLOAD_PATH);
        if (!file.exists()) {
            file.mkdirs();
        }
    }

    public int getCloudStorage() {
        return mSettings.getInt(PROPERTY_CLOUD_STORAGE, DEFAULT_CLOUD_STORAGE);
    }

    public void setCloudStorage(int value) {
        savePreference(PROPERTY_CLOUD_STORAGE, value);
        mCore.setCloudStorage(value);
    }

    public boolean isFirstRun() {
        return mSettings.getBoolean(PROPERTY_FIRST_RUN, DEFAULT_FIRST_RUN);
    }

    public void setFirstRun(boolean value) {
        savePreference(PROPERTY_FIRST_RUN, value);
    }

    public String getInternalStorage() {
        return mSettings.getString(PROPERTY_INTERNAL_STORAGE, DEFAULT_INTERNAL_STORAGE);
    }

    public void setInternalStorage(String value) {
        savePreference(PROPERTY_INTERNAL_STORAGE, value);
    }

    public String getExternalStorage() {
        return mSettings.getString(PROPERTY_EXTERNAL_STORAGE, DEFAULT_EXTERNAL_STORAGE);
    }

    public void setExternalStorage(String value) {
        savePreference(PROPERTY_EXTERNAL_STORAGE, value);
    }

    public boolean isForceExternalStorage() {
        return mSettings.getBoolean(PROPERTY_FORCE_EXTERNAL_STORAGE, DEFAULT_FORCE_EXTERNAL_STORAGE);
    }

    public void setForceExternalStorage(boolean value) {
        savePreference(PROPERTY_FORCE_EXTERNAL_STORAGE, value);
    }

    public boolean isForceDataMedia() {
        return mSettings.getBoolean(PROPERTY_FORCE_DATA_MEDIA, DEFAULT_FORCE_DATA_MEDIA);
    }

    public void setForceDataMedia(boolean value) {
        savePreference(PROPERTY_FORCE_DATA_MEDIA, value);
    }

    public boolean isBackupExternalStorage() {
        return mSettings.getBoolean(PROPERTY_BACKUP_EXTERNAL_STORAGE, DEFAULT_BACKUP_EXTERNAL_STORAGE);
    }

    public void setBackupExternalStorage(boolean value) {
        savePreference(PROPERTY_BACKUP_EXTERNAL_STORAGE, value);
    }

    public boolean existsRecovery() {
        return mSettings.contains(PROPERTY_RECOVERY);
    }

    public String getRecovery() {
        return mSettings.getString(PROPERTY_RECOVERY, DEFAULT_RECOVERY);
    }

    public void setRecovery(String value) {
        savePreference(PROPERTY_RECOVERY, value);
    }

    public String getList() {
        return mSettings.getString(PROPERTY_LIST, "");
    }

    public void setList(String value) {
        savePreference(PROPERTY_LIST, value);
    }

    public boolean isShowOption(String option) {
        Set<String> options = getShowOptions();
        return options.contains(option);
    }

    public Set<String> getShowOptions() {
        return mSettings.getStringSet(PROPERTY_SHOW_OPTIONS, DEFAULT_SHOW_OPTIONS);
    }

    public void setShowOptions(String options) {
        savePreference(PROPERTY_SHOW_OPTIONS, options);
    }

    public boolean isUseDragAndDrop() {
        return mSettings.getBoolean(PROPERTY_DRAG_AND_DROP, DEFAULT_DRAG_AND_DROP);
    }

    public void setUseDragAndDrop(boolean value) {
        savePreference(PROPERTY_DRAG_AND_DROP, value);
    }

    public boolean isDarkTheme() {
        return mSettings.getBoolean(PROPERTY_DARK_THEME, DEFAULT_DARK_THEME);
    }

    public void setDarkTheme(boolean value) {
        savePreference(PROPERTY_DARK_THEME, value);
    }

    public boolean isAlertOnChangeRecovery() {
        return mSettings.getBoolean(PROPERTY_CHANGED_RECOVERY, DEFAULT_CHANGED_RECOVERY);
    }

    public void setAlertOnChangeRecovery(boolean value) {
        savePreference(PROPERTY_CHANGED_RECOVERY, value);
    }

    public boolean isCheckExists() {
        return mSettings.getBoolean(PROPERTY_CHECK_EXISTS, DEFAULT_CHECK_EXISTS);
    }

    public void setCheckExists(boolean value) {
        savePreference(PROPERTY_CHECK_EXISTS, value);
    }

    public boolean isCheckUpdatesStartup() {
        return mSettings.getBoolean(PROPERTY_CHECK_UPDATES_STARTUP, DEFAULT_CHECK_UPDATES_STARTUP);
    }

    public void setCheckUpdatesStartup(boolean value) {
        savePreference(PROPERTY_CHECK_UPDATES_STARTUP, value);
    }

    public boolean isCheckMD5() {
        return mSettings.getBoolean(PROPERTY_CHECK_MD5, DEFAULT_CHECK_MD5);
    }

    public void setCheckMD5(boolean value) {
        savePreference(PROPERTY_CHECK_MD5, value);
    }

    public boolean isOverrideList() {
        return mSettings.getBoolean(PROPERTY_OVERRIDE_LIST, DEFAULT_OVERRIDE_LIST);
    }

    public void setOverrideList(boolean value) {
        savePreference(PROPERTY_OVERRIDE_LIST, value);
    }

    public boolean isAutoloadList() {
        return mSettings.getBoolean(PROPERTY_AUTOLOAD_LIST, DEFAULT_AUTOLOAD_LIST);
    }

    public void setAutoloadList(boolean value) {
        savePreference(PROPERTY_AUTOLOAD_LIST, value);
    }

    public String getDownloadPath() {
        return mSettings.getString(PROPERTY_DOWNLOAD_PATH, DEFAULT_DOWNLOAD_PATH);
    }

    public void setDownloadPath(String value) {
        if (!value.endsWith("/"))
            value = value + "/";
        savePreference(PROPERTY_DOWNLOAD_PATH, value);
    }

    public boolean isAcceptNotifications() {
        return mSettings.getBoolean(PROPERTY_ENABLE_NOTIFICATIONS, DEFAULT_ENABLE_NOTIFICATIONS);
    }

    public void setAcceptNotifications(boolean value) {
        savePreference(PROPERTY_ENABLE_NOTIFICATIONS, value);
    }

    public long getTimeNotifications() {
        return Long.parseLong(mSettings.getString(PROPERTY_TIME_NOTIFICATIONS,
                DEFAULT_TIME_NOTIFICATIONS));
    }

    public void setTimeNotifications(long value) {
        savePreference(PROPERTY_TIME_NOTIFICATIONS, String.valueOf(value));
    }

    public boolean isUseONandroid() {
        return mSettings.getBoolean(PROPERTY_USE_ONANDROID, DEFAULT_USE_ONANDROID);
    }

    public void setUseONandroid(boolean value) {
        savePreference(PROPERTY_USE_ONANDROID, value);
    }

    public String getZipPosition() {
        return mSettings.getString(PROPERTY_ZIP_POSITION, DEFAULT_ZIP_POSITION);
    }

    public void setZipPosition(String value) {
        savePreference(PROPERTY_ZIP_POSITION, value);
    }

    public double getSpaceLeft() {
        return Double.parseDouble(mSettings.getString(PROPERTY_SPACE_LEFT, DEFAULT_SPACE_LEFT));
    }

    public void setSpaceLeft(double value) {
        savePreference(PROPERTY_SPACE_LEFT, String.valueOf(value));
    }

    public boolean isShowSystemWipeAlert() {
        return mSettings.getBoolean(PROPERTY_SHOW_WIPESYSTEM_ALERT, DEFAULT_SHOW_WIPESYSTEM_ALERT);
    }

    public void setShowSystemWipeAlert(boolean value) {
        savePreference(PROPERTY_SHOW_WIPESYSTEM_ALERT, value);
    }

    public boolean isUseFolder() {
        return mSettings.getBoolean(PROPERTY_USE_FOLDER, DEFAULT_USE_FOLDER);
    }

    public void setUseFolder(boolean value) {
        savePreference(PROPERTY_USE_FOLDER, value);
    }

    public String getFolder() {
        return mSettings.getString(PROPERTY_FOLDER, DEFAULT_FOLDER);
    }

    public void setFolder(String value) {
        savePreference(PROPERTY_FOLDER, value);
    }

    public String[] getToDelete() {
        return getArrayProperty(PROPERTY_TO_DELETE);
    }

    public void setToDelete(String[] value) {
        setArrayProperty(PROPERTY_TO_DELETE, value);
    }

    public List<Rule> getRules() {
        return Rule.createRulesAsList(mContext, mSettings.getString(PROPERTY_RULES, ""));
    }

    public void setRules(List<Rule> rules) {
        savePreference(PROPERTY_RULES, Rule.storeRules(rules));
    }

    public void addRule(String name, int type) {
        List<Rule> rules = getRules();
        List<Rule> newRules = new ArrayList<Rule>();
        for (Rule rule : rules) {
            newRules.add(rule);
        }
        newRules.add(new Rule(mContext, name, type));
        savePreference(PROPERTY_RULES, Rule.storeRules(newRules));
    }

    public void removeRule(int index) {
        List<Rule> rules = getRules();
        rules.remove(index);
        savePreference(PROPERTY_RULES, Rule.storeRules(rules));
    }

    public String getRecoveryBlock() {
        return mSettings.getString(PROPERTY_RECOVERY_BLOCK, null);
    }

    public void setRecoveryBlock(String value) {
        savePreference(PROPERTY_RECOVERY_BLOCK, value);
    }

    public String getBootBlock() {
        return mSettings.getString(PROPERTY_BOOT_BLOCK, null);
    }

    public void setBootBlock(String value) {
        savePreference(PROPERTY_BOOT_BLOCK, value);
    }

    public void logout() {
        savePreference(PROPERTY_LOGIN, "");
    }

    public void login(String value) {
        savePreference(PROPERTY_LOGIN, value);
    }

    public void setLoginUserName(String value) {
        savePreference(PROPERTY_LOGIN_USERNAME, value);
    }

    public boolean isLogged() {
        return !"".equals(mSettings.getString(PROPERTY_LOGIN, ""));
    }

    public String getLogin() {
        return mSettings.getString(PROPERTY_LOGIN, "");
    }

    public String getLoginUserName() {
        return mSettings.getString(PROPERTY_LOGIN_USERNAME, null);
    }

    public void setDropboxKey(String value) {
        savePreference(PROPERTY_DROPBOX_KEY, value);
    }

    public String getDropboxKey() {
        return mSettings.getString(PROPERTY_DROPBOX_KEY, null);
    }

    public void setDropboxSecret(String value) {
        savePreference(PROPERTY_DROPBOX_SECRET, value);
    }

    public String getDropboxSecret() {
        return mSettings.getString(PROPERTY_DROPBOX_SECRET, null);
    }

    public void setLicense(boolean value) {
        savePreference(PROPERTY_LICENSE, value);
    }

    public boolean getLicense() {
        return mSettings.getBoolean(PROPERTY_LICENSE, false);
    }

    private String[] getArrayProperty(String property) {
        String value = mSettings.getString(property, "");
        String[] array = value.split("\n");
        if (array.length == 1 && "".equals(array[0])) {
            return new String[0];
        }
        return array;
    }

    public void setArrayProperty(String property, String[] value) {
        String array = "";
        for (int i = 0; i < value.length; i++) {
            array += value[i] + "\n";
        }
        savePreference(property, array);
    }

    private void savePreference(String preference, String value) {
        SharedPreferences.Editor editor = mSettings.edit();
        editor.putString(preference, value);
        editor.commit();
    }

    private void savePreference(String preference, boolean value) {
        SharedPreferences.Editor editor = mSettings.edit();
        editor.putBoolean(preference, value);
        editor.commit();
    }

    private void savePreference(String preference, int value) {
        SharedPreferences.Editor editor = mSettings.edit();
        editor.putInt(preference, value);
        editor.commit();
    }
} public class Rule extends FileItem {

    private static final String RULES_SEPARATOR = "\n";

    public static final int STARTS_WITH = 0;
    public static final int ENDS_WITH = 1;
    public static final int EQUAL = 2;

    public static Rule[] createRules(Context context, String str) {
        List<Rule> list = createRulesAsList(context, str);
        return list.toArray(new Rule[list.size()]);
    }

    public static List<Rule> createRulesAsList(Context context, String str) {
        if (str == null || "".equals(str.trim())) {
            return new ArrayList<Rule>();
        }
        String[] rulesStr = str.split(RULES_SEPARATOR);
        List<Rule> list = new ArrayList<Rule>();
        for (int i = 0; i < rulesStr.length; i++) {
            if (rulesStr[i] != null && !"".equals(rulesStr[i].trim())) {
                list.add(new Rule(context, rulesStr[i]));
            }
        }
        return list;
    }

    public static String storeRules(List<Rule> rules) {
        if (rules == null) {
            return "";
        }
        return storeRules(rules.toArray(new Rule[rules.size()]));
    }

    public static String storeRules(Rule[] rules) {
        if (rules == null || rules.length == 0) {
            return "";
        }
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < rules.length; i++) {
            sb.append(rules[i].getType() + rules[i].getOriginalName());
            if (i < rules.length - 1) {
                sb.append(RULES_SEPARATOR);
            }
        }
        return sb.toString();
    }

    private int mType;

    public Rule(Context context, String name, int type) {
        super(name, name, null, false);
        this.mType = type;
        setPath(getTypeString(context));
    }

    private Rule(Context context, String str) {
        super(str.substring(1), str.substring(1), null, false);
        this.mType = Integer.parseInt(new String(new char[] { str.charAt(0) }));
        setPath(getTypeString(context));
    }

    public int getType() {
        return mType;
    }

    @Override
    public String getShortPath() {
        return getPath() + " " + getKey();
    }

    public String getOriginalName() {
        return super.getName();
    }

    @Override
    public String getName() {
        String name = super.getName();
        switch (mType) {
            case STARTS_WITH :
                name = name + "*";
                break;
            case ENDS_WITH :
                name = "*" + name;
                break;
        }
        return name;
    }

    private String getTypeString(Context context) {
        Resources res = context.getResources();
        switch (mType) {
            case STARTS_WITH :
                return res.getString(R.string.rule_starts_with);
            case ENDS_WITH :
                return res.getString(R.string.rule_ends_with);
            case EQUAL :
                return res.getString(R.string.rule_equals);
        }
        return null;
    }

    public boolean apply(String fileName) {
        String name = getKey();
        switch (mType) {
            case STARTS_WITH :
                return fileName.startsWith(name);
            case ENDS_WITH :
                return fileName.endsWith(name);
            case EQUAL :
                return fileName.equals(name);
        }
        return false;
    }
} public class FileItem implements Serializable {

    private String mKey;
    private String mName;
    private String mPath;
    private String mMd5;
    private boolean mDelete;
    private long mSize = -1;
    private int mImageAttr = 0;
    private int mImage = 0;

    public FileItem(String key, String name, String path, boolean delete) {
        mKey = key;
        mName = name;
        mPath = path;
        mDelete = delete;
    }

    public String getKey() {
        return mKey;
    }

    public String getName() {
        return mName;
    }

    public String getPath() {
        return mPath;
    }

    protected void setPath(String path) {
        mPath = path;
    }

    protected void setName(String name) {
        mName = name;
    }

    public String getShortPath() {
        return mPath.substring(0, mPath.lastIndexOf("/") + 1);
    }

    public void setDelete(boolean delete) {
        mDelete = delete;
    }

    public boolean isDelete() {
        return mDelete;
    }

    public void setMd5(String md5) {
        mMd5 = md5;
    }

    public String getMd5() {
        return mMd5;
    }

    public void setSize(long size) {
        mSize = size;
    }

    public long getSize() {
        return mSize;
    }

    public void setImage(int image) {
        mImage = image;
    }

    public int getImage() {
        return mImage;
    }

    public void setImageAttr(int imageAttr) {
        mImageAttr = imageAttr;
    }

    public int getImageAttr() {
        return mImageAttr;
    }

    public boolean isZip() {
        return mKey.endsWith(".zip") || mPath.endsWith(".zip");
    }

    public boolean isScript() {
        return mKey.endsWith(".sh") || mPath.endsWith(".sh");
    }
} public class FileItemStore {

    public interface FileItemStoreListener {

        public void storeChanged();
    }

    private static FileItemStoreListener sListener;
    private static List<FileItem> sList = new ArrayList<FileItem>();

    public static void setFileItemStoreListener(FileItemStoreListener listener) {
        sListener = listener;
    }

    public static int size() {
        return sList.size();
    }

    public static List<FileItem> getItems() {
        return sList;
    }

    public static String[] getPaths() {
        String[] paths = new String[sList.size()];
        int i = 0;
        for (FileItem item : sList) {
            paths[i] = item.getPath();
            i++;
        }
        return paths;
    }

    public static FileItem getItem(int i) {
        return sList.get(i);
    }

    public static void addItem(String realPath, String sdcardPath, boolean delete, String zipPosition) {

        sList.remove(realPath);

        FileItem item = new FileItem(realPath, sdcardPath.substring(sdcardPath.lastIndexOf("/") + 1),
                sdcardPath, delete);

        if ("first".equals(zipPosition)) {
            sList.add(0, item);
        } else {
            sList.add(item);
        }

        if (sListener != null) {
            sListener.storeChanged();
        }
    }

    public static void removeItems() {
        sList.clear();
        if (sListener != null) {
            sListener.storeChanged();
        }
    }

    public static void removeItem(String key) {
        int size = size(), i = 0;
        for (; i < size; i++) {
            if (key.equals(sList.get(i).getKey())) {
                sList.remove(i);
                return;
            }
        }
        if (sListener != null) {
            sListener.storeChanged();
        }
    }

    public static void move(int from, int to) {
        if (from == to)
            return;
        FileItem toMove = sList.get(from);
        while (sList.indexOf(toMove) != to) {
            int i = sList.indexOf(toMove);
            Collections.swap(sList, i, to < from ? i - 1 : i + 1);
        }
        if (sListener != null) {
            sListener.storeChanged();
        }
    }

    private FileItemStore() {
    }
} public interface FileItemStoreListener {

    public void storeChanged();
} public class FileItemsAdapter extends ArrayAdapter<FileItem> {

    public interface FileItemsAdapterHolder {

        public boolean useDragAndDrop();

        public boolean canRemove();

        public boolean showPath();

        public boolean showSize();

        public boolean showDate();

        public int getItemLayoutId();
    }

    private UIPlugin mUiPlugin;
    private FileItemsAdapterHolder mHolder;

    public FileItemsAdapter(Core core, FileItemsAdapterHolder holder, List<FileItem> items) {
        super(core.getContext(), holder.getItemLayoutId(), items);

        mUiPlugin = (UIPlugin) core.getPlugin(Core.PLUGIN_UI);

        mHolder = holder;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        LinearLayout itemView;
        FileItem item = getItem(position);

        if (convertView == null) {
            itemView = new LinearLayout(getContext());
            LayoutInflater vi = (LayoutInflater) getContext().getSystemService(
                    Context.LAYOUT_INFLATER_SERVICE);
            vi.inflate(mHolder.getItemLayoutId(), itemView, true);
        } else {
            itemView = (LinearLayout) convertView;
        }
        TextView title = (TextView) itemView.findViewById(R.id.title);
        TextView summary = (TextView) itemView.findViewById(R.id.summary);

        title.setText(item.getName());

        String summaryStr = "";
        if (mHolder.showPath()) {
            summaryStr += item.getShortPath();
            if (mHolder.showSize() || mHolder.showDate()) {
                summaryStr += ", ";
            }
        }
        if (mHolder.showSize()) {
            if (item.getSize() >= 0) {
                summaryStr += Strings.formatSize(item.getSize());
            } else if (item.getKey() != null) {
                File file = new File(item.getKey());
                if (file.exists()) {
                    summaryStr += Strings.formatSize(file.length());
                }
            }
            if (mHolder.showDate()) {
                summaryStr += ", ";
            }
        }
        if (mHolder.showDate()) {
            summaryStr += Strings.formatDate(new File(item.getKey()).lastModified());
        }
        if (item.isDelete()) {
            summaryStr += " " + getContext().getResources().getString(R.string.to_be_deleted);
        }
        summary.setText(summaryStr);

        if (!mHolder.useDragAndDrop()) {
            itemView.findViewById(R.id.grabber).setVisibility(View.GONE);
        }
        if (!mHolder.canRemove()) {
            ((ImageView)itemView.findViewById(R.id.trash)).setImageDrawable(null);
            itemView.findViewById(R.id.trash_separator).setVisibility(View.GONE);
        }
        if (item.getImage() != 0) {
            ((ImageView) itemView.findViewById(R.id.grabber)).setImageDrawable(getContext()
                    .getResources().getDrawable(item.getImage()));
        } else if (item.getImageAttr() != 0) {
            TypedArray ta = getContext().getTheme().obtainStyledAttributes(new int[] {item.getImageAttr()});
            ((ImageView) itemView.findViewById(R.id.grabber)).setImageDrawable(ta.getDrawable(0));
            ta.recycle();
        }

        mUiPlugin.redraw(itemView);

        return itemView;
    }
} public interface FileItemsAdapterHolder {

    public boolean useDragAndDrop();

    public boolean canRemove();

    public boolean showPath();

    public boolean showSize();

    public boolean showDate();

    public int getItemLayoutId();
} public abstract class UIFragment extends Fragment implements IFragment, UIInterface, Serializable {

    private Core mCore;
    private View mMainView;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {

        mCore = CoreFactory.getCore();

        ((UIPlugin) mCore.getPlugin(Core.PLUGIN_UI)).registerUI(this);

        View rootView = inflater.inflate(getContentViewId(), container, false);

        mMainView = getMainView(rootView);

        create(savedInstanceState == null);

        getActivity().setTitle(getTitle());

        if (savedInstanceState != null) {
            restore(savedInstanceState);
        }

        redraw();

        return rootView;
    }

    public abstract int getTitle();

    @Override
    public void setTheme(int resid) {
    }

    @Override
    public void redraw() {
        ((UIPlugin) mCore.getPlugin(Core.PLUGIN_UI)).redraw(this);
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        save(outState);
    }

    @Override
    public View getMainView() {
        return mMainView;
    }

    @Override
    public void onPrepareOptionsMenu(Menu menu) {
    }

    protected Core getCore() {
        return mCore;
    }

} public class Dialog extends android.app.Dialog {

    public interface OnDialogClosedListener {

        public void dialogOk();

        public void dialogCancel();
    }

    public interface WizardListener {

        public void wizardNextStep();

        public void wizardCancelled();

        public void wizardFinished();

        public boolean wizardIsLastStep();

        public boolean wizardIsContinueEnabled();
    }

    public static void alert(Context context, int messageId, OnDialogClosedListener listener) {
        alert(context, context.getResources().getString(messageId), listener);
    }

    public static void alert(Context context, String message, OnDialogClosedListener listener) {
        dialog(context, message, R.string.alert_title, false, listener);
    }

    public static void error(Context context, int messageId, OnDialogClosedListener listener) {
        error(context, context.getResources().getString(messageId), listener);
    }

    public static void error(Context context, String message, OnDialogClosedListener listener) {
        dialog(context, message, R.string.error_title, false, listener);
    }

    public static void dialog(Context context, String message, int titleId, boolean showCancel,
            final OnDialogClosedListener listener) {
        dialog(context, message, titleId, showCancel, android.R.string.ok, android.R.string.cancel,
                listener);
    }

    public static void dialog(Context context, String message, int titleId, boolean showCancel,
            int okId, int cancelId, final OnDialogClosedListener listener) {
        AlertDialog.Builder alert = new AlertDialog.Builder(context);
        alert.setTitle(titleId);
        alert.setMessage(message);
        alert.setCancelable(false);
        alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {

            public void onClick(DialogInterface dialog, int whichButton) {
                dialog.dismiss();

                if (listener != null) {
                    listener.dialogOk();
                }
            }
        });
        if (showCancel) {
            alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {

                public void onClick(DialogInterface dialog, int whichButton) {
                    dialog.dismiss();

                    if (listener != null) {
                        listener.dialogCancel();
                    }
                }
            });
        }
        alert.show();
    }

    public static AlertDialog wizard(Context context, int titleId, int layoutId, final WizardListener listener) {
        return wizard(context, titleId, layoutId, R.string.wizard_finish, listener);
    }

    public static AlertDialog wizard(Context context, int titleId, int layoutId, final int finishId, final WizardListener listener) {
        final AlertDialog.Builder builder = new AlertDialog.Builder(context);
        builder.setTitle(titleId);
        builder.setCancelable(false);
        View view = LayoutInflater.from(context).inflate(layoutId, null);
        builder.setView(view);
        builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {

            public void onClick(DialogInterface dialog, int whichButton) {
                dialog.dismiss();

                listener.wizardCancelled();
            }
        });
        builder.setPositiveButton(R.string.wizard_continue, new DialogInterface.OnClickListener() {

            @Override
            public void onClick(DialogInterface dialog, int whichButton) {
            }
        });
        final AlertDialog alertDialog = builder.create();
        alertDialog.setOnShowListener(new DialogInterface.OnShowListener() {

            @Override
            public void onShow(DialogInterface dialog) {
                Button b = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
                b.setOnClickListener(new View.OnClickListener() {

                    @Override
                    public void onClick(View v) {
                        if (listener.wizardIsLastStep()) {
                            alertDialog.dismiss();
                            listener.wizardFinished();
                        } else {
                            listener.wizardNextStep();
                            Button button = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
                            if (listener.wizardIsLastStep()) {
                                button.setText(finishId);
                            }
                            button.setEnabled(listener.wizardIsContinueEnabled());
                            button.invalidate();
                        }
                    }
                });
            }
        });
        alertDialog.show();
        listener.wizardNextStep();
        Button button = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
        button.setText(listener.wizardIsLastStep() ? finishId : R.string.wizard_continue);
        button.setEnabled(listener.wizardIsContinueEnabled());
        button.invalidate();
        return alertDialog;
    }

    public static AlertDialog customDialog(Context context, int layoutId, int titleId, int okButtonTextId,
            boolean showCancel, final OnDialogClosedListener listener) {
        final AlertDialog.Builder builder = new AlertDialog.Builder(context);
        builder.setTitle(titleId);
        View view = LayoutInflater.from(context).inflate(layoutId, null);
        builder.setView(view);
        builder.setCancelable(false);
        builder.setPositiveButton(okButtonTextId, new DialogInterface.OnClickListener() {

            public void onClick(DialogInterface dialog, int whichButton) {
                dialog.dismiss();

                if (listener != null) {
                    listener.dialogOk();
                }
            }
        });
        if (showCancel) {
            builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {

                public void onClick(DialogInterface dialog, int whichButton) {
                    dialog.dismiss();

                    if (listener != null) {
                        listener.dialogCancel();
                    }
                }
            });
        }
        AlertDialog alertDialog = builder.create();
        alertDialog.show();
        return alertDialog;
    }

    public static void toast(final Context context, final int resourceId) {
        toast(context, context.getResources().getString(resourceId));
    }

    public static void toast(final Context context, final String message) {
        ((Activity) context).runOnUiThread(new Runnable() {

            public void run() {
                Toast.makeText(context, message, Toast.LENGTH_LONG).show();
            }
        });
    }

    private Dialog(Context context) {
        super(context);
    }
} public interface OnDialogClosedListener {

    public void dialogOk();

    public void dialogCancel();
} public class FolderPicker extends Dialog implements OnItemClickListener, OnClickListener {

    private ListView mFolders;
    private TextView mCurrentFolder;
    private Folder mPath;
    private Folder mFilePath;
    private File mRoot;
    private FolderAdapter mAdapter;
    private OnClickListener mListener;
    private boolean mAcceptFiles;
    private View mOkButton;
    private String[] mFileExtensions;
    private Drawable[] mFileDrawables;

    public FolderPicker(Context context, OnClickListener listener, String defaultFolder) {
        this(context, listener, defaultFolder, null, null, false);
    }

    public FolderPicker(Context context, OnClickListener listener, String defaultFolder,
            String[] fileExtensions, Drawable[] fileDrawables, boolean acceptFiles) {
        super(context);
        mListener = listener;
        mAcceptFiles = acceptFiles;
        mFileExtensions = fileExtensions;
        mFileDrawables = fileDrawables;
        setTitle(acceptFiles ? R.string.pick_file : R.string.pick_folder);
        setContentView(R.layout.picker_folders);

        mRoot = new File("/");

        mOkButton = findViewById(R.id.ok_btn);
        mOkButton.setOnClickListener(this);
        findViewById(R.id.cancel_btn).setOnClickListener(this);
        mCurrentFolder = (TextView) findViewById(R.id.current_folder);
        mCurrentFolder.setSelected(true);
        mFolders = (ListView) findViewById(R.id.folders);
        mFolders.setOnItemClickListener(this);

        mAdapter = new FolderAdapter();
        mFolders.setAdapter(mAdapter);
        if (defaultFolder != null) {
            mPath = new Folder(defaultFolder);
        }
        if (mPath == null || !mPath.exists()) {
            mPath = new Folder(Environment.getExternalStorageDirectory().getAbsolutePath());
        }
        updateAdapter();
    }

    public String getPath() {
        if (!mAcceptFiles) {
            return mPath.getAbsolutePath();
        } else if (mFilePath != null) {
            return mFilePath.getAbsolutePath();
        } else {
            return null;
        }
    }

    public void onClick(View v) {
        if (v == mOkButton && mListener != null) {
            mListener.onClick(this, DialogInterface.BUTTON_POSITIVE);
        }
        dismiss();
    }

    private void updateAdapter() {
        mCurrentFolder.setText(mPath.getAbsolutePath());
        mAdapter.clear();
        if (!mPath.equals(mRoot)) {
            mAdapter.add(new Folder(mPath, true));
        }
        File[] dirs = mPath.listFiles(mDirFilter);
        Arrays.sort(dirs);
        for (int i = 0; i < dirs.length; i++) {
            mAdapter.add(new Folder(dirs[i]));
        }
        if (mAcceptFiles) {
            File[] files = mPath.listFiles(mFileFilter);
            Arrays.sort(files);
            for (int i = 0; i < files.length; i++) {
                if (mFileExtensions == null) {
                    mAdapter.add(new Folder(files[i]));
                } else {
                    for (int j=0;j<mFileExtensions.length;j++) {
                        if (files[i].getName().endsWith(mFileExtensions[j])) {
                            mAdapter.add(new Folder(files[i],
                                    mFileDrawables != null ? mFileDrawables[j] : null));
                        }
                    }
                }
            }
        }
        mAdapter.notifyDataSetChanged();
        mFolders.setSelection(0);
        mFolders.startLayoutAnimation();
    }

    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        if (mAcceptFiles) {
            Folder item = (Folder) mAdapter.getItem(position);
            if (item.isDirectory()) {
                mPath = item;
                updateAdapter();
                mFilePath = null;
            } else {
                mCurrentFolder.setText(item.getAbsolutePath());
                mFilePath = item;
                mListener.onClick(this, DialogInterface.BUTTON_POSITIVE);
                dismiss();
            }
        } else {
            mPath = (Folder) mAdapter.getItem(position);
            updateAdapter();
        }
    }

    private FileFilter mDirFilter = new FileFilter() {

        public boolean accept(File file) {
            return file.isDirectory();
        }
    };

    private FileFilter mFileFilter = new FileFilter() {

        public boolean accept(File file) {
            return file.isFile();
        }
    };

    class FolderAdapter extends BaseAdapter {

        ArrayList<Folder> mFolders = new ArrayList<Folder>();
        LayoutInflater mInflater = LayoutInflater.from(getContext());
        private Drawable mFileDrawable;
        private Drawable mFolderDrawable;

        public FolderAdapter() {
            TypedArray ta = getContext().getTheme().obtainStyledAttributes(new int[] {R.attr.fileIcon});
            mFileDrawable = ta.getDrawable(0);
            ta.recycle();
            ta = getContext().getTheme().obtainStyledAttributes(new int[] {R.attr.folderIcon});
            mFolderDrawable = ta.getDrawable(0);
            ta.recycle();
        }

        public int getCount() {
            return mFolders.size();
        }

        public void add(Folder folder) {
            mFolders.add(folder);
        }

        public void clear() {
            mFolders.clear();
        }

        public Object getItem(int position) {
            return mFolders.get(position);
        }

        public long getItemId(int position) {
            return position;
        }

        public View getView(int position, View convertView, ViewGroup parent) {
            View v = mInflater.inflate(R.layout.picker_folder, parent, false);
            Folder folder = mFolders.get(position);
            TextView name = (TextView) v.findViewById(R.id.folder_name);

            Drawable drawable = null;
            if (folder.isParent) {
                name.setText("[..]");
                drawable = mFolderDrawable;
            } else {
                name.setText(folder.getName());
                if (folder.isDirectory()) {
                    drawable = mFolderDrawable;
                } else if (folder.drawable != null) {
                    drawable = folder.drawable;
                } else {
                    drawable = mFileDrawable;
                }
            }
            if (VERSION.SDK_INT > 15) {
                v.findViewById(R.id.folder_icon).setBackground(drawable);
            } else {
                v.findViewById(R.id.folder_icon).setBackgroundDrawable(drawable);
            }
            return v;
        }
    }

    @SuppressWarnings("serial")
    class Folder extends File {

        protected Drawable drawable;
        protected boolean isParent;

        public Folder(File file) {
            super(file.getAbsolutePath());
        }

        public Folder(File file, Drawable drawable) {
            super(file.getAbsolutePath());
            this.drawable = drawable;
        }

        public Folder(File file, boolean unused) {
            super(file.getParent() == null ? "/" : file.getParent());
            isParent = true;
        }

        public Folder(String path) {
            super(path);
        }
    }
} public class RecoveryPicker extends Dialog implements OnItemClickListener {

    public static final int ZIP = 0;
    public static final int TWRP = 1;

    public interface RecoveryPickerClicked {

        public void onRecoveryPickerClicked(int id);
    }

    private ListView mList;
    private RecoveryPickerClicked mListener;

    public RecoveryPicker(Context context, RecoveryPickerClicked listener) {
        super(context);
        setTitle(R.string.picker_recovery_title);
        setContentView(R.layout.picker_recovery);

        mListener = listener;

        mList = (ListView) findViewById(R.id.list);
        mList.setOnItemClickListener(this);
    }

    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        mListener.onRecoveryPickerClicked(position);
        dismiss();
    }
} public interface RecoveryPickerClicked {

    public void onRecoveryPickerClicked(int id);
} public class DragSortListView extends ListView {

    private UIInterface mUiInterface;
    /**
     * The View that floats above the ListView and represents
     * the dragged item.
     */
    private View mFloatView;

    /**
     * The float View location. First based on touch location
     * and given deltaX and deltaY. Then restricted by callback
     * to FloatViewManager.onDragFloatView(). Finally restricted
     * by bounds of DSLV.
     */
    private Point mFloatLoc = new Point();

    private Point mTouchLoc = new Point();

    /**
     * The middle (in the y-direction) of the floating View.
     */
    private int mFloatViewMid;

    /**
     * Flag to make sure float View isn't measured twice
     */
    private boolean mFloatViewOnMeasured = false;

    /**
     * Watch the Adapter for data changes. Cancel a drag if
     * coincident with a change.
     */
    private DataSetObserver mObserver;

    /**
     * Transparency for the floating View (XML attribute).
     */
    private float mFloatAlpha = 1.0f;
    private float mCurrFloatAlpha = 1.0f;

    /**
     * While drag-sorting, the current position of the floating
     * View. If dropped, the dragged item will land in this position.
     */
    private int mFloatPos;

    /**
     * The first expanded ListView position that helps represent
     * the drop slot tracking the floating View.
     */
    private int mFirstExpPos;

    /**
     * The second expanded ListView position that helps represent
     * the drop slot tracking the floating View. This can equal
     * mFirstExpPos if there is no slide shuffle occurring; otherwise
     * it is equal to mFirstExpPos + 1.
     */
    private int mSecondExpPos;

    /**
     * Flag set if slide shuffling is enabled.
     */
    private boolean mAnimate = false;

    /**
     * The user dragged from this position.
     */
    private int mSrcPos;

    /**
     * Offset (in x) within the dragged item at which the user
     * picked it up (or first touched down with the digitalis).
     */
    private int mDragDeltaX;

    /**
     * Offset (in y) within the dragged item at which the user
     * picked it up (or first touched down with the digitalis).
     */
    private int mDragDeltaY;

    /**
     * The difference (in x) between screen coordinates and coordinates
     * in this view.
     */
    private int mOffsetX;

    /**
     * The difference (in y) between screen coordinates and coordinates
     * in this view.
     */
    private int mOffsetY;

    /**
     * A listener that receives callbacks whenever the floating View
     * hovers over a new position.
     */
    private DragListener mDragListener;

    /**
     * A listener that receives a callback when the floating View
     * is dropped.
     */
    private DropListener mDropListener;

    /**
     * A listener that receives a callback when the floating View
     * (or more precisely the originally dragged item) is removed
     * by one of the provided gestures.
     */
    private RemoveListener mRemoveListener;

    /**
     * Enable/Disable item dragging
     * 
     * @attr name dslv:drag_enabled
     */
    private boolean mDragEnabled = true;

    /**
     * Drag state enum.
     */
    private final static int IDLE = 0;
    private final static int REMOVING = 1;
    private final static int DROPPING = 2;
    private final static int STOPPED = 3;
    private final static int DRAGGING = 4;

    private int mDragState = IDLE;

    /**
     * Height in pixels to which the originally dragged item
     * is collapsed during a drag-sort. Currently, this value
     * must be greater than zero.
     */
    private int mItemHeightCollapsed = 1;

    /**
     * Height of the floating View. Stored for the purpose of
     * providing the tracking drop slot.
     */
    private int mFloatViewHeight;

    /**
     * Convenience member. See above.
     */
    private int mFloatViewHeightHalf;

    /**
     * Save the given width spec for use in measuring children
     */
    private int mWidthMeasureSpec = 0;

    /**
     * Sample Views ultimately used for calculating the height
     * of ListView items that are off-screen.
     */
    private View[] mSampleViewTypes = new View[1];

    /**
     * Drag-scroll encapsulator!
     */
    private DragScroller mDragScroller;

    /**
     * Determines the start of the upward drag-scroll region
     * at the top of the ListView. Specified by a fraction
     * of the ListView height, thus screen resolution agnostic.
     */
    private float mDragUpScrollStartFrac = 1.0f / 3.0f;

    /**
     * Determines the start of the downward drag-scroll region
     * at the bottom of the ListView. Specified by a fraction
     * of the ListView height, thus screen resolution agnostic.
     */
    private float mDragDownScrollStartFrac = 1.0f / 3.0f;

    /**
     * The following are calculated from the above fracs.
     */
    private int mUpScrollStartY;
    private int mDownScrollStartY;
    private float mDownScrollStartYF;
    private float mUpScrollStartYF;

    /**
     * Calculated from above above and current ListView height.
     */
    private float mDragUpScrollHeight;

    /**
     * Calculated from above above and current ListView height.
     */
    private float mDragDownScrollHeight;

    /**
     * Maximum drag-scroll speed in pixels per ms. Only used with
     * default linear drag-scroll profile.
     */
    private float mMaxScrollSpeed = 0.5f;

    /**
     * Defines the scroll speed during a drag-scroll. User can
     * provide their own; this default is a simple linear profile
     * where scroll speed increases linearly as the floating View
     * nears the top/bottom of the ListView.
     */
    private DragScrollProfile mScrollProfile = new DragScrollProfile() {

        @Override
        public float getSpeed(float w, long t) {
            return mMaxScrollSpeed * w;
        }
    };

    /**
     * Current touch x.
     */
    private int mX;

    /**
     * Current touch y.
     */
    private int mY;

    /**
     * Last touch x.
     */
    private int mLastX;

    /**
     * Last touch y.
     */
    private int mLastY;

    /**
     * The touch y-coord at which drag started
     */
    private int mDragStartY;

    /**
     * Drag flag bit. Floating View can move in the positive
     * x direction.
     */
    public final static int DRAG_POS_X = 0x1;

    /**
     * Drag flag bit. Floating View can move in the negative
     * x direction.
     */
    public final static int DRAG_NEG_X = 0x2;

    /**
     * Drag flag bit. Floating View can move in the positive
     * y direction. This is subtle. What this actually means is
     * that, if enabled, the floating View can be dragged below its starting
     * position. Remove in favor of upper-bounding item position?
     */
    public final static int DRAG_POS_Y = 0x4;

    /**
     * Drag flag bit. Floating View can move in the negative
     * y direction. This is subtle. What this actually means is
     * that the floating View can be dragged above its starting
     * position. Remove in favor of lower-bounding item position?
     */
    public final static int DRAG_NEG_Y = 0x8;

    /**
     * Flags that determine limits on the motion of the
     * floating View. See flags above.
     */
    private int mDragFlags = 0;

    /**
     * Last call to an on*TouchEvent was a call to
     * onInterceptTouchEvent.
     */
    private boolean mLastCallWasIntercept = false;

    /**
     * A touch event is in progress.
     */
    private boolean mInTouchEvent = false;

    /**
     * Let the user customize the floating View.
     */
    private FloatViewManager mFloatViewManager = null;

    /**
     * Given to ListView to cancel its action when a drag-sort
     * begins.
     */
    private MotionEvent mCancelEvent;

    /**
     * Enum telling where to cancel the ListView action when a
     * drag-sort begins
     */
    private static final int NO_CANCEL = 0;
    private static final int ON_TOUCH_EVENT = 1;
    private static final int ON_INTERCEPT_TOUCH_EVENT = 2;

    /**
     * Where to cancel the ListView action when a
     * drag-sort begins
     */
    private int mCancelMethod = NO_CANCEL;

    /**
     * Determines when a slide shuffle animation starts. That is,
     * defines how close to the edge of the drop slot the floating
     * View must be to initiate the slide.
     */
    private float mSlideRegionFrac = 0.25f;

    /**
     * Number between 0 and 1 indicating the relative location of
     * a sliding item (only used if drag-sort animations
     * are turned on). Nearly 1 means the item is 
     * at the top of the slide region (nearly full blank item
     * is directly below).
     */
    private float mSlideFrac = 0.0f;

    /**
     * Wraps the user-provided ListAdapter. This is used to wrap each
     * item View given by the user inside another View (currenly
     * a RelativeLayout) which
     * expands and collapses to simulate the item shuffling.
     */
    private AdapterWrapper mAdapterWrapper;

    /**
     * Turn on custom debugger.
     */
    private boolean mTrackDragSort = false;

    /**
     * Debugging class.
     */
    private DragSortTracker mDragSortTracker;

    /**
     * Needed for adjusting item heights from within layoutChildren
     */
    private boolean mBlockLayoutRequests = false;

    /**
     * Set to true when a down event happens during drag sort;
     * for example, when drag finish animations are
     * playing.
     */
    private boolean mIgnoreTouchEvent = false;

    /**
     * Caches DragSortItemView child heights. Sometimes DSLV has to
     * know the height of an offscreen item. Since ListView virtualizes
     * these, DSLV must get the item from the ListAdapter to obtain
     * its height. That process can be expensive, but often the same
     * offscreen item will be requested many times in a row. Once an
     * offscreen item height is calculated, we cache it in this guy.
     * Actually, we cache the height of the child of the
     * DragSortItemView since the item height changes often during a
     * drag-sort.
     */
    private static final int sCacheSize = 3;
    private HeightCache mChildHeightCache = new HeightCache(sCacheSize);

    private RemoveAnimator mRemoveAnimator;

    private LiftAnimator mLiftAnimator;

    private DropAnimator mDropAnimator;

    private boolean mUseRemoveVelocity;
    private float mRemoveVelocityX = 0;

    private int mFloatBGColor = Color.BLACK;

    public DragSortListView(Context context, AttributeSet attrs) {
        super(context, attrs);

        int defaultDuration = 150;
        int removeAnimDuration = defaultDuration; // ms
        int dropAnimDuration = defaultDuration; // ms

        if (attrs != null) {
            TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.DragSortListView,
                    0, 0);

            mItemHeightCollapsed = Math.max(1,
                    a.getDimensionPixelSize(R.styleable.DragSortListView_collapsed_height, 1));

            mTrackDragSort = a.getBoolean(R.styleable.DragSortListView_track_drag_sort, false);

            if (mTrackDragSort) {
                mDragSortTracker = new DragSortTracker();
            }

            // alpha between 0 and 255, 0=transparent, 255=opaque
            mFloatAlpha = a.getFloat(R.styleable.DragSortListView_float_alpha, mFloatAlpha);
            mCurrFloatAlpha = mFloatAlpha;

            mDragEnabled = a.getBoolean(R.styleable.DragSortListView_drag_enabled, mDragEnabled);

            mSlideRegionFrac = Math.max(0.0f, Math.min(1.0f,
                    1.0f - a.getFloat(R.styleable.DragSortListView_slide_shuffle_speed, 0.75f)));

            mAnimate = mSlideRegionFrac > 0.0f;

            float frac = a.getFloat(R.styleable.DragSortListView_drag_scroll_start,
                    mDragUpScrollStartFrac);

            setDragScrollStart(frac);

            mMaxScrollSpeed = a.getFloat(R.styleable.DragSortListView_max_drag_scroll_speed,
                    mMaxScrollSpeed);

            removeAnimDuration = a.getInt(R.styleable.DragSortListView_remove_animation_duration,
                    removeAnimDuration);

            dropAnimDuration = a.getInt(R.styleable.DragSortListView_drop_animation_duration,
                    dropAnimDuration);

            boolean useDefault = a.getBoolean(R.styleable.DragSortListView_use_default_controller,
                    true);

            if (useDefault) {
                boolean removeEnabled = a.getBoolean(R.styleable.DragSortListView_remove_enabled,
                        false);
                int removeMode = a.getInt(R.styleable.DragSortListView_remove_mode,
                        DragSortController.FLING_REMOVE);
                boolean sortEnabled = a.getBoolean(R.styleable.DragSortListView_sort_enabled, true);
                int dragInitMode = a.getInt(R.styleable.DragSortListView_drag_start_mode,
                        DragSortController.ON_DOWN);
                int dragHandleId = a.getResourceId(R.styleable.DragSortListView_drag_handle_id, 0);
                int flingHandleId = a
                        .getResourceId(R.styleable.DragSortListView_fling_handle_id, 0);
                int clickRemoveId = a
                        .getResourceId(R.styleable.DragSortListView_click_remove_id, 0);
                mFloatBGColor = a.getColor(R.styleable.DragSortListView_float_background_color,
                        Color.BLACK);

                DragSortController controller = new DragSortController(this, dragHandleId,
                        dragInitMode, removeMode, clickRemoveId, flingHandleId);
                controller.setRemoveEnabled(removeEnabled);
                controller.setSortEnabled(sortEnabled);
                controller.setBackgroundColor(mFloatBGColor);

                mFloatViewManager = controller;
                setOnTouchListener(controller);
            }

            a.recycle();
        }

        mDragScroller = new DragScroller();

        float smoothness = 0.5f;
        if (removeAnimDuration > 0) {
            mRemoveAnimator = new RemoveAnimator(smoothness, removeAnimDuration);
        }
        // mLiftAnimator = new LiftAnimator(smoothness, 100);
        if (dropAnimDuration > 0) {
            mDropAnimator = new DropAnimator(smoothness, dropAnimDuration);
        }

        mCancelEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_CANCEL, 0f, 0f, 0f, 0f, 0, 0f,
                0f, 0, 0);

        // construct the dataset observer
        mObserver = new DataSetObserver() {

            private void cancel() {
                if (mDragState == DRAGGING) {
                    cancelDrag();
                }
            }

            @Override
            public void onChanged() {
                cancel();
            }

            @Override
            public void onInvalidated() {
                cancel();
            }
        };
    }

    public void setUiInterface(UIInterface uiInterface) {
        mUiInterface = uiInterface;
    }

    /**
     * Usually called from a FloatViewManager. The float alpha
     * will be reset to the xml-defined value every time a drag
     * is stopped.
     */
    public void setFloatAlpha(float alpha) {
        mCurrFloatAlpha = alpha;
    }

    public float getFloatAlpha() {
        return mCurrFloatAlpha;
    }

    /**
     * Set maximum drag scroll speed in positions/second. Only applies
     * if using default ScrollSpeedProfile.
     * 
     * @param max Maximum scroll speed.
     */
    public void setMaxScrollSpeed(float max) {
        mMaxScrollSpeed = max;
    }

    /**
     * For each DragSortListView Listener interface implemented by
     * <code>adapter</code>, this method calls the appropriate
     * set*Listener method with <code>adapter</code> as the argument.
     * 
     * @param adapter The ListAdapter providing data to back
     * DragSortListView.
     *
     * @see android.widget.ListView#setAdapter(android.widget.ListAdapter)
     */
    @Override
    public void setAdapter(ListAdapter adapter) {
        if (adapter != null) {
            mAdapterWrapper = new AdapterWrapper(adapter);
            adapter.registerDataSetObserver(mObserver);

            if (adapter instanceof DropListener) {
                setDropListener((DropListener) adapter);
            }
            if (adapter instanceof DragListener) {
                setDragListener((DragListener) adapter);
            }
            if (adapter instanceof RemoveListener) {
                setRemoveListener((RemoveListener) adapter);
            }
        } else {
            mAdapterWrapper = null;
        }

        super.setAdapter(mAdapterWrapper);
    }

    /**
     * As opposed to {@link ListView#getAdapter()}, which returns
     * a heavily wrapped ListAdapter (DragSortListView wraps the
     * input ListAdapter {\emph and} ListView wraps the wrapped one).
     *
     * @return The ListAdapter set as the argument of {@link setAdapter()}
     */
    public ListAdapter getInputAdapter() {
        if (mAdapterWrapper == null) {
            return null;
        } else {
            return mAdapterWrapper.getAdapter();
        }
    }

    private class AdapterWrapper extends BaseAdapter {

        private ListAdapter mAdapter;

        public AdapterWrapper(ListAdapter adapter) {
            super();
            mAdapter = adapter;

            mAdapter.registerDataSetObserver(new DataSetObserver() {

                public void onChanged() {
                    notifyDataSetChanged();
                }

                public void onInvalidated() {
                    notifyDataSetInvalidated();
                }
            });
        }

        public ListAdapter getAdapter() {
            return mAdapter;
        }

        @Override
        public long getItemId(int position) {
            return mAdapter.getItemId(position);
        }

        @Override
        public Object getItem(int position) {
            return mAdapter.getItem(position);
        }

        @Override
        public int getCount() {
            return mAdapter.getCount();
        }

        @Override
        public boolean areAllItemsEnabled() {
            return mAdapter.areAllItemsEnabled();
        }

        @Override
        public boolean isEnabled(int position) {
            return mAdapter.isEnabled(position);
        }

        @Override
        public int getItemViewType(int position) {
            return mAdapter.getItemViewType(position);
        }

        @Override
        public int getViewTypeCount() {
            return mAdapter.getViewTypeCount();
        }

        @Override
        public boolean hasStableIds() {
            return mAdapter.hasStableIds();
        }

        @Override
        public boolean isEmpty() {
            return mAdapter.isEmpty();
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {

            DragSortItemView v;
            View child;
            // Log.d("mobeta",
            // "getView: position="+position+" convertView="+convertView);
            if (convertView != null) {
                v = (DragSortItemView) convertView;
                View oldChild = v.getChildAt(0);

                child = mAdapter.getView(position, oldChild, DragSortListView.this);
                if (child != oldChild) {
                    // shouldn't get here if user is reusing convertViews
                    // properly
                    if (oldChild != null) {
                        v.removeViewAt(0);
                    }
                    v.addView(child);
                }
            } else {
                child = mAdapter.getView(position, null, DragSortListView.this);
                if (child instanceof Checkable) {
                    v = new DragSortItemViewCheckable(getContext());
                } else {
                    v = new DragSortItemView(getContext());
                }
                v.setSelectableColor(mFloatBGColor);
                v.setLayoutParams(new AbsListView.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
                        ViewGroup.LayoutParams.WRAP_CONTENT));
                v.addView(child);
            }

            // Set the correct item height given drag state; passed
            // View needs to be measured if measurement is required.
            adjustItem(position + getHeaderViewsCount(), v, true);

            return v;
        }
    }

    private void drawDivider(int expPosition, Canvas canvas) {

        final Drawable divider = getDivider();
        final int dividerHeight = getDividerHeight();
        // Log.d("mobeta", "div="+divider+" divH="+dividerHeight);

        if (divider != null && dividerHeight != 0) {
            final ViewGroup expItem = (ViewGroup) getChildAt(expPosition
                    - getFirstVisiblePosition());
            if (expItem != null) {
                final int l = getPaddingLeft();
                final int r = getWidth() - getPaddingRight();
                final int t;
                final int b;

                final int childHeight = expItem.getChildAt(0).getHeight();

                if (expPosition > mSrcPos) {
                    t = expItem.getTop() + childHeight;
                    b = t + dividerHeight;
                } else {
                    b = expItem.getBottom() - childHeight;
                    t = b - dividerHeight;
                }
                // Log.d("mobeta", "l="+l+" t="+t+" r="+r+" b="+b);

                // Have to clip to support ColorDrawable on <= Gingerbread
                canvas.save();
                canvas.clipRect(l, t, r, b);
                divider.setBounds(l, t, r, b);
                divider.draw(canvas);
                canvas.restore();
            }
        }
    }

    @Override
    protected void dispatchDraw(Canvas canvas) {
        super.dispatchDraw(canvas);

        if (mDragState != IDLE) {
            // draw the divider over the expanded item
            if (mFirstExpPos != mSrcPos) {
                drawDivider(mFirstExpPos, canvas);
            }
            if (mSecondExpPos != mFirstExpPos && mSecondExpPos != mSrcPos) {
                drawDivider(mSecondExpPos, canvas);
            }
        }

        if (mFloatView != null) {
            // draw the float view over everything
            final int w = mFloatView.getWidth();
            final int h = mFloatView.getHeight();

            int x = mFloatLoc.x;

            int width = getWidth();
            if (x < 0)
                x = -x;
            float alphaMod;
            if (x < width) {
                alphaMod = ((float) (width - x)) / ((float) width);
                alphaMod *= alphaMod;
            } else {
                alphaMod = 0;
            }

            final int alpha = (int) (255f * mCurrFloatAlpha * alphaMod);

            canvas.save();
            // Log.d("mobeta", "clip rect bounds: " + canvas.getClipBounds());
            canvas.translate(mFloatLoc.x, mFloatLoc.y);
            canvas.clipRect(0, 0, w, h);

            // Log.d("mobeta", "clip rect bounds: " + canvas.getClipBounds());
            canvas.saveLayerAlpha(0, 0, w, h, alpha, Canvas.ALL_SAVE_FLAG);
            mFloatView.draw(canvas);
            canvas.restore();
            canvas.restore();
        }
    }

    private int getItemHeight(int position) {
        View v = getChildAt(position - getFirstVisiblePosition());

        if (v != null) {
            // item is onscreen, just get the height of the View
            return v.getHeight();
        } else {
            // item is offscreen. get child height and calculate
            // item height based on current shuffle state
            return calcItemHeight(position, getChildHeight(position));
        }
    }

    private void printPosData() {
        Log.d("mobeta", "mSrcPos=" + mSrcPos + " mFirstExpPos=" + mFirstExpPos + " mSecondExpPos="
                + mSecondExpPos);
    }

    private class HeightCache {

        private SparseIntArray mMap;
        private ArrayList<Integer> mOrder;
        private int mMaxSize;

        public HeightCache(int size) {
            mMap = new SparseIntArray(size);
            mOrder = new ArrayList<Integer>(size);
            mMaxSize = size;
        }

        /**
         * Add item height at position if doesn't already exist.
         */
        public void add(int position, int height) {
            int currHeight = mMap.get(position, -1);
            if (currHeight != height) {
                if (currHeight == -1) {
                    if (mMap.size() == mMaxSize) {
                        // remove oldest entry
                        mMap.delete(mOrder.remove(0));
                    }
                } else {
                    // move position to newest slot
                    mOrder.remove((Integer) position);
                }
                mMap.put(position, height);
                mOrder.add(position);
            }
        }

        public int get(int position) {
            return mMap.get(position, -1);
        }

        public void clear() {
            mMap.clear();
            mOrder.clear();
        }

    }

    /**
     * Get the shuffle edge for item at position when top of
     * item is at y-coord top. Assumes that current item heights
     * are consistent with current float view location and
     * thus expanded positions and slide fraction. i.e. Should not be
     * called between update of expanded positions/slide fraction
     * and layoutChildren.
     *
     * @param position 
     * @param top
     * @param height Height of item at position. If -1, this function
     * calculates this height.
     *
     * @return Shuffle line between position-1 and position (for
     * the given view of the list; that is, for when top of item at
     * position has y-coord of given `top`). If
     * floating View (treated as horizontal line) is dropped
     * immediately above this line, it lands in position-1. If
     * dropped immediately below this line, it lands in position.
     */
    private int getShuffleEdge(int position, int top) {

        final int numHeaders = getHeaderViewsCount();
        final int numFooters = getFooterViewsCount();

        // shuffle edges are defined between items that can be
        // dragged; there are N-1 of them if there are N draggable
        // items.

        if (position <= numHeaders || (position >= getCount() - numFooters)) {
            return top;
        }

        int divHeight = getDividerHeight();

        int edge;

        int maxBlankHeight = mFloatViewHeight - mItemHeightCollapsed;
        int childHeight = getChildHeight(position);
        int itemHeight = getItemHeight(position);

        // first calculate top of item given that floating View is
        // centered over src position
        int otop = top;
        if (mSecondExpPos <= mSrcPos) {
            // items are expanded on and/or above the source position

            if (position == mSecondExpPos && mFirstExpPos != mSecondExpPos) {
                if (position == mSrcPos) {
                    otop = top + itemHeight - mFloatViewHeight;
                } else {
                    int blankHeight = itemHeight - childHeight;
                    otop = top + blankHeight - maxBlankHeight;
                }
            } else if (position > mSecondExpPos && position <= mSrcPos) {
                otop = top - maxBlankHeight;
            }

        } else {
            // items are expanded on and/or below the source position

            if (position > mSrcPos && position <= mFirstExpPos) {
                otop = top + maxBlankHeight;
            } else if (position == mSecondExpPos && mFirstExpPos != mSecondExpPos) {
                int blankHeight = itemHeight - childHeight;
                otop = top + blankHeight;
            }
        }

        // otop is set
        if (position <= mSrcPos) {
            edge = otop + (mFloatViewHeight - divHeight - getChildHeight(position - 1)) / 2;
        } else {
            edge = otop + (childHeight - divHeight - mFloatViewHeight) / 2;
        }

        return edge;
    }

    private boolean updatePositions() {

        final int first = getFirstVisiblePosition();
        int startPos = mFirstExpPos;
        View startView = getChildAt(startPos - first);

        if (startView == null) {
            startPos = first + getChildCount() / 2;
            startView = getChildAt(startPos - first);
        }
        int startTop = startView.getTop();

        int itemHeight = startView.getHeight();

        int edge = getShuffleEdge(startPos, startTop);
        int lastEdge = edge;

        int divHeight = getDividerHeight();

        // Log.d("mobeta", "float mid="+mFloatViewMid);

        int itemPos = startPos;
        int itemTop = startTop;
        if (mFloatViewMid < edge) {
            // scanning up for float position
            // Log.d("mobeta", "    edge="+edge);
            while (itemPos >= 0) {
                itemPos--;
                itemHeight = getItemHeight(itemPos);

                if (itemPos == 0) {
                    edge = itemTop - divHeight - itemHeight;
                    break;
                }

                itemTop -= itemHeight + divHeight;
                edge = getShuffleEdge(itemPos, itemTop);
                // Log.d("mobeta", "    edge="+edge);

                if (mFloatViewMid >= edge) {
                    break;
                }

                lastEdge = edge;
            }
        } else {
            // scanning down for float position
            // Log.d("mobeta", "    edge="+edge);
            final int count = getCount();
            while (itemPos < count) {
                if (itemPos == count - 1) {
                    edge = itemTop + divHeight + itemHeight;
                    break;
                }

                itemTop += divHeight + itemHeight;
                itemHeight = getItemHeight(itemPos + 1);
                edge = getShuffleEdge(itemPos + 1, itemTop);
                // Log.d("mobeta", "    edge="+edge);

                // test for hit
                if (mFloatViewMid < edge) {
                    break;
                }

                lastEdge = edge;
                itemPos++;
            }
        }

        final int numHeaders = getHeaderViewsCount();
        final int numFooters = getFooterViewsCount();

        boolean updated = false;

        int oldFirstExpPos = mFirstExpPos;
        int oldSecondExpPos = mSecondExpPos;
        float oldSlideFrac = mSlideFrac;

        if (mAnimate) {
            int edgeToEdge = Math.abs(edge - lastEdge);

            int edgeTop, edgeBottom;
            if (mFloatViewMid < edge) {
                edgeBottom = edge;
                edgeTop = lastEdge;
            } else {
                edgeTop = edge;
                edgeBottom = lastEdge;
            }
            // Log.d("mobeta", "edgeTop="+edgeTop+" edgeBot="+edgeBottom);

            int slideRgnHeight = (int) (0.5f * mSlideRegionFrac * edgeToEdge);
            float slideRgnHeightF = (float) slideRgnHeight;
            int slideEdgeTop = edgeTop + slideRgnHeight;
            int slideEdgeBottom = edgeBottom - slideRgnHeight;

            // Three regions
            if (mFloatViewMid < slideEdgeTop) {
                mFirstExpPos = itemPos - 1;
                mSecondExpPos = itemPos;
                mSlideFrac = 0.5f * ((float) (slideEdgeTop - mFloatViewMid)) / slideRgnHeightF;
                // Log.d("mobeta",
                // "firstExp="+mFirstExpPos+" secExp="+mSecondExpPos+" slideFrac="+mSlideFrac);
            } else if (mFloatViewMid < slideEdgeBottom) {
                mFirstExpPos = itemPos;
                mSecondExpPos = itemPos;
            } else {
                mFirstExpPos = itemPos;
                mSecondExpPos = itemPos + 1;
                mSlideFrac = 0.5f * (1.0f + ((float) (edgeBottom - mFloatViewMid))
                        / slideRgnHeightF);
                // Log.d("mobeta",
                // "firstExp="+mFirstExpPos+" secExp="+mSecondExpPos+" slideFrac="+mSlideFrac);
            }

        } else {
            mFirstExpPos = itemPos;
            mSecondExpPos = itemPos;
        }

        // correct for headers and footers
        if (mFirstExpPos < numHeaders) {
            itemPos = numHeaders;
            mFirstExpPos = itemPos;
            mSecondExpPos = itemPos;
        } else if (mSecondExpPos >= getCount() - numFooters) {
            itemPos = getCount() - numFooters - 1;
            mFirstExpPos = itemPos;
            mSecondExpPos = itemPos;
        }

        if (mFirstExpPos != oldFirstExpPos || mSecondExpPos != oldSecondExpPos
                || mSlideFrac != oldSlideFrac) {
            updated = true;
        }

        if (itemPos != mFloatPos) {
            if (mDragListener != null) {
                mDragListener.drag(mFloatPos - numHeaders, itemPos - numHeaders);
            }

            mFloatPos = itemPos;
            updated = true;
        }

        return updated;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        if (mTrackDragSort) {
            mDragSortTracker.appendState();
        }
    }

    private class SmoothAnimator implements Runnable {

        protected long mStartTime;

        private float mDurationF;

        private float mAlpha;
        private float mA, mB, mC, mD;

        private boolean mCanceled;

        public SmoothAnimator(float smoothness, int duration) {
            mAlpha = smoothness;
            mDurationF = (float) duration;
            mA = mD = 1f / (2f * mAlpha * (1f - mAlpha));
            mB = mAlpha / (2f * (mAlpha - 1f));
            mC = 1f / (1f - mAlpha);
        }

        public float transform(float frac) {
            if (frac < mAlpha) {
                return mA * frac * frac;
            } else if (frac < 1f - mAlpha) {
                return mB + mC * frac;
            } else {
                return 1f - mD * (frac - 1f) * (frac - 1f);
            }
        }

        public void start() {
            mStartTime = SystemClock.uptimeMillis();
            mCanceled = false;
            onStart();
            post(this);
        }

        public void cancel() {
            mCanceled = true;
        }

        public void onStart() {
            // stub
        }

        public void onUpdate(float frac, float smoothFrac) {
            // stub
        }

        public void onStop() {
            // stub
        }

        @Override
        public void run() {
            if (mCanceled) {
                return;
            }

            float fraction = ((float) (SystemClock.uptimeMillis() - mStartTime)) / mDurationF;

            if (fraction >= 1f) {
                onUpdate(1f, 1f);
                onStop();
            } else {
                onUpdate(fraction, transform(fraction));
                post(this);
            }
        }
    }

    /**
     * Centers floating View under touch point.
     */
    private class LiftAnimator extends SmoothAnimator {

        private float mInitDragDeltaY;
        private float mFinalDragDeltaY;

        public LiftAnimator(float smoothness, int duration) {
            super(smoothness, duration);
        }

        @Override
        public void onStart() {
            mInitDragDeltaY = mDragDeltaY;
            mFinalDragDeltaY = mFloatViewHeightHalf;
        }

        @Override
        public void onUpdate(float frac, float smoothFrac) {
            if (mDragState != DRAGGING) {
                cancel();
            } else {
                mDragDeltaY = (int) (smoothFrac * mFinalDragDeltaY + (1f - smoothFrac)
                        * mInitDragDeltaY);
                mFloatLoc.y = mY - mDragDeltaY;
                doDragFloatView(true);
            }
        }
    }

    /**
     * Centers floating View over drop slot before destroying.
     */
    private class DropAnimator extends SmoothAnimator {

        private int mDropPos;
        private int srcPos;
        private float mInitDeltaY;
        private float mInitDeltaX;

        public DropAnimator(float smoothness, int duration) {
            super(smoothness, duration);
        }

        @Override
        public void onStart() {
            mDropPos = mFloatPos;
            srcPos = mSrcPos;
            mDragState = DROPPING;
            mInitDeltaY = mFloatLoc.y - getTargetY();
            mInitDeltaX = mFloatLoc.x - getPaddingLeft();
        }

        private int getTargetY() {
            final int first = getFirstVisiblePosition();
            final int otherAdjust = (mItemHeightCollapsed + getDividerHeight()) / 2;
            View v = getChildAt(mDropPos - first);
            int targetY = -1;
            if (v != null) {
                if (mDropPos == srcPos) {
                    targetY = v.getTop();
                } else if (mDropPos < srcPos) {
                    // expanded down
                    targetY = v.getTop() - otherAdjust;
                } else {
                    // expanded up
                    targetY = v.getBottom() + otherAdjust - mFloatViewHeight;
                }
            } else {
                // drop position is not on screen?? no animation
                cancel();
            }

            return targetY;
        }

        @Override
        public void onUpdate(float frac, float smoothFrac) {
            final int targetY = getTargetY();
            final int targetX = getPaddingLeft();
            final float deltaY = mFloatLoc.y - targetY;
            final float deltaX = mFloatLoc.x - targetX;
            final float f = 1f - smoothFrac;
            if (f < Math.abs(deltaY / mInitDeltaY) || f < Math.abs(deltaX / mInitDeltaX)) {
                mFloatLoc.y = targetY + (int) (mInitDeltaY * f);
                mFloatLoc.x = getPaddingLeft() + (int) (mInitDeltaX * f);
                doDragFloatView(true);
            }
        }

        @Override
        public void onStop() {
            dropFloatView();
        }

    }

    /**
     * Collapses expanded items.
     */
    private class RemoveAnimator extends SmoothAnimator {

        private float mFloatLocX;
        private float mFirstStartBlank;
        private float mSecondStartBlank;

        private int mFirstChildHeight = -1;
        private int mSecondChildHeight = -1;

        private int mFirstPos;
        private int mSecondPos;
        private int srcPos;

        public RemoveAnimator(float smoothness, int duration) {
            super(smoothness, duration);
        }

        @Override
        public void onStart() {
            mFirstChildHeight = -1;
            mSecondChildHeight = -1;
            mFirstPos = mFirstExpPos;
            mSecondPos = mSecondExpPos;
            srcPos = mSrcPos;
            mDragState = REMOVING;

            mFloatLocX = mFloatLoc.x;
            if (mUseRemoveVelocity) {
                float minVelocity = 2f * getWidth();
                if (mRemoveVelocityX == 0) {
                    mRemoveVelocityX = (mFloatLocX < 0 ? -1 : 1) * minVelocity;
                } else {
                    minVelocity *= 2;
                    if (mRemoveVelocityX < 0 && mRemoveVelocityX > -minVelocity)
                        mRemoveVelocityX = -minVelocity;
                    else if (mRemoveVelocityX > 0 && mRemoveVelocityX < minVelocity)
                        mRemoveVelocityX = minVelocity;
                }
            } else {
                destroyFloatView();
            }
        }

        @Override
        public void onUpdate(float frac, float smoothFrac) {
            float f = 1f - smoothFrac;

            final int firstVis = getFirstVisiblePosition();
            View item = getChildAt(mFirstPos - firstVis);
            ViewGroup.LayoutParams lp;
            int blank;

            if (mUseRemoveVelocity) {
                float dt = (float) (SystemClock.uptimeMillis() - mStartTime) / 1000;
                if (dt == 0)
                    return;
                float dx = mRemoveVelocityX * dt;
                int w = getWidth();
                mRemoveVelocityX += (mRemoveVelocityX > 0 ? 1 : -1) * dt * w;
                mFloatLocX += dx;
                mFloatLoc.x = (int) mFloatLocX;
                if (mFloatLocX < w && mFloatLocX > -w) {
                    mStartTime = SystemClock.uptimeMillis();
                    doDragFloatView(true);
                    return;
                }
            }

            if (item != null) {
                if (mFirstChildHeight == -1) {
                    mFirstChildHeight = getChildHeight(mFirstPos, item, false);
                    mFirstStartBlank = (float) (item.getHeight() - mFirstChildHeight);
                }
                blank = Math.max((int) (f * mFirstStartBlank), 1);
                lp = item.getLayoutParams();
                lp.height = mFirstChildHeight + blank;
                item.setLayoutParams(lp);
            }
            if (mSecondPos != mFirstPos) {
                item = getChildAt(mSecondPos - firstVis);
                if (item != null) {
                    if (mSecondChildHeight == -1) {
                        mSecondChildHeight = getChildHeight(mSecondPos, item, false);
                        mSecondStartBlank = (float) (item.getHeight() - mSecondChildHeight);
                    }
                    blank = Math.max((int) (f * mSecondStartBlank), 1);
                    lp = item.getLayoutParams();
                    lp.height = mSecondChildHeight + blank;
                    item.setLayoutParams(lp);
                }
            }
        }

        @Override
        public void onStop() {
            doRemoveItem();
        }
    }

    public void removeItem(int which) {

        mUseRemoveVelocity = false;
        removeItem(which, 0);
    }

    /**
     * Removes an item from the list and animates the removal.
     *
     * @param which Position to remove (NOTE: headers/footers ignored!
     * this is a position in your input ListAdapter).
     * @param velocityX 
     */
    public void removeItem(int which, float velocityX) {
        if (mDragState == IDLE || mDragState == DRAGGING) {

            if (mDragState == IDLE) {
                // called from outside drag-sort
                mSrcPos = getHeaderViewsCount() + which;
                mFirstExpPos = mSrcPos;
                mSecondExpPos = mSrcPos;
                mFloatPos = mSrcPos;
                View v = getChildAt(mSrcPos - getFirstVisiblePosition());
                if (v != null) {
                    v.setVisibility(View.INVISIBLE);
                }
            }

            mDragState = REMOVING;
            mRemoveVelocityX = velocityX;

            if (mInTouchEvent) {
                switch (mCancelMethod) {
                    case ON_TOUCH_EVENT:
                        super.onTouchEvent(mCancelEvent);
                        break;
                    case ON_INTERCEPT_TOUCH_EVENT:
                        super.onInterceptTouchEvent(mCancelEvent);
                        break;
                }
            }

            if (mRemoveAnimator != null) {
                mRemoveAnimator.start();
            } else {
                doRemoveItem(which);
            }
        }
    }

    /**
     * Move an item, bypassing the drag-sort process. Simply calls
     * through to {@link DropListener#drop(int, int)}.
     * 
     * @param from Position to move (NOTE: headers/footers ignored!
     * this is a position in your input ListAdapter).
     * @param to Target position (NOTE: headers/footers ignored!
     * this is a position in your input ListAdapter).
     */
    public void moveItem(int from, int to) {
        if (mDropListener != null) {
            final int count = getInputAdapter().getCount();
            if (from >= 0 && from < count && to >= 0 && to < count) {
                mDropListener.drop(from, to);
            }
        }
    }

    /**
     * Cancel a drag. Calls {@link #stopDrag(boolean, boolean)} with
     * <code>true</code> as the first argument.
     */
    public void cancelDrag() {
        if (mDragState == DRAGGING) {
            mDragScroller.stopScrolling(true);
            destroyFloatView();
            clearPositions();
            adjustAllItems();

            if (mInTouchEvent) {
                mDragState = STOPPED;
            } else {
                mDragState = IDLE;
            }
        }
    }

    private void clearPositions() {
        mSrcPos = -1;
        mFirstExpPos = -1;
        mSecondExpPos = -1;
        mFloatPos = -1;
    }

    private void dropFloatView() {
        // must set to avoid cancelDrag being called from the
        // DataSetObserver
        mDragState = DROPPING;

        if (mDropListener != null && mFloatPos >= 0 && mFloatPos < getCount()) {
            final int numHeaders = getHeaderViewsCount();
            mDropListener.drop(mSrcPos - numHeaders, mFloatPos - numHeaders);
        }

        destroyFloatView();

        adjustOnReorder();
        clearPositions();
        adjustAllItems();

        // now the drag is done
        if (mInTouchEvent) {
            mDragState = STOPPED;
        } else {
            mDragState = IDLE;
        }
    }

    private void doRemoveItem() {
        doRemoveItem(mSrcPos - getHeaderViewsCount());
    }

    /**
     * Removes dragged item from the list. Calls RemoveListener.
     */
    private void doRemoveItem(int which) {
        // must set to avoid cancelDrag being called from the
        // DataSetObserver
        mDragState = REMOVING;

        // end it
        if (mRemoveListener != null) {
            mRemoveListener.remove(which);
        }

        destroyFloatView();

        adjustOnReorder();
        clearPositions();

        // now the drag is done
        if (mInTouchEvent) {
            mDragState = STOPPED;
        } else {
            mDragState = IDLE;
        }
    }

    private void adjustOnReorder() {
        final int firstPos = getFirstVisiblePosition();
        // Log.d("mobeta", "first="+firstPos+" src="+mSrcPos);
        if (mSrcPos < firstPos) {
            // collapsed src item is off screen;
            // adjust the scroll after item heights have been fixed
            View v = getChildAt(0);
            int top = 0;
            if (v != null) {
                top = v.getTop();
            }
            // Log.d("mobeta", "top="+top+" fvh="+mFloatViewHeight);
            setSelectionFromTop(firstPos - 1, top - getPaddingTop());
        }
    }

    /**
     * Stop a drag in progress. Pass <code>true</code> if you would
     * like to remove the dragged item from the list.
     *
     * @param remove Remove the dragged item from the list. Calls
     * a registered RemoveListener, if one exists. Otherwise, calls
     * the DropListener, if one exists.
     *
     * @return True if the stop was successful. False if there is
     * no floating View.
     */
    public boolean stopDrag(boolean remove) {
        mUseRemoveVelocity = false;
        return stopDrag(remove, 0);
    }

    public boolean stopDragWithVelocity(boolean remove, float velocityX) {

        mUseRemoveVelocity = true;
        return stopDrag(remove, velocityX);
    }

    public boolean stopDrag(boolean remove, float velocityX) {
        if (mFloatView != null) {
            mDragScroller.stopScrolling(true);

            if (remove) {
                removeItem(mSrcPos - getHeaderViewsCount(), velocityX);
            } else {
                if (mDropAnimator != null) {
                    mDropAnimator.start();
                } else {
                    dropFloatView();
                }
            }

            if (mTrackDragSort) {
                mDragSortTracker.stopTracking();
            }

            return true;
        } else {
            // stop failed
            return false;
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        if (mIgnoreTouchEvent) {
            mIgnoreTouchEvent = false;
            return false;
        }

        if (!mDragEnabled) {
            return super.onTouchEvent(ev);
        }

        boolean more = false;

        boolean lastCallWasIntercept = mLastCallWasIntercept;
        mLastCallWasIntercept = false;

        if (!lastCallWasIntercept) {
            saveTouchCoords(ev);
        }

        // if (mFloatView != null) {
        if (mDragState == DRAGGING) {
            onDragTouchEvent(ev);
            more = true; // give us more!
        } else {
            // what if float view is null b/c we dropped in middle
            // of drag touch event?

            // if (mDragState != STOPPED) {
            if (mDragState == IDLE) {
                if (super.onTouchEvent(ev)) {
                    more = true;
                }
            }

            int action = ev.getAction() & MotionEvent.ACTION_MASK;

            switch (action) {
                case MotionEvent.ACTION_CANCEL:
                case MotionEvent.ACTION_UP:
                    doActionUpOrCancel();
                    break;
                default:
                    if (more) {
                        mCancelMethod = ON_TOUCH_EVENT;
                    }
            }
        }

        return more;
    }

    private void doActionUpOrCancel() {
        mCancelMethod = NO_CANCEL;
        mInTouchEvent = false;
        if (mDragState == STOPPED) {
            mDragState = IDLE;
        }
        mCurrFloatAlpha = mFloatAlpha;
        mListViewIntercepted = false;
        mChildHeightCache.clear();
    }

    private void saveTouchCoords(MotionEvent ev) {
        int action = ev.getAction() & MotionEvent.ACTION_MASK;
        if (action != MotionEvent.ACTION_DOWN) {
            mLastX = mX;
            mLastY = mY;
        }
        mX = (int) ev.getX();
        mY = (int) ev.getY();
        if (action == MotionEvent.ACTION_DOWN) {
            mLastX = mX;
            mLastY = mY;
        }
        mOffsetX = (int) ev.getRawX() - mX;
        mOffsetY = (int) ev.getRawY() - mY;
    }

    public boolean listViewIntercepted() {
        return mListViewIntercepted;
    }

    private boolean mListViewIntercepted = false;

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (!mDragEnabled) {
            return super.onInterceptTouchEvent(ev);
        }

        saveTouchCoords(ev);
        mLastCallWasIntercept = true;

        int action = ev.getAction() & MotionEvent.ACTION_MASK;

        if (action == MotionEvent.ACTION_DOWN) {
            if (mDragState != IDLE) {
                // intercept and ignore
                mIgnoreTouchEvent = true;
                return true;
            }
            mInTouchEvent = true;
        }

        boolean intercept = false;

        // the following deals with calls to super.onInterceptTouchEvent
        if (mFloatView != null) {
            // super's touch event canceled in startDrag
            intercept = true;
        } else {
            if (super.onInterceptTouchEvent(ev)) {
                mListViewIntercepted = true;
                intercept = true;
            }

            switch (action) {
                case MotionEvent.ACTION_CANCEL:
                case MotionEvent.ACTION_UP:
                    doActionUpOrCancel();
                    break;
                default:
                    if (intercept) {
                        mCancelMethod = ON_TOUCH_EVENT;
                    } else {
                        mCancelMethod = ON_INTERCEPT_TOUCH_EVENT;
                    }
            }
        }

        if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
            mInTouchEvent = false;
        }

        return intercept;
    }

    /**
     * Set the width of each drag scroll region by specifying
     * a fraction of the ListView height.
     *
     * @param heightFraction Fraction of ListView height. Capped at
     * 0.5f.
     * 
     */
    public void setDragScrollStart(float heightFraction) {
        setDragScrollStarts(heightFraction, heightFraction);
    }

    /**
     * Set the width of each drag scroll region by specifying
     * a fraction of the ListView height.
     *
     * @param upperFrac Fraction of ListView height for up-scroll bound.
     * Capped at 0.5f.
     * @param lowerFrac Fraction of ListView height for down-scroll bound.
     * Capped at 0.5f.
     * 
     */
    public void setDragScrollStarts(float upperFrac, float lowerFrac) {
        if (lowerFrac > 0.5f) {
            mDragDownScrollStartFrac = 0.5f;
        } else {
            mDragDownScrollStartFrac = lowerFrac;
        }

        if (upperFrac > 0.5f) {
            mDragUpScrollStartFrac = 0.5f;
        } else {
            mDragUpScrollStartFrac = upperFrac;
        }

        if (getHeight() != 0) {
            updateScrollStarts();
        }
    }

    private void continueDrag(int x, int y) {

        // proposed position
        mFloatLoc.x = x - mDragDeltaX;
        mFloatLoc.y = y - mDragDeltaY;

        doDragFloatView(true);

        int minY = Math.min(y, mFloatViewMid + mFloatViewHeightHalf);
        int maxY = Math.max(y, mFloatViewMid - mFloatViewHeightHalf);

        // get the current scroll direction
        int currentScrollDir = mDragScroller.getScrollDir();

        if (minY > mLastY && minY > mDownScrollStartY && currentScrollDir != DragScroller.DOWN) {
            // dragged down, it is below the down scroll start and it is not
            // scrolling up

            if (currentScrollDir != DragScroller.STOP) {
                // moved directly from up scroll to down scroll
                mDragScroller.stopScrolling(true);
            }

            // start scrolling down
            mDragScroller.startScrolling(DragScroller.DOWN);
        } else if (maxY < mLastY && maxY < mUpScrollStartY && currentScrollDir != DragScroller.UP) {
            // dragged up, it is above the up scroll start and it is not
            // scrolling up

            if (currentScrollDir != DragScroller.STOP) {
                // moved directly from down scroll to up scroll
                mDragScroller.stopScrolling(true);
            }

            // start scrolling up
            mDragScroller.startScrolling(DragScroller.UP);
        } else if (maxY >= mUpScrollStartY && minY <= mDownScrollStartY
                && mDragScroller.isScrolling()) {
            // not in the upper nor in the lower drag-scroll regions but it is
            // still scrolling

            mDragScroller.stopScrolling(true);
        }
    }

    private void updateScrollStarts() {
        final int padTop = getPaddingTop();
        final int listHeight = getHeight() - padTop - getPaddingBottom();
        float heightF = (float) listHeight;

        mUpScrollStartYF = padTop + mDragUpScrollStartFrac * heightF;
        mDownScrollStartYF = padTop + (1.0f - mDragDownScrollStartFrac) * heightF;

        mUpScrollStartY = (int) mUpScrollStartYF;
        mDownScrollStartY = (int) mDownScrollStartYF;

        mDragUpScrollHeight = mUpScrollStartYF - padTop;
        mDragDownScrollHeight = padTop + listHeight - mDownScrollStartYF;
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        updateScrollStarts();
    }

    private void adjustAllItems() {
        final int first = getFirstVisiblePosition();
        final int last = getLastVisiblePosition();

        int begin = Math.max(0, getHeaderViewsCount() - first);
        int end = Math.min(last - first, getCount() - 1 - getFooterViewsCount() - first);

        for (int i = begin; i <= end; ++i) {
            View v = getChildAt(i);
            if (v != null) {
                adjustItem(first + i, v, false);
            }
        }
    }

    private void adjustItem(int position) {
        View v = getChildAt(position - getFirstVisiblePosition());

        if (v != null) {
            adjustItem(position, v, false);
        }
    }

    /**
     * Sets layout param height, gravity, and visibility  on
     * wrapped item.
     */
    private void adjustItem(int position, View v, boolean invalidChildHeight) {

        // Adjust item height
        ViewGroup.LayoutParams lp = v.getLayoutParams();
        int height;
        if (position != mSrcPos && position != mFirstExpPos && position != mSecondExpPos) {
            height = ViewGroup.LayoutParams.WRAP_CONTENT;
        } else {
            height = calcItemHeight(position, v, invalidChildHeight);
        }

        if (height != lp.height) {
            lp.height = height;
            v.setLayoutParams(lp);
        }

        // Adjust item gravity
        if (position == mFirstExpPos || position == mSecondExpPos) {
            if (position < mSrcPos) {
                ((DragSortItemView) v).setGravity(Gravity.BOTTOM);
            } else if (position > mSrcPos) {
                ((DragSortItemView) v).setGravity(Gravity.TOP);
            }
        }

        // Finally adjust item visibility

        int oldVis = v.getVisibility();
        int vis = View.VISIBLE;

        if (position == mSrcPos && mFloatView != null) {
            vis = View.INVISIBLE;
        }

        if (vis != oldVis) {
            v.setVisibility(vis);
        }
    }

    private int getChildHeight(int position) {
        if (position == mSrcPos) {
            return 0;
        }

        View v = getChildAt(position - getFirstVisiblePosition());

        if (v != null) {
            // item is onscreen, therefore child height is valid,
            // hence the "true"
            return getChildHeight(position, v, false);
        } else {
            // item is offscreen
            // first check cache for child height at this position
            int childHeight = mChildHeightCache.get(position);
            if (childHeight != -1) {
                // Log.d("mobeta", "found child height in cache!");
                return childHeight;
            }

            final ListAdapter adapter = getAdapter();
            int type = adapter.getItemViewType(position);

            // There might be a better place for checking for the following
            final int typeCount = adapter.getViewTypeCount();
            if (typeCount != mSampleViewTypes.length) {
                mSampleViewTypes = new View[typeCount];
            }

            if (type >= 0) {
                if (mSampleViewTypes[type] == null) {
                    v = adapter.getView(position, null, this);
                    mSampleViewTypes[type] = v;
                } else {
                    v = adapter.getView(position, mSampleViewTypes[type], this);
                }
            } else {
                // type is HEADER_OR_FOOTER or IGNORE
                v = adapter.getView(position, null, this);
            }

            // current child height is invalid, hence "true" below
            childHeight = getChildHeight(position, v, true);

            // cache it because this could have been expensive
            mChildHeightCache.add(position, childHeight);

            return childHeight;
        }
    }

    private int getChildHeight(int position, View item, boolean invalidChildHeight) {
        if (position == mSrcPos) {
            return 0;
        }

        View child;
        if (position < getHeaderViewsCount() || position >= getCount() - getFooterViewsCount()) {
            child = item;
        } else {
            child = ((ViewGroup) item).getChildAt(0);
        }

        ViewGroup.LayoutParams lp = child.getLayoutParams();

        if (lp != null) {
            if (lp.height > 0) {
                return lp.height;
            }
        }

        int childHeight = child.getHeight();

        if (childHeight == 0 || invalidChildHeight) {
            measureItem(child);
            childHeight = child.getMeasuredHeight();
        }

        return childHeight;
    }

    private int calcItemHeight(int position, View item, boolean invalidChildHeight) {
        return calcItemHeight(position, getChildHeight(position, item, invalidChildHeight));
    }

    private int calcItemHeight(int position, int childHeight) {

        int divHeight = getDividerHeight();

        boolean isSliding = mAnimate && mFirstExpPos != mSecondExpPos;
        int maxNonSrcBlankHeight = mFloatViewHeight - mItemHeightCollapsed;
        int slideHeight = (int) (mSlideFrac * maxNonSrcBlankHeight);

        int height;

        if (position == mSrcPos) {
            if (mSrcPos == mFirstExpPos) {
                if (isSliding) {
                    height = slideHeight + mItemHeightCollapsed;
                } else {
                    height = mFloatViewHeight;
                }
            } else if (mSrcPos == mSecondExpPos) {
                // if gets here, we know an item is sliding
                height = mFloatViewHeight - slideHeight;
            } else {
                height = mItemHeightCollapsed;
            }
        } else if (position == mFirstExpPos) {
            if (isSliding) {
                height = childHeight + slideHeight;
            } else {
                height = childHeight + maxNonSrcBlankHeight;
            }
        } else if (position == mSecondExpPos) {
            // we know an item is sliding (b/c 2ndPos != 1stPos)
            height = childHeight + maxNonSrcBlankHeight - slideHeight;
        } else {
            height = childHeight;
        }

        return height;
    }

    @Override
    public void requestLayout() {
        if (!mBlockLayoutRequests) {
            super.requestLayout();
        }
    }

    private int adjustScroll(int movePos, View moveItem, int oldFirstExpPos, int oldSecondExpPos) {
        int adjust = 0;

        final int childHeight = getChildHeight(movePos);

        int moveHeightBefore = moveItem.getHeight();
        int moveHeightAfter = calcItemHeight(movePos, childHeight);

        int moveBlankBefore = moveHeightBefore;
        int moveBlankAfter = moveHeightAfter;
        if (movePos != mSrcPos) {
            moveBlankBefore -= childHeight;
            moveBlankAfter -= childHeight;
        }

        int maxBlank = mFloatViewHeight;
        if (mSrcPos != mFirstExpPos && mSrcPos != mSecondExpPos) {
            maxBlank -= mItemHeightCollapsed;
        }

        if (movePos <= oldFirstExpPos) {
            if (movePos > mFirstExpPos) {
                adjust += maxBlank - moveBlankAfter;
            }
        } else if (movePos == oldSecondExpPos) {
            if (movePos <= mFirstExpPos) {
                adjust += moveBlankBefore - maxBlank;
            } else if (movePos == mSecondExpPos) {
                adjust += moveHeightBefore - moveHeightAfter;
            } else {
                adjust += moveBlankBefore;
            }
        } else {
            if (movePos <= mFirstExpPos) {
                adjust -= maxBlank;
            } else if (movePos == mSecondExpPos) {
                adjust -= moveBlankAfter;
            }
        }

        return adjust;
    }

    private void measureItem(View item) {
        ViewGroup.LayoutParams lp = item.getLayoutParams();
        if (lp == null) {
            lp = new AbsListView.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
                    ViewGroup.LayoutParams.WRAP_CONTENT);
            item.setLayoutParams(lp);
        }
        int wspec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec, getListPaddingLeft()
                + getListPaddingRight(), lp.width);
        int hspec;
        if (lp.height > 0) {
            hspec = MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY);
        } else {
            hspec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
        }
        item.measure(wspec, hspec);
    }

    private void measureFloatView() {
        if (mFloatView != null) {
            measureItem(mFloatView);
            mFloatViewHeight = mFloatView.getMeasuredHeight();
            mFloatViewHeightHalf = mFloatViewHeight / 2;
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        // Log.d("mobeta", "onMeasure called");
        if (mFloatView != null) {
            if (mFloatView.isLayoutRequested()) {
                measureFloatView();
            }
            mFloatViewOnMeasured = true; // set to false after layout
        }
        mWidthMeasureSpec = widthMeasureSpec;
    }

    @Override
    protected void layoutChildren() {
        super.layoutChildren();

        if (mFloatView != null) {
            if (mFloatView.isLayoutRequested() && !mFloatViewOnMeasured) {
                // Have to measure here when usual android measure
                // pass is skipped. This happens during a drag-sort
                // when layoutChildren is called directly.
                measureFloatView();
            }
            mFloatView.layout(0, 0, mFloatView.getMeasuredWidth(), mFloatView.getMeasuredHeight());
            mFloatViewOnMeasured = false;
        }

        if (mUiInterface != null) {
            mUiInterface.redraw();
        }
    }

    protected boolean onDragTouchEvent(MotionEvent ev) {
        // we are in a drag
        int action = ev.getAction() & MotionEvent.ACTION_MASK;

        switch (ev.getAction() & MotionEvent.ACTION_MASK) {
            case MotionEvent.ACTION_CANCEL:
                if (mDragState == DRAGGING) {
                    cancelDrag();
                }
                doActionUpOrCancel();
                break;
            case MotionEvent.ACTION_UP:
                // Log.d("mobeta", "calling stopDrag from onDragTouchEvent");
                if (mDragState == DRAGGING) {
                    stopDrag(false);
                }
                doActionUpOrCancel();
                break;
            case MotionEvent.ACTION_MOVE:
                continueDrag((int) ev.getX(), (int) ev.getY());
                break;
        }

        return true;
    }

    private boolean mFloatViewInvalidated = false;

    private void invalidateFloatView() {
        mFloatViewInvalidated = true;
    }

    /**
     * Start a drag of item at <code>position</code> using the
     * registered FloatViewManager. Calls through
     * to {@link #startDrag(int,View,int,int,int)} after obtaining
     * the floating View from the FloatViewManager.
     *
     * @param position Item to drag.
     * @param dragFlags Flags that restrict some movements of the
     * floating View. For example, set <code>dragFlags |= 
     * ~{@link #DRAG_NEG_X}</code> to allow dragging the floating
     * View in all directions except off the screen to the left.
     * @param deltaX Offset in x of the touch coordinate from the
     * left edge of the floating View (i.e. touch-x minus float View
     * left).
     * @param deltaY Offset in y of the touch coordinate from the
     * top edge of the floating View (i.e. touch-y minus float View
     * top).
     *
     * @return True if the drag was started, false otherwise. This
     * <code>startDrag</code> will fail if we are not currently in
     * a touch event, there is no registered FloatViewManager,
     * or the FloatViewManager returns a null View.
     */
    public boolean startDrag(int position, int dragFlags, int deltaX, int deltaY) {
        if (!mInTouchEvent || mFloatViewManager == null) {
            return false;
        }

        View v = mFloatViewManager.onCreateFloatView(position);

        if (v == null) {
            return false;
        } else {
            return startDrag(position, v, dragFlags, deltaX, deltaY);
        }

    }

    /**
     * Start a drag of item at <code>position</code> without using
     * a FloatViewManager.
     *
     * @param position Item to drag.
     * @param floatView Floating View.
     * @param dragFlags Flags that restrict some movements of the
     * floating View. For example, set <code>dragFlags |= 
     * ~{@link #DRAG_NEG_X}</code> to allow dragging the floating
     * View in all directions except off the screen to the left.
     * @param deltaX Offset in x of the touch coordinate from the
     * left edge of the floating View (i.e. touch-x minus float View
     * left).
     * @param deltaY Offset in y of the touch coordinate from the
     * top edge of the floating View (i.e. touch-y minus float View
     * top).
     *
     * @return True if the drag was started, false otherwise. This
     * <code>startDrag</code> will fail if we are not currently in
     * a touch event, <code>floatView</code> is null, or there is
     * a drag in progress.
     */
    public boolean startDrag(int position, View floatView, int dragFlags, int deltaX, int deltaY) {
        if (mDragState != IDLE || !mInTouchEvent || mFloatView != null || floatView == null
                || !mDragEnabled) {
            return false;
        }

        if (getParent() != null) {
            getParent().requestDisallowInterceptTouchEvent(true);
        }

        int pos = position + getHeaderViewsCount();
        mFirstExpPos = pos;
        mSecondExpPos = pos;
        mSrcPos = pos;
        mFloatPos = pos;

        // mDragState = dragType;
        mDragState = DRAGGING;
        mDragFlags = 0;
        mDragFlags |= dragFlags;

        mFloatView = floatView;
        measureFloatView(); // sets mFloatViewHeight

        mDragDeltaX = deltaX;
        mDragDeltaY = deltaY;
        mDragStartY = mY;

        // updateFloatView(mX - mDragDeltaX, mY - mDragDeltaY);
        mFloatLoc.x = mX - mDragDeltaX;
        mFloatLoc.y = mY - mDragDeltaY;

        // set src item invisible
        final View srcItem = getChildAt(mSrcPos - getFirstVisiblePosition());

        if (srcItem != null) {
            srcItem.setVisibility(View.INVISIBLE);
        }

        if (mTrackDragSort) {
            mDragSortTracker.startTracking();
        }

        // once float view is created, events are no longer passed
        // to ListView
        switch (mCancelMethod) {
            case ON_TOUCH_EVENT:
                super.onTouchEvent(mCancelEvent);
                break;
            case ON_INTERCEPT_TOUCH_EVENT:
                super.onInterceptTouchEvent(mCancelEvent);
                break;
        }

        requestLayout();

        if (mLiftAnimator != null) {
            mLiftAnimator.start();
        }

        return true;
    }

    private void doDragFloatView(boolean forceInvalidate) {
        int movePos = getFirstVisiblePosition() + getChildCount() / 2;
        View moveItem = getChildAt(getChildCount() / 2);

        if (moveItem == null) {
            return;
        }

        doDragFloatView(movePos, moveItem, forceInvalidate);
    }

    private void doDragFloatView(int movePos, View moveItem, boolean forceInvalidate) {
        mBlockLayoutRequests = true;

        updateFloatView();

        int oldFirstExpPos = mFirstExpPos;
        int oldSecondExpPos = mSecondExpPos;

        boolean updated = updatePositions();

        if (updated) {
            adjustAllItems();
            int scroll = adjustScroll(movePos, moveItem, oldFirstExpPos, oldSecondExpPos);
            // Log.d("mobeta", "  adjust scroll="+scroll);

            setSelectionFromTop(movePos, moveItem.getTop() + scroll - getPaddingTop());
            layoutChildren();
        }

        if (updated || forceInvalidate) {
            invalidate();
        }

        mBlockLayoutRequests = false;
    }

    /**
     * Sets float View location based on suggested values and
     * constraints set in mDragFlags.
     */
    private void updateFloatView() {

        if (mFloatViewManager != null) {
            mTouchLoc.set(mX, mY);
            mFloatViewManager.onDragFloatView(mFloatView, mFloatLoc, mTouchLoc);
        }

        final int floatX = mFloatLoc.x;
        final int floatY = mFloatLoc.y;

        // restrict x motion
        int padLeft = getPaddingLeft();
        if ((mDragFlags & DRAG_POS_X) == 0 && floatX > padLeft) {
            mFloatLoc.x = padLeft;
        } else if ((mDragFlags & DRAG_NEG_X) == 0 && floatX < padLeft) {
            mFloatLoc.x = padLeft;
        }

        // keep floating view from going past bottom of last header view
        final int numHeaders = getHeaderViewsCount();
        final int numFooters = getFooterViewsCount();
        final int firstPos = getFirstVisiblePosition();
        final int lastPos = getLastVisiblePosition();

        // Log.d("mobeta",
        // "nHead="+numHeaders+" nFoot="+numFooters+" first="+firstPos+" last="+lastPos);
        int topLimit = getPaddingTop();
        if (firstPos < numHeaders) {
            topLimit = getChildAt(numHeaders - firstPos - 1).getBottom();
        }
        if ((mDragFlags & DRAG_NEG_Y) == 0) {
            if (firstPos <= mSrcPos) {
                topLimit = Math.max(getChildAt(mSrcPos - firstPos).getTop(), topLimit);
            }
        }
        // bottom limit is top of first footer View or
        // bottom of last item in list
        int bottomLimit = getHeight() - getPaddingBottom();
        if (lastPos >= getCount() - numFooters - 1) {
            bottomLimit = getChildAt(getCount() - numFooters - 1 - firstPos).getBottom();
        }
        if ((mDragFlags & DRAG_POS_Y) == 0) {
            if (lastPos >= mSrcPos) {
                bottomLimit = Math.min(getChildAt(mSrcPos - firstPos).getBottom(), bottomLimit);
            }
        }

        // Log.d("mobeta", "dragView top=" + (y - mDragDeltaY));
        // Log.d("mobeta", "limit=" + limit);
        // Log.d("mobeta", "mDragDeltaY=" + mDragDeltaY);

        if (floatY < topLimit) {
            mFloatLoc.y = topLimit;
        } else if (floatY + mFloatViewHeight > bottomLimit) {
            mFloatLoc.y = bottomLimit - mFloatViewHeight;
        }

        // get y-midpoint of floating view (constrained to ListView bounds)
        mFloatViewMid = mFloatLoc.y + mFloatViewHeightHalf;
    }

    private void destroyFloatView() {
        if (mFloatView != null) {
            mFloatView.setVisibility(GONE);
            if (mFloatViewManager != null) {
                mFloatViewManager.onDestroyFloatView(mFloatView);
            }
            mFloatView = null;
            invalidate();
        }
    }

    /**
     * Interface for customization of the floating View appearance
     * and dragging behavior. Implement
     * your own and pass it to {@link #setFloatViewManager}. If
     * your own is not passed, the default {@link SimpleFloatViewManager}
     * implementation is used.
     */
    public interface FloatViewManager {

        /**
         * Return the floating View for item at <code>position</code>.
         * DragSortListView will measure and layout this View for you,
         * so feel free to just inflate it. You can help DSLV by
         * setting some {@link ViewGroup.LayoutParams} on this View;
         * otherwise it will set some for you (with a width of FILL_PARENT
         * and a height of WRAP_CONTENT).
         *
         * @param position Position of item to drag (NOTE:
         * <code>position</code> excludes header Views; thus, if you
         * want to call {@link ListView#getChildAt(int)}, you will need
         * to add {@link ListView#getHeaderViewsCount()} to the index).
         *
         * @return The View you wish to display as the floating View.
         */
        public View onCreateFloatView(int position);

        /**
         * Called whenever the floating View is dragged. Float View
         * properties can be changed here. Also, the upcoming location
         * of the float View can be altered by setting
         * <code>location.x</code> and <code>location.y</code>.
         *
         * @param floatView The floating View.
         * @param location The location (top-left; relative to DSLV
         * top-left) at which the float
         * View would like to appear, given the current touch location
         * and the offset provided in {@link DragSortListView#startDrag}.
         * @param touch The current touch location (relative to DSLV
         * top-left).
         * @param pendingScroll 
         */
        public void onDragFloatView(View floatView, Point location, Point touch);

        /**
         * Called when the float View is dropped; lets you perform
         * any necessary cleanup. The internal DSLV floating View
         * reference is set to null immediately after this is called.
         *
         * @param floatView The floating View passed to
         * {@link #onCreateFloatView(int)}.
         */
        public void onDestroyFloatView(View floatView);
    }

    public void setFloatViewManager(FloatViewManager manager) {
        mFloatViewManager = manager;
    }

    public void setDragListener(DragListener l) {
        mDragListener = l;
    }

    /**
     * Allows for easy toggling between a DragSortListView
     * and a regular old ListView. If enabled, items are
     * draggable, where the drag init mode determines how
     * items are lifted (see {@link setDragInitMode(int)}).
     * If disabled, items cannot be dragged.
     *
     * @param enabled Set <code>true</code> to enable list
     * item dragging
     */
    public void setDragEnabled(boolean enabled) {
        mDragEnabled = enabled;
    }

    public boolean isDragEnabled() {
        return mDragEnabled;
    }

    /**
     * This better reorder your ListAdapter! DragSortListView does not do this
     * for you; doesn't make sense to. Make sure
     * {@link BaseAdapter#notifyDataSetChanged()} or something like it is called
     * in your implementation. Furthermore, if you have a choiceMode other than
     * none and the ListAdapter does not return true for
     * {@link ListAdapter#hasStableIds()}, you will need to call
     * {@link #moveCheckState(int, int)} to move the check boxes along with the
     * list items.
     * 
     * @param l
     */
    public void setDropListener(DropListener l) {
        mDropListener = l;
    }

    /**
     * Probably a no-brainer, but make sure that your remove listener
     * calls {@link BaseAdapter#notifyDataSetChanged()} or something like it.
     * When an item removal occurs, DragSortListView
     * relies on a redraw of all the items to recover invisible views
     * and such. Strictly speaking, if you remove something, your dataset
     * has changed...
     * 
     * @param l
     */
    public void setRemoveListener(RemoveListener l) {
        mRemoveListener = l;
    }

    public interface DragListener {

        public void drag(int from, int to);
    }

    /**
     * Your implementation of this has to reorder your ListAdapter! 
     * Make sure to call
     * {@link BaseAdapter#notifyDataSetChanged()} or something like it
     * in your implementation.
     * 
     * @author heycosmo
     *
     */
    public interface DropListener {

        public void drop(int from, int to);
    }

    /**
     * Make sure to call
     * {@link BaseAdapter#notifyDataSetChanged()} or something like it
     * in your implementation.
     * 
     * @author heycosmo
     *
     */
    public interface RemoveListener {

        public void remove(int which);
    }

    public interface DragSortListener extends DropListener, DragListener, RemoveListener {
    }

    public void setDragSortListener(DragSortListener l) {
        setDropListener(l);
        setDragListener(l);
        setRemoveListener(l);
    }

    /**
     * Completely custom scroll speed profile. Default increases linearly
     * with position and is constant in time. Create your own by implementing
     * {@link DragSortListView.DragScrollProfile}.
     * 
     * @param ssp
     */
    public void setDragScrollProfile(DragScrollProfile ssp) {
        if (ssp != null) {
            mScrollProfile = ssp;
        }
    }

    /**
     * Use this to move the check state of an item from one position to another
     * in a drop operation. If you have a choiceMode which is not none, this
     * method must be called when the order of items changes in an underlying
     * adapter which does not have stable IDs (see
     * {@link ListAdapter#hasStableIds()}). This is because without IDs, the
     * ListView has no way of knowing which items have moved where, and cannot
     * update the check state accordingly.
     * <p>
     * A word of warning about a "feature" in Android that you may run into when
     * dealing with movable list items: for an adapter that <em>does</em> have
     * stable IDs, ListView will attempt to locate each item based on its ID and
     * move the check state from the item's old position to the new position —
     * which is all fine and good (and removes the need for calling this
     * function), except for the half-baked approach. Apparently to save time in
     * the naive algorithm used, ListView will only search for an ID in the
     * close neighborhood of the old position. If the user moves an item too far
     * (specifically, more than 20 rows away), ListView will give up and just
     * force the item to be unchecked. So if there is a reasonable chance that
     * the user will move items more than 20 rows away from the original
     * position, you may wish to use an adapter with unstable IDs and call this
     * method manually instead.
     * 
     * @param from
     * @param to
     */
    public void moveCheckState(int from, int to) {
        // This method runs in O(n log n) time (n being the number of list
        // items). The bottleneck is the call to AbsListView.setItemChecked,
        // which is O(log n) because of the binary search involved in calling
        // SparseBooleanArray.put().
        //
        // To improve on the average time, we minimize the number of calls to
        // setItemChecked by only calling it for items that actually have a
        // changed state. This is achieved by building a list containing the
        // start and end of the "runs" of checked items, and then moving the
        // runs. Note that moving an item from A to B is essentially a rotation
        // of the range of items in [A, B]. Let's say we have
        // . . U V X Y Z . .
        // and move U after Z. This is equivalent to a rotation one step to the
        // left within the range you are moving across:
        // . . V X Y Z U . .
        //
        // So, to perform the move we enumerate all the runs within the move
        // range, then rotate each run one step to the left or right (depending
        // on move direction). For example, in the list:
        // X X . X X X . X
        // we have two runs. One begins at the last item of the list and wraps
        // around to the beginning, ending at position 1. The second begins at
        // position 3 and ends at position 5. To rotate a run, regardless of
        // length, we only need to set a check mark at one end of the run, and
        // clear a check mark at the other end:
        // X . X X X . X X
        SparseBooleanArray cip = getCheckedItemPositions();
        int rangeStart = from;
        int rangeEnd = to;
        if (to < from) {
            rangeStart = to;
            rangeEnd = from;
        }
        rangeEnd += 1;

        int[] runStart = new int[cip.size()];
        int[] runEnd = new int[cip.size()];
        int runCount = buildRunList(cip, rangeStart, rangeEnd, runStart, runEnd);
        if (runCount == 1 && (runStart[0] == runEnd[0])) {
            // Special case where all items are checked, we can never set any
            // item to false like we do below.
            return;
        }

        if (from < to) {
            for (int i = 0; i != runCount; i++) {
                setItemChecked(rotate(runStart[i], -1, rangeStart, rangeEnd), true);
                setItemChecked(rotate(runEnd[i], -1, rangeStart, rangeEnd), false);
            }

        } else {
            for (int i = 0; i != runCount; i++) {
                setItemChecked(runStart[i], false);
                setItemChecked(runEnd[i], true);
            }
        }
    }

    /**
     * Use this when an item has been deleted, to move the check state of all
     * following items up one step. If you have a choiceMode which is not none,
     * this method must be called when the order of items changes in an
     * underlying adapter which does not have stable IDs (see
     * {@link ListAdapter#hasStableIds()}). This is because without IDs, the
     * ListView has no way of knowing which items have moved where, and cannot
     * update the check state accordingly.
     * 
     * See also further comments on {@link #moveCheckState(int, int)}.
     * 
     * @param position
     */
    public void removeCheckState(int position) {
        SparseBooleanArray cip = getCheckedItemPositions();

        if (cip.size() == 0)
            return;
        int[] runStart = new int[cip.size()];
        int[] runEnd = new int[cip.size()];
        int rangeStart = position;
        int rangeEnd = cip.keyAt(cip.size() - 1) + 1;
        int runCount = buildRunList(cip, rangeStart, rangeEnd, runStart, runEnd);
        for (int i = 0; i != runCount; i++) {
            if (!(runStart[i] == position || (runEnd[i] < runStart[i] && runEnd[i] > position))) {
                // Only set a new check mark in front of this run if it does
                // not contain the deleted position. If it does, we only need
                // to make it one check mark shorter at the end.
                setItemChecked(rotate(runStart[i], -1, rangeStart, rangeEnd), true);
            }
            setItemChecked(rotate(runEnd[i], -1, rangeStart, rangeEnd), false);
        }
    }

    private static int buildRunList(SparseBooleanArray cip, int rangeStart, int rangeEnd,
            int[] runStart, int[] runEnd) {
        int runCount = 0;

        int i = findFirstSetIndex(cip, rangeStart, rangeEnd);
        if (i == -1)
            return 0;

        int position = cip.keyAt(i);
        int currentRunStart = position;
        int currentRunEnd = currentRunStart + 1;
        for (i++; i < cip.size() && (position = cip.keyAt(i)) < rangeEnd; i++) {
            if (!cip.valueAt(i)) // not checked => not interesting
                continue;
            if (position == currentRunEnd) {
                currentRunEnd++;
            } else {
                runStart[runCount] = currentRunStart;
                runEnd[runCount] = currentRunEnd;
                runCount++;
                currentRunStart = position;
                currentRunEnd = position + 1;
            }
        }

        if (currentRunEnd == rangeEnd) {
            // rangeStart and rangeEnd are equivalent positions so to be
            // consistent we translate them to the same integer value. That way
            // we can check whether a run covers the entire range by just
            // checking if the start equals the end position.
            currentRunEnd = rangeStart;
        }
        runStart[runCount] = currentRunStart;
        runEnd[runCount] = currentRunEnd;
        runCount++;

        if (runCount > 1) {
            if (runStart[0] == rangeStart && runEnd[runCount - 1] == rangeStart) {
                // The last run ends at the end of the range, and the first run
                // starts at the beginning of the range. So they are actually
                // part of the same run, except they wrap around the end of the
                // range. To avoid adjacent runs, we need to merge them.
                runStart[0] = runStart[runCount - 1];
                runCount--;
            }
        }
        return runCount;
    }

    private static int rotate(int value, int offset, int lowerBound, int upperBound) {
        int windowSize = upperBound - lowerBound;

        value += offset;
        if (value < lowerBound) {
            value += windowSize;
        } else if (value >= upperBound) {
            value -= windowSize;
        }
        return value;
    }

    private static int findFirstSetIndex(SparseBooleanArray sba, int rangeStart, int rangeEnd) {
        int size = sba.size();
        int i = insertionIndexForKey(sba, rangeStart);
        while (i < size && sba.keyAt(i) < rangeEnd && !sba.valueAt(i))
            i++;
        if (i == size || sba.keyAt(i) >= rangeEnd)
            return -1;
        return i;
    }

    private static int insertionIndexForKey(SparseBooleanArray sba, int key) {
        int low = 0;
        int high = sba.size();
        while (high - low > 0) {
            int middle = (low + high) >> 1;
            if (sba.keyAt(middle) < key)
                low = middle + 1;
            else
                high = middle;
        }
        return low;
    }

    /**
     * Interface for controlling
     * scroll speed as a function of touch position and time. Use
     * {@link DragSortListView#setDragScrollProfile(DragScrollProfile)} to
     * set custom profile.
     * 
     * @author heycosmo
     *
     */
    public interface DragScrollProfile {

        /**
         * Return a scroll speed in pixels/millisecond. Always return a
         * positive number.
         * 
         * @param w Normalized position in scroll region (i.e. w \in [0,1]).
         * Small w typically means slow scrolling.
         * @param t Time (in milliseconds) since start of scroll (handy if you
         * want scroll acceleration).
         * @return Scroll speed at position w and time t in pixels/ms.
         */
        float getSpeed(float w, long t);
    }

    private class DragScroller implements Runnable {

        private boolean mAbort;

        private long mPrevTime;
        private long mCurrTime;

        private int dy;
        private float dt;
        private long tStart;
        private int scrollDir;

        public final static int STOP = -1;
        public final static int UP = 0;
        public final static int DOWN = 1;

        private float mScrollSpeed; // pixels per ms

        private boolean mScrolling = false;

        private int mLastHeader;
        private int mFirstFooter;

        public boolean isScrolling() {
            return mScrolling;
        }

        public int getScrollDir() {
            return mScrolling ? scrollDir : STOP;
        }

        public DragScroller() {
        }

        public void startScrolling(int dir) {
            if (!mScrolling) {
                // Debug.startMethodTracing("dslv-scroll");
                mAbort = false;
                mScrolling = true;
                tStart = SystemClock.uptimeMillis();
                mPrevTime = tStart;
                scrollDir = dir;
                post(this);
            }
        }

        public void stopScrolling(boolean now) {
            if (now) {
                DragSortListView.this.removeCallbacks(this);
                mScrolling = false;
            } else {
                mAbort = true;
            }

            // Debug.stopMethodTracing();
        }

        @Override
        public void run() {
            if (mAbort) {
                mScrolling = false;
                return;
            }

            // Log.d("mobeta", "scroll");

            final int first = getFirstVisiblePosition();
            final int last = getLastVisiblePosition();
            final int count = getCount();
            final int padTop = getPaddingTop();
            final int listHeight = getHeight() - padTop - getPaddingBottom();

            int minY = Math.min(mY, mFloatViewMid + mFloatViewHeightHalf);
            int maxY = Math.max(mY, mFloatViewMid - mFloatViewHeightHalf);

            if (scrollDir == UP) {
                View v = getChildAt(0);
                // Log.d("mobeta", "vtop="+v.getTop()+" padtop="+padTop);
                if (v == null) {
                    mScrolling = false;
                    return;
                } else {
                    if (first == 0 && v.getTop() == padTop) {
                        mScrolling = false;
                        return;
                    }
                }
                mScrollSpeed = mScrollProfile.getSpeed((mUpScrollStartYF - maxY)
                        / mDragUpScrollHeight, mPrevTime);
            } else {
                View v = getChildAt(last - first);
                if (v == null) {
                    mScrolling = false;
                    return;
                } else {
                    if (last == count - 1 && v.getBottom() <= listHeight + padTop) {
                        mScrolling = false;
                        return;
                    }
                }
                mScrollSpeed = -mScrollProfile.getSpeed((minY - mDownScrollStartYF)
                        / mDragDownScrollHeight, mPrevTime);
            }

            mCurrTime = SystemClock.uptimeMillis();
            dt = (float) (mCurrTime - mPrevTime);

            // dy is change in View position of a list item; i.e. positive dy
            // means user is scrolling up (list item moves down the screen,
            // remember
            // y=0 is at top of View).
            dy = (int) Math.round(mScrollSpeed * dt);

            int movePos;
            if (dy >= 0) {
                dy = Math.min(listHeight, dy);
                movePos = first;
            } else {
                dy = Math.max(-listHeight, dy);
                movePos = last;
            }

            final View moveItem = getChildAt(movePos - first);
            int top = moveItem.getTop() + dy;

            if (movePos == 0 && top > padTop) {
                top = padTop;
            }

            // always do scroll
            mBlockLayoutRequests = true;

            setSelectionFromTop(movePos, top - padTop);
            DragSortListView.this.layoutChildren();
            invalidate();

            mBlockLayoutRequests = false;

            // scroll means relative float View movement
            doDragFloatView(movePos, moveItem, false);

            mPrevTime = mCurrTime;
            // Log.d("mobeta", "  updated prevTime="+mPrevTime);

            post(this);
        }
    }

    private class DragSortTracker {

        StringBuilder mBuilder = new StringBuilder();

        File mFile;

        private int mNumInBuffer = 0;
        private int mNumFlushes = 0;

        private boolean mTracking = false;

        public DragSortTracker() {
            File root = Environment.getExternalStorageDirectory();
            mFile = new File(root, "dslv_state.txt");

            if (!mFile.exists()) {
                try {
                    mFile.createNewFile();
                    Log.d("mobeta", "file created");
                } catch (IOException e) {
                    Log.w("mobeta", "Could not create dslv_state.txt");
                    Log.d("mobeta", e.getMessage());
                }
            }

        }

        public void startTracking() {
            mBuilder.append("<DSLVStates>\n");
            mNumFlushes = 0;
            mTracking = true;
        }

        public void appendState() {
            if (!mTracking) {
                return;
            }

            mBuilder.append("<DSLVState>\n");
            final int children = getChildCount();
            final int first = getFirstVisiblePosition();
            mBuilder.append("    <Positions>");
            for (int i = 0; i < children; ++i) {
                mBuilder.append(first + i).append(",");
            }
            mBuilder.append("</Positions>\n");

            mBuilder.append("    <Tops>");
            for (int i = 0; i < children; ++i) {
                mBuilder.append(getChildAt(i).getTop()).append(",");
            }
            mBuilder.append("</Tops>\n");
            mBuilder.append("    <Bottoms>");
            for (int i = 0; i < children; ++i) {
                mBuilder.append(getChildAt(i).getBottom()).append(",");
            }
            mBuilder.append("</Bottoms>\n");

            mBuilder.append("    <FirstExpPos>").append(mFirstExpPos).append("</FirstExpPos>\n");
            mBuilder.append("    <FirstExpBlankHeight>")
                    .append(getItemHeight(mFirstExpPos) - getChildHeight(mFirstExpPos))
                    .append("</FirstExpBlankHeight>\n");
            mBuilder.append("    <SecondExpPos>").append(mSecondExpPos).append("</SecondExpPos>\n");
            mBuilder.append("    <SecondExpBlankHeight>")
                    .append(getItemHeight(mSecondExpPos) - getChildHeight(mSecondExpPos))
                    .append("</SecondExpBlankHeight>\n");
            mBuilder.append("    <SrcPos>").append(mSrcPos).append("</SrcPos>\n");
            mBuilder.append("    <SrcHeight>").append(mFloatViewHeight + getDividerHeight())
                    .append("</SrcHeight>\n");
            mBuilder.append("    <ViewHeight>").append(getHeight()).append("</ViewHeight>\n");
            mBuilder.append("    <LastY>").append(mLastY).append("</LastY>\n");
            mBuilder.append("    <FloatY>").append(mFloatViewMid).append("</FloatY>\n");
            mBuilder.append("    <ShuffleEdges>");
            for (int i = 0; i < children; ++i) {
                mBuilder.append(getShuffleEdge(first + i, getChildAt(i).getTop())).append(",");
            }
            mBuilder.append("</ShuffleEdges>\n");

            mBuilder.append("</DSLVState>\n");
            mNumInBuffer++;

            if (mNumInBuffer > 1000) {
                flush();
                mNumInBuffer = 0;
            }
        }

        public void flush() {
            if (!mTracking) {
                return;
            }

            // save to file on sdcard
            try {
                boolean append = true;
                if (mNumFlushes == 0) {
                    append = false;
                }
                FileWriter writer = new FileWriter(mFile, append);

                writer.write(mBuilder.toString());
                mBuilder.delete(0, mBuilder.length());

                writer.flush();
                writer.close();

                mNumFlushes++;
            } catch (IOException e) {
                // do nothing
            }
        }

        public void stopTracking() {
            if (mTracking) {
                mBuilder.append("</DSLVStates>\n");
                flush();
                mTracking = false;
            }
        }

    }

}
import java.io.File;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import org.json.JSONObject;
import org.json.JSONTokener;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager.BadTokenException;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import com.beerbong.zipinst.R;
import com.beerbong.zipinst.RequestFileActivity;
import com.beerbong.zipinst.core.Core;
import com.beerbong.zipinst.core.plugins.reboot.RebootPlugin;
import com.beerbong.zipinst.core.plugins.recovery.RecoveryPlugin;
import com.beerbong.zipinst.core.plugins.storage.StoragePlugin;
import com.beerbong.zipinst.http.DownloadFile;
import com.beerbong.zipinst.http.DownloadTask;
import com.beerbong.zipinst.http.URLStringReader;
import com.beerbong.zipinst.http.URLStringReader.URLStringReaderListener;
import com.beerbong.zipinst.io.Files;
import com.beerbong.zipinst.io.Strings;
import com.beerbong.zipinst.io.SystemProperties;
import com.beerbong.zipinst.preferences.Preferences;
import com.beerbong.zipinst.preferences.Rule;
import com.beerbong.zipinst.store.FileItem;
import com.beerbong.zipinst.store.FileItemStore;
import com.beerbong.zipinst.store.FileItemStore.FileItemStoreListener;
import com.beerbong.zipinst.store.FileItemsAdapter;
import com.beerbong.zipinst.store.FileItemsAdapter.FileItemsAdapterHolder;
import com.beerbong.zipinst.ui.UIFragment;
import com.beerbong.zipinst.ui.widget.Dialog;
import com.beerbong.zipinst.ui.widget.Dialog.OnDialogClosedListener;
import com.beerbong.zipinst.ui.widget.FolderPicker;
import com.beerbong.zipinst.ui.widget.RecoveryPicker;
import com.beerbong.zipinst.ui.widget.RecoveryPicker.RecoveryPickerClicked;
import com.mobeta.android.dslv.DragSortListView;
            }
        });
        alert.show();
    }

    private void addZip() {
        Preferences prefs = getCore().getPreferences();
        Context context = getCore().getContext();
        if (prefs.isUseFolder()) {
            File folder = new File(prefs.getFolder());
            if (!folder.exists() || !folder.isDirectory()) {
                Toast.makeText(context, R.string.error_folder, Toast.LENGTH_SHORT).show();
            } else {
                new FolderPicker(context,
                        new DialogInterface.OnClickListener() {

                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                FolderPicker picker = (FolderPicker) dialog;
                                StoragePlugin sPlugin = (StoragePlugin) getCore()
                                        .getPlugin(Core.PLUGIN_STORAGE);
                                sPlugin.addFileItemToStore(picker.getPath());
                            }
                        }, prefs.getFolder(), new String[] { "zip" },
                        null,
                        true).show();
            }
        } else {
            Intent intent = new Intent(context, RequestFileActivity.class);
            context.startActivity(intent);
        }
    }

    private void selectImage(final boolean isBoot) {
        Preferences prefs = getCore().getPreferences();
        Context context = getCore().getContext();
        File folder = new File(prefs.getFolder());
        if (!folder.exists() || !folder.isDirectory()) {
            Toast.makeText(context, R.string.error_folder, Toast.LENGTH_SHORT).show();
        } else {
            FolderPicker p = new FolderPicker(context, new DialogInterface.OnClickListener() {

                @Override
                public void onClick(DialogInterface dialog, int which) {
                    FolderPicker picker = (FolderPicker) dialog;
                    installImage(picker.getPath(), isBoot);
                }
            }, prefs.getFolder(), new String[] { "img" }, null,
                    true);
            try {
                p.show();
            } catch (BadTokenException ex) {
                // ignore
            }
        }
    }

    private void getTwrpUrl() {
        final Context context = getCore().getContext();
        String board = SystemProperties.getProperty("ro.product.device");
        new URLStringReader(new URLStringReaderListener() {

            @Override
            public void onReadEnd(final String buffer) {
                try {
                    if (buffer != null && !"false".equals(buffer)) {
                        ((Activity) context).runOnUiThread(new Runnable() {

                            public void run() {
                                try {
                                    JSONObject result = (JSONObject) new JSONTokener(buffer).nextValue();
                                    new DownloadFile(getCore(),
                                            "http://goo.im" + result.getString("path"),
                                            result.getString("filename"), result.getString("md5"), new DownloadTask.OnDownloadFinishListener() {
            
                                                @Override
                                                public void onDownloadFinish(File file) {
                                                    installImage(file.getAbsolutePath(), false);
                                                }

                                                @Override
                                                public void onDownloadError(Exception ex) {
                                                    Dialog.error(context, R.string.downloading_error, null);
                                                }
                                    });

                                } catch (Exception ex) {
                                    ex.printStackTrace();
                                }
                            }
                        });
                    } else {
                        Dialog.toast(context, R.string.picker_recovery_no_recovery_found);
                    }
                } catch (Exception ex) {
                    System.out.println(buffer);
                    ex.printStackTrace();
                }
            }

            @Override
            public void onReadError(Exception ex) {
                Dialog.toast(context, R.string.picker_recovery_error_twrp);
            }
        }).execute("http://goo.im/json2&action=recovery&ro_board=" + board);
    }

    private void installImage(final String path, final boolean isBoot) {
        final Context context = getCore().getContext();
        int titleId = -1;
        String message = "";
        if (isBoot) {
            titleId = R.string.flashboot_confirm_title;
            message = context.getResources()
                    .getString(R.string.flashboot_confirm_message, path);
        } else {
            titleId = R.string.flashrecovery_confirm_title;
            message = context.getResources()
                    .getString(R.string.flashrecovery_confirm_message, path);
        }
        Dialog.dialog(context, message, titleId, true, new OnDialogClosedListener() {