private static File getBackupDirectory() { SharedPreferences prefs = AnkiDroidApp.getSharedPrefs(AnkiDroidApp.getInstance().getBaseContext()); File directory = new File(prefs.getString("deckPath", AnkiDroidApp.getStorageDirectory()) + BACKUP_SUFFIX); if (!directory.isDirectory()) { directory.mkdirs(); } return directory; }
public void setCard(Card card) { mCurrentCard = card; Long cardId = 0l; if (card != null) { cardId = card.getId(); } AnkiDroidApp.getSharedPrefs(AnkiDroidApp.getInstance().getBaseContext()) .edit() .putLong("lastWidgetCard", cardId) .commit(); }
@Override public void onCreate() { super.onCreate(); SharedPreferences prefs = AnkiDroidApp.getSharedPrefs(AnkiDroidApp.getInstance().getBaseContext()); String path = prefs.getString("lastWidgetDeck", ""); if (path != null && path.length() > 0 && AnkiDroidApp.isSdCardMounted()) { Log.i(AnkiDroidApp.TAG, "BigWidget: reloading deck " + path); mCol = Collection.currentCollection(); if (mCol != null) { mCurrentCard = mCol.getSched().getCard(); } } }
@Override protected void onResume() { super.onResume(); ((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)) .cancel(SIMPLE_NOTIFICATION_ID); // Show any pending dialogs Message handlerMsg = AnkiDroidApp.getStoredDialogHandlerMessage(); if (handlerMsg != null) { // TODO: Confirm whether or not it's ok to send the message without checking if the collection // is open. mHandler.sendMessage(handlerMsg); AnkiDroidApp.setStoredDialogHandlerMessage(null); } }
private void disableDrawerSwipeOnConflicts() { SharedPreferences preferences = AnkiDroidApp.getSharedPrefs(getBaseContext()); boolean gesturesEnabled = AnkiDroidApp.initiateGestures(preferences); if (gesturesEnabled) { int gestureSwipeUp = Integer.parseInt(preferences.getString("gestureSwipeUp", "9")); int gestureSwipeDown = Integer.parseInt(preferences.getString("gestureSwipeDown", "0")); int gestureSwipeRight = Integer.parseInt(preferences.getString("gestureSwipeRight", "17")); if (gestureSwipeUp != GESTURE_NOTHING || gestureSwipeDown != GESTURE_NOTHING || gestureSwipeRight != GESTURE_NOTHING) { mHasDrawerSwipeConflicts = true; super.disableDrawerSwipe(); } } }
@Override protected void setTitle() { try { String[] title = {""}; if (AnkiDroidApp.colIsOpen()) { title = getCol().getDecks().current().getString("name").split("::"); } else { Timber.e("Could not set title in reviewer because collection closed"); } AnkiDroidApp.getCompat().setTitle(this, title[title.length - 1], mNightMode); super.setTitle(title[title.length - 1]); } catch (JSONException e) { throw new RuntimeException(e); } AnkiDroidApp.getCompat().setSubtitle(this, "", mNightMode); }
/** * Factory for AnkiFont creation. Creates a typeface wrapper from a font file representing. * * @param ctx Activity context, needed to access assets * @param path Path to typeface file, needed when this is a custom font. * @param fromAssets True if the font is to be found in assets of application * @return A new AnkiFont object or null if the file can't be interpreted as typeface. */ public static AnkiFont createAnkiFont(Context ctx, String path, boolean fromAssets) { File fontfile = new File(path); String name = Utils.splitFilename(fontfile.getName())[0]; String family = name; List<String> attributes = new ArrayList<>(); if (fromAssets) { path = fAssetPathPrefix.concat(fontfile.getName()); } Typeface tf = getTypeface(ctx, path); if (tf == null) { // unable to create typeface return null; } if (tf.isBold() || name.toLowerCase(Locale.US).contains("bold")) { attributes.add("font-weight: bolder;"); family = family.replaceFirst("(?i)-?Bold", ""); } else if (name.toLowerCase(Locale.US).contains("light")) { attributes.add("font-weight: lighter;"); family = family.replaceFirst("(?i)-?Light", ""); } else { attributes.add("font-weight: normal;"); } if (tf.isItalic() || name.toLowerCase(Locale.US).contains("italic")) { attributes.add("font-style: italic;"); family = family.replaceFirst("(?i)-?Italic", ""); } else if (name.toLowerCase(Locale.US).contains("oblique")) { attributes.add("font-style: oblique;"); family = family.replaceFirst("(?i)-?Oblique", ""); } else { attributes.add("font-style: normal;"); } if (name.toLowerCase(Locale.US).contains("condensed") || name.toLowerCase(Locale.US).contains("narrow")) { attributes.add("font-stretch: condensed;"); family = family.replaceFirst("(?i)-?Condensed", ""); family = family.replaceFirst("(?i)-?Narrow(er)?", ""); } else if (name.toLowerCase(Locale.US).contains("expanded") || name.toLowerCase(Locale.US).contains("wide")) { attributes.add("font-stretch: expanded;"); family = family.replaceFirst("(?i)-?Expanded", ""); family = family.replaceFirst("(?i)-?Wide(r)?", ""); } AnkiFont createdFont = new AnkiFont(name, family, attributes, path); // determine if override font or default font SharedPreferences preferences = AnkiDroidApp.getSharedPrefs(ctx); String defaultFont = preferences.getString("defaultFont", ""); boolean overrideFont = preferences.getString("overrideFontBehavior", "0").equals("1"); if (defaultFont.equalsIgnoreCase(name)) { if (overrideFont) { createdFont.setAsOverride(); } else { createdFont.setAsDefault(); } } return createdFont; }
@Override protected SharedPreferences restorePreferences() { super.restorePreferences(); SharedPreferences preferences = AnkiDroidApp.getSharedPrefs(getBaseContext()); mBlackWhiteboard = preferences.getBoolean("blackWhiteboard", true); return preferences; }
@Override protected void onCreate(Bundle icicle) { // Workaround for bug 4611: http://code.google.com/p/android/issues/detail?id=4611 if (AnkiDroidApp.SDK_VERSION >= 7 && AnkiDroidApp.SDK_VERSION <= 10) { Themes.applyTheme(this, Themes.THEME_ANDROID_DARK); } super.onCreate(icicle); mCol = AnkiDroidApp.getCol(); if (mCol == null) { finish(); return; } mDeck = mCol.getDecks().current(); registerExternalStorageListener(); if (mCol == null) { Log.i(AnkiDroidApp.TAG, "DeckOptions - No Collection loaded"); finish(); return; } else { mPref = new DeckPreferenceHack(); mPref.registerOnSharedPreferenceChangeListener(this); this.addPreferencesFromResource(R.xml.deck_options); this.buildLists(); this.updateSummaries(); } }
// Method for loading the collection which is inherited by all AnkiActivitys public void startLoadingCollection() { // Initialize the open collection loader Timber.d("AnkiActivity.startLoadingCollection()"); if (!AnkiDroidApp.colIsOpen()) { showOpeningCollectionDialog(); } getSupportLoaderManager().restartLoader(0, null, this); }
@Override public void onLoadFinished(Loader<Collection> loader, Collection col) { if (col != null && AnkiDroidApp.colIsOpen()) { onCollectionLoaded(col); } else { onCollectionLoadError(); } }
public SizeControlledListAdapter( Context context, List<? extends Map<String, ?>> data, int resource, String[] from, int[] to, int fontSizeScalePcent) { super(context, data, resource, from, to); this.fontSizeScalePcent = fontSizeScalePcent; mTibetan = AnkiDroidApp.isTibetan(); mTibTypeface = null; if (mTibetan) { mTibTypeface = AnkiDroidApp.getTibetanTypeface(); } }
private void recreateTagsDialog() { Resources res = getResources(); if (allTags == null) { String[] oldTags = AnkiDroidApp.deck().allTags_(); Log.i(AnkiDroidApp.TAG, "all tags: " + Arrays.toString(oldTags)); allTags = new String[oldTags.length]; for (int i = 0; i < oldTags.length; i++) { allTags[i] = oldTags[i]; } } mSelectedTags.clear(); StyledDialog.Builder builder = new StyledDialog.Builder(this); builder.setTitle(R.string.studyoptions_limit_select_tags); builder.setMultiChoiceItems( allTags, new boolean[0], new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { String tag = allTags[which]; if (mSelectedTags.contains(tag)) { Log.i(AnkiDroidApp.TAG, "unchecked tag: " + tag); mSelectedTags.remove(tag); } else { Log.i(AnkiDroidApp.TAG, "checked tag: " + tag); mSelectedTags.add(tag); } } }); builder.setPositiveButton( res.getString(R.string.select), new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { updateCardsList(); } }); builder.setNegativeButton( res.getString(R.string.cancel), new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { mSelectedTags.clear(); } }); builder.setOnCancelListener( new OnCancelListener() { @Override public void onCancel(DialogInterface dialog) { mSelectedTags.clear(); } }); mTagsDialog = builder.create(); }
private String unzipSharedDeckFile(String zipFilename, String title) { ZipInputStream zipInputStream = null; Log.i(AnkiDroidApp.TAG, "unzipSharedDeckFile"); if (zipFilename.endsWith(".zip")) { Log.i(AnkiDroidApp.TAG, "zipFilename ends with .zip"); try { zipInputStream = new ZipInputStream(new FileInputStream(new File(zipFilename))); title = title.replace("^", ""); title = title.substring(0, java.lang.Math.min(title.length(), 40)); if (new File(mDestination + "/" + title + ".anki").exists()) { title += System.currentTimeMillis(); } String partialDeckPath = mDestination + "/tmp/" + title; String deckFilename = partialDeckPath + ".anki.updating"; ZipEntry zipEntry = null; while ((zipEntry = zipInputStream.getNextEntry()) != null) { Log.i(AnkiDroidApp.TAG, "zipEntry = " + zipEntry.getName()); if ("shared.anki".equalsIgnoreCase(zipEntry.getName())) { Utils.writeToFile(zipInputStream, deckFilename); } else if (zipEntry.getName().startsWith("shared.media/", 0)) { Log.i( AnkiDroidApp.TAG, "Folder created = " + new File(partialDeckPath + ".media/").mkdir()); Log.i( AnkiDroidApp.TAG, "Destination = " + AnkiDroidApp.getStorageDirectory() + "/" + title + ".media/" + zipEntry.getName().replace("shared.media/", "")); Utils.writeToFile( zipInputStream, partialDeckPath + ".media/" + zipEntry.getName().replace("shared.media/", "")); } } zipInputStream.close(); // Delete zip file new File(zipFilename).delete(); } catch (FileNotFoundException e) { Log.e(AnkiDroidApp.TAG, "FileNotFoundException = " + e.getMessage()); e.printStackTrace(); } catch (IOException e) { Log.e(AnkiDroidApp.TAG, "IOException = " + e.getMessage()); e.printStackTrace(); } } return title; }
@Override public void onDestroy() { // // TODO: this does not seem to be reliably called // String path = ""; long cardId = 0l; if (mCol != null) { // path = mLoadedDeck.getDeckPath(); // DeckManager.closeDeck(path, DeckManager.REQUESTING_ACTIVITY_BIGWIDGET); if (mCurrentCard != null) { cardId = mCurrentCard.getId(); } } // PrefSettings.getSharedPrefs(AnkiDroidApp.getInstance().getBaseContext()).edit().putString("lastWidgetDeck", // path).commit(); AnkiDroidApp.getSharedPrefs(AnkiDroidApp.getInstance().getBaseContext()) .edit() .putLong("lastWidgetCard", cardId) .commit(); }
/** * Global method to show a dialog fragment including adding it to back stack and handling the case * where the dialog is shown from an async task, by showing the message in the notification bar if * the activity was stopped before the AsyncTask completed * * @param newFragment the AsyncDialogFragment you want to show */ public void showAsyncDialogFragment(AsyncDialogFragment newFragment) { try { showDialogFragment(newFragment); } catch (IllegalStateException e) { String title = newFragment.getNotificationTitle(); String message = newFragment.getNotificationMessage(); AnkiDroidApp.setStoredDialogHandlerMessage(newFragment.getDialogHandlerMessage()); showSimpleNotification(title, message); } }
public static boolean initialize(Context context) { mContext = context; SharedPreferences preferences = AnkiDroidApp.getSharedPrefs(AnkiDroidApp.getInstance().getBaseContext()); mDictionary = Integer.parseInt(preferences.getString("dictionary", Integer.toString(DICTIONARY_NONE))); switch (mDictionary) { case DICTIONARY_NONE: mIsDictionaryAvailable = false; break; case DICTIONARY_AEDICT: mDictionaryAction = "sk.baka.aedict.action.ACTION_SEARCH_EDICT"; mIsDictionaryAvailable = Utils.isIntentAvailable(mContext, mDictionaryAction); break; case DICTIONARY_LEO_WEB: case DICTIONARY_NCIKU_WEB: case DICTIONARY_EIJIRO_WEB: mDictionaryAction = "android.intent.action.VIEW"; mIsDictionaryAvailable = Utils.isIntentAvailable(mContext, mDictionaryAction); break; case DICTIONARY_LEO_APP: mDictionaryAction = "android.intent.action.SEND"; mIsDictionaryAvailable = Utils.isIntentAvailable( mContext, mDictionaryAction, new ComponentName("org.leo.android.dict", "org.leo.android.dict.LeoDict")); break; case DICTIONARY_COLORDICT: mDictionaryAction = "colordict.intent.action.SEARCH"; mIsDictionaryAvailable = Utils.isIntentAvailable(mContext, mDictionaryAction); break; case DICTIONARY_FORA: mDictionaryAction = "com.ngc.fora.action.LOOKUP"; mIsDictionaryAvailable = Utils.isIntentAvailable(mContext, mDictionaryAction); break; default: mIsDictionaryAvailable = false; break; } Log.i(AnkiDroidApp.TAG, "Is intent available = " + mIsDictionaryAvailable); return mIsDictionaryAvailable; }
@Override public void onCorruption(SQLiteDatabase db) { Timber.e("The database has been corrupted..."); AnkiDroidApp.sendExceptionReport( new RuntimeException("Database corrupted"), "AnkiDb.MyDbErrorHandler.onCorruption", "Db has been corrupted "); CollectionHelper.getInstance().closeCollection(false); DatabaseErrorDialog.databaseCorruptFlag = true; }
@Override protected void onStop() { super.onStop(); if (!isFinishing()) { if (AnkiDroidApp.colIsOpen()) { WidgetStatus.update(this, mSched.progressToday(null, mCurrentCard, true)); } } UIUtils.saveCollectionInBackground(); }
/** * If collection has not been opened for a long time, we perform a backup here because Android * deleted sometimes corrupted decks */ public static boolean safetyBackupNeeded(String path, int days) { if (!AnkiDroidApp.getSharedPrefs(AnkiDroidApp.getInstance().getBaseContext()) .getBoolean("useBackup", true)) { return false; } File collectionFile = new File(path); File[] deckBackups = getBackups(collectionFile); int len = deckBackups.length; if (len == 0) { // no backup available return true; } else if (deckBackups[len - 1].lastModified() == collectionFile.lastModified()) { return false; } SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd-HH-mm"); Calendar cal = new GregorianCalendar(); cal.setTimeInMillis(System.currentTimeMillis()); Date lastBackupDate = null; while (lastBackupDate == null && len > 0) { try { len--; lastBackupDate = df.parse( deckBackups[len] .getName() .replaceAll("^.*-(\\d{4}-\\d{2}-\\d{2}-\\d{2}-\\d{2}).anki2$", "$1")); } catch (ParseException e) { lastBackupDate = null; } } if (lastBackupDate == null) { return true; } else if (lastBackupDate.getTime() + days * 24 * 3600000 < Utils.intNow(1000)) { return true; } else { return false; } }
/** * Get the package version as defined in the manifest. * * @return the package version. */ public static String getPkgVersion() { String pkgVersion = "?"; Context context = sInstance.getApplicationContext(); try { PackageInfo pInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0); pkgVersion = pInfo.versionName; } catch (PackageManager.NameNotFoundException e) { Log.e(TAG, "Couldn't find package named " + context.getPackageName(), e); } return pkgVersion; }
/** * Get package name as defined in the manifest. * * @return the package name. */ public static String getPkgName() { String pkgName = TAG; Context context = sInstance.getApplicationContext(); try { PackageInfo pInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0); pkgName = context.getString(pInfo.applicationInfo.labelRes); } catch (PackageManager.NameNotFoundException e) { Log.e(TAG, "Couldn't find package named " + context.getPackageName(), e); } return pkgName; }
private void reloadCollection(final Bundle savedInstanceState) { DeckTask.launchDeckTask( DeckTask.TASK_TYPE_OPEN_COLLECTION, new DeckTask.TaskListener() { @Override public void onPostExecute(DeckTask.TaskData result) { mCol = result.getCollection(); Collection.putCurrentCollection(mCol); if (mCol == null) { finish(); } else { onCreate(savedInstanceState); } } @Override public void onPreExecute() { mOpenCollectionDialog = StyledOpenCollectionDialog.show( CardBrowser.this, getResources().getString(R.string.open_collection), new OnCancelListener() { @Override public void onCancel(DialogInterface arg0) { finish(); } }); } @Override public void onProgressUpdate(DeckTask.TaskData... values) {} }, new DeckTask.TaskData( AnkiDroidApp.getSharedPrefs(getBaseContext()) .getString("deckPath", AnkiDroidApp.getDefaultAnkiDroidDirectory()) + AnkiDroidApp.COLLECTION_PATH)); }
@Override protected void onResume() { super.onResume(); getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this); // syncAccount's summary can change while preferences are still open (user logs // in from preferences screen), so we need to update it here. SharedPreferences preferences = AnkiDroidApp.getSharedPrefs(getBaseContext()); String username = preferences.getString("username", ""); if (TextUtils.isEmpty(username)) { syncAccount.setSummary(R.string.sync_account_summ_logged_out); } else { syncAccount.setSummary(getString(R.string.sync_account_summ_logged_in, username)); } }
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); initAllContentViews(); initAllAlertDialogs(); if (AnkiDroidApp.isUserLoggedIn()) { SharedPreferences preferences = PrefSettings.getSharedPrefs(getBaseContext()); String username = preferences.getString("username", ""); mUsernameLoggedIn.setText(username); setContentView(mLoggedIntoMyAccountView); } else { setContentView(mLoginToMyAccountView); } }
/** On application creation. */ @Override public void onCreate() { super.onCreate(); sInstance = this; Connection.setContext(getApplicationContext()); // Error Reporter CustomExceptionHandler customExceptionHandler = CustomExceptionHandler.getInstance(); customExceptionHandler.Init(sInstance.getApplicationContext()); Thread.setDefaultUncaughtExceptionHandler(customExceptionHandler); SharedPreferences preferences = PrefSettings.getSharedPrefs(this); // Assign some default settings if necessary if (preferences.getString(PrefSettings.KEY_CHECK_URI, null) == null) { Editor editor = preferences.edit(); // Test Update Notifications // Some ridiculously fast polling, just to demonstrate it working... /* * editor.putBoolean(PrefSettings.KEY_ENABLED, true); editor.putLong(PrefSettings.KEY_PERIOD, 30 * 1000L); * editor.putLong(PrefSettings.KEY_CHECK_INTERVAL, 60 * 1000L); editor.putString(PrefSettings.KEY_CHECK_URI, * "http://ankidroid.googlecode.com/files/test_notifications.xml"); */ editor.putString( PrefSettings.KEY_CHECK_URI, "http://ankidroid.googlecode.com/files/last_release.xml"); // Create the folder "AnkiDroid", if not exists, where the decks // will be stored by default new File(getStorageDirectory() + "/AnkiDroid").mkdir(); // Put the base path in preferences pointing to the default // "AnkiDroid" folder editor.putString("deckPath", getStorageDirectory() + "/AnkiDroid"); // Using commit instead of apply even though we don't need a return value. // Reason: apply() not available on Android 1.5 editor.commit(); } // Reschedule the checks - we need to do this if the settings have // changed (as above) // It may also necessary in the case where an application has been // updated // Here for simplicity, we do it every time the application is launched Intent intent = new Intent(Veecheck.getRescheduleAction(this)); sendBroadcast(intent); }
protected void showSimpleNotification(String title, String message) { SharedPreferences prefs = AnkiDroidApp.getSharedPrefs(this); // Don't show notification if disabled in preferences if (Integer.parseInt(prefs.getString("minimumCardsDueForNotification", "0")) <= 1000000) { // Use the title as the ticker unless the title is simply "AnkiDroid" String ticker = title; if (title.equals(getResources().getString(R.string.app_name))) { ticker = message; } // Build basic notification NotificationCompat.Builder builder = new NotificationCompat.Builder(this) .setSmallIcon(R.drawable.ic_stat_notify) .setContentTitle(title) .setContentText(message) .setStyle(new NotificationCompat.BigTextStyle().bigText(message)) .setTicker(ticker); // Enable vibrate and blink if set in preferences if (prefs.getBoolean("widgetVibrate", false)) { builder.setVibrate(new long[] {1000, 1000, 1000}); } if (prefs.getBoolean("widgetBlink", false)) { builder.setLights(Color.BLUE, 1000, 1000); } // Creates an explicit intent for an Activity in your app Intent resultIntent = new Intent(this, DeckPicker.class); resultIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | IntentCompat.FLAG_ACTIVITY_CLEAR_TASK); PendingIntent resultPendingIntent = PendingIntent.getActivity(this, 0, resultIntent, PendingIntent.FLAG_UPDATE_CURRENT); if (resultPendingIntent == null) { // PendingIntent could not be created... probably something wrong with the extras // try again without the extras, though the original dialog will not be shown when app // started Timber.e("AnkiActivity.showSimpleNotification() failed due to null PendingIntent"); resultIntent = new Intent(this, DeckPicker.class); resultPendingIntent = PendingIntent.getActivity(this, 0, resultIntent, PendingIntent.FLAG_UPDATE_CURRENT); } builder.setContentIntent(resultPendingIntent); NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); // mId allows you to update the notification later on. notificationManager.notify(SIMPLE_NOTIFICATION_ID, builder.build()); } }
// It could be part of the AIDL Interface but at the moment no Activity uses it directly public void resumeDownload(Download download) { // Create tmp folder where the temporal decks are going to be stored new File(mDestination + "/tmp/").mkdirs(); AnkiDroidApp.createNoMediaFileIfMissing(new File(mDestination)); if (download instanceof SharedDeckDownload) { SharedDeckDownload sharedDeckDownload = (SharedDeckDownload) download; // We need to go through UpdateDeckTask even when the download is paused, in order for // numUpdatedCards and numTotalCards to get updated, so that progress is displayed correctly if (sharedDeckDownload.getStatus() == SharedDeckDownload.STATUS_PAUSED || sharedDeckDownload.getStatus() == SharedDeckDownload.STATUS_UPDATING) { new UpdateDeckTask().execute(new Payload(new Object[] {sharedDeckDownload})); } else { new DownloadSharedDeckTask().execute(sharedDeckDownload); } } else { // TODO: Check if there is already a deck with the same name, and if that's so // add the current milliseconds to the end of the name or notify the user new DownloadPersonalDeckTask().execute(download); } }
public static Typeface getTypeface(Context ctx, String path) { try { if (path.startsWith(fAssetPathPrefix)) { return Typeface.createFromAsset(ctx.getAssets(), path.replaceFirst("/android_asset/", "")); } else { return Typeface.createFromFile(path); } } catch (RuntimeException e) { Timber.w(e, "Runtime error in getTypeface for File: %s", path); if (!corruptFonts.contains(path)) { // Show warning toast String name = new File(path).getName(); Resources res = AnkiDroidApp.getAppResources(); Toast toast = Toast.makeText(ctx, res.getString(R.string.corrupt_font, name), Toast.LENGTH_LONG); toast.show(); // Don't warn again in this session corruptFonts.add(path); } return null; } }
public static boolean repairDeck(String deckPath) { File deckFile = new File(deckPath); Collection col = AnkiDroidApp.getCol(); if (col != null) { col.close(); } AnkiDatabaseManager.closeDatabase(deckPath); // repair file String execString = "sqlite3 " + deckPath + " .dump | sqlite3 " + deckPath + ".tmp"; Log.i(AnkiDroidApp.TAG, "repairDeck - Execute: " + execString); try { String[] cmd = {"/system/bin/sh", "-c", execString}; Process process = Runtime.getRuntime().exec(cmd); process.waitFor(); if (!new File(deckPath + ".tmp").exists()) { return false; } if (!moveDatabaseToBrokenFolder(deckPath, false)) { return false; } Log.i(AnkiDroidApp.TAG, "repairDeck - moved corrupt file to broken folder"); File repairedFile = new File(deckPath + ".tmp"); if (!repairedFile.renameTo(deckFile)) { return false; } return true; } catch (IOException e) { Log.e("AnkiDroidApp.TAG", "repairCollection - error: " + e); } catch (InterruptedException e) { Log.e("AnkiDroidApp.TAG", "repairCollection - error: " + e); } return false; }