use of com.ichi2.anki.exception.StorageAccessException in project AnkiChinaAndroid by ankichinateam.
the class AnkiDroidApp method onCreate.
/**
* On application creation.
*/
@Override
public void onCreate() {
super.onCreate();
registerActivityLifecycleCallbacks(activityLifecycleCallbacks);
if (sInstance != null) {
Timber.i("onCreate() called multiple times");
// 5887 - fix crash.
if (sInstance.getResources() == null) {
Timber.w("Skipping re-initialisation - no resources. Maybe uninstalling app?");
return;
}
}
sInstance = this;
// Get preferences
SharedPreferences preferences = getSharedPrefs(this);
Consts.LOGIN_SERVER = preferences.getInt(Consts.KEY_ANKI_ACCOUNT_SERVER, 0);
// Setup logging and crash reporting
acraCoreConfigBuilder = new CoreConfigurationBuilder(this);
if (BuildConfig.DEBUG) {
// Enable verbose error logging and do method tracing to put the Class name as log tag
Timber.plant(new DebugTree());
setDebugACRAConfig(preferences);
} else {
Timber.plant(new ProductionCrashReportingTree());
setProductionACRAConfig(preferences);
}
Timber.tag(TAG);
Timber.d("Startup - Application Start");
// Analytics falls back to a sensible default if this is not set.
if (ACRA.isACRASenderServiceProcess() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
try {
WebViewDebugging.setDataDirectorySuffix("acra");
} catch (Exception e) {
Timber.w(e, "Failed to set WebView data directory");
}
}
// analytics after ACRA, they both install UncaughtExceptionHandlers but Analytics chains while ACRA does not
UsageAnalytics.initialize(this);
if (BuildConfig.DEBUG) {
UsageAnalytics.setDryRun(true);
}
// Stop after analytics and logging are initialised.
if (ACRA.isACRASenderServiceProcess()) {
Timber.d("Skipping AnkiDroidApp.onCreate from ACRA sender process");
return;
}
if (AdaptionUtil.isUserATestClient()) {
UIUtils.showThemedToast(this.getApplicationContext(), getString(R.string.user_is_a_robot), false);
}
CardBrowserContextMenu.ensureConsistentStateWithSharedPreferences(this);
AnkiCardContextMenu.ensureConsistentStateWithSharedPreferences(this);
NotificationChannels.setup(getApplicationContext());
// Configure WebView to allow file scheme pages to access cookies.
CookieManager.setAcceptFileSchemeCookies(true);
// Prepare Cookies to be synchronized between RAM and permanent storage.
CompatHelper.getCompat().prepareWebViewCookies(this.getApplicationContext());
// Set good default values for swipe detection
final ViewConfiguration vc = ViewConfiguration.get(this);
DEFAULT_SWIPE_MIN_DISTANCE = vc.getScaledPagingTouchSlop();
DEFAULT_SWIPE_THRESHOLD_VELOCITY = vc.getScaledMinimumFlingVelocity();
// Forget the last deck that was used in the CardBrowser
CardBrowser.clearLastDeckId();
// Create the AnkiDroid directory if missing. Send exception report if inaccessible.
if (Permissions.hasStorageAccessPermission(this)) {
try {
String dir = CollectionHelper.getCurrentAnkiDroidDirectory(this);
CollectionHelper.initializeAnkiDroidDirectory(dir);
} catch (StorageAccessException e) {
Timber.e(e, "Could not initialize AnkiDroid directory");
String defaultDir = CollectionHelper.getDefaultAnkiDroidDirectory();
if (isSdCardMounted() && CollectionHelper.getCurrentAnkiDroidDirectory(this).equals(defaultDir)) {
// Don't send report if the user is using a custom directory as SD cards trip up here a lot
sendExceptionReport(e, "AnkiDroidApp.onCreate");
}
}
}
Timber.i("AnkiDroidApp: Starting Services");
new BootService().onReceive(this, new Intent(this, BootService.class));
// Register BroadcastReceiver NotificationService
NotificationService ns = new NotificationService();
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(this);
lbm.registerReceiver(ns, new IntentFilter(NotificationService.INTENT_ACTION));
}
use of com.ichi2.anki.exception.StorageAccessException in project Anki-Android by ankidroid.
the class AnkiDroidApp method onCreate.
/**
* On application creation.
*/
@Override
public void onCreate() {
super.onCreate();
if (sInstance != null) {
Timber.i("onCreate() called multiple times");
// 5887 - fix crash.
if (sInstance.getResources() == null) {
Timber.w("Skipping re-initialisation - no resources. Maybe uninstalling app?");
return;
}
}
sInstance = this;
// Get preferences
SharedPreferences preferences = getSharedPrefs(this);
// Setup logging and crash reporting
mAcraCoreConfigBuilder = new CoreConfigurationBuilder(this);
if (BuildConfig.DEBUG) {
// Enable verbose error logging and do method tracing to put the Class name as log tag
Timber.plant(new DebugTree());
setDebugACRAConfig(preferences);
List<ReferenceMatcher> referenceMatchers = new ArrayList<>();
// Add known memory leaks to 'referenceMatchers'
matchKnownMemoryLeaks(referenceMatchers);
// AppWatcher manual install if not already installed
if (!AppWatcher.INSTANCE.isInstalled()) {
AppWatcher.INSTANCE.manualInstall(this);
}
// Show 'Leaks' app launcher. It has been removed by default via constants.xml.
LeakCanary.INSTANCE.showLeakDisplayActivityLauncherIcon(true);
} else {
Timber.plant(new ProductionCrashReportingTree());
setProductionACRAConfig(preferences);
disableLeakCanary();
}
Timber.tag(TAG);
Timber.d("Startup - Application Start");
// Analytics falls back to a sensible default if this is not set.
if (ACRA.isACRASenderServiceProcess() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
try {
WebViewDebugging.setDataDirectorySuffix("acra");
} catch (Exception e) {
Timber.w(e, "Failed to set WebView data directory");
}
}
// analytics after ACRA, they both install UncaughtExceptionHandlers but Analytics chains while ACRA does not
UsageAnalytics.initialize(this);
if (BuildConfig.DEBUG) {
UsageAnalytics.setDryRun(true);
}
// Stop after analytics and logging are initialised.
if (ACRA.isACRASenderServiceProcess()) {
Timber.d("Skipping AnkiDroidApp.onCreate from ACRA sender process");
return;
}
if (AdaptionUtil.isUserATestClient()) {
UIUtils.showThemedToast(this.getApplicationContext(), getString(R.string.user_is_a_robot), false);
}
// make default HTML / JS debugging true for debug build and disable for unit/android tests
if (BuildConfig.DEBUG && !AdaptionUtil.isRunningAsUnitTest()) {
preferences.edit().putBoolean("html_javascript_debugging", true).apply();
}
CardBrowserContextMenu.ensureConsistentStateWithSharedPreferences(this);
AnkiCardContextMenu.ensureConsistentStateWithSharedPreferences(this);
NotificationChannels.setup(getApplicationContext());
// Configure WebView to allow file scheme pages to access cookies.
if (!acceptFileSchemeCookies()) {
return;
}
// Forget the last deck that was used in the CardBrowser
CardBrowser.clearLastDeckId();
// Create the AnkiDroid directory if missing. Send exception report if inaccessible.
if (Permissions.hasStorageAccessPermission(this)) {
try {
String dir = CollectionHelper.getCurrentAnkiDroidDirectory(this);
CollectionHelper.initializeAnkiDroidDirectory(dir);
} catch (StorageAccessException e) {
Timber.e(e, "Could not initialize AnkiDroid directory");
String defaultDir = CollectionHelper.getDefaultAnkiDroidDirectory(this);
if (isSdCardMounted() && CollectionHelper.getCurrentAnkiDroidDirectory(this).equals(defaultDir)) {
// Don't send report if the user is using a custom directory as SD cards trip up here a lot
sendExceptionReport(e, "AnkiDroidApp.onCreate");
}
}
}
Timber.i("AnkiDroidApp: Starting Services");
new BootService().onReceive(this, new Intent(this, BootService.class));
// Register BroadcastReceiver NotificationService
NotificationService ns = new NotificationService();
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(this);
lbm.registerReceiver(ns, new IntentFilter(NotificationService.INTENT_ACTION));
}
use of com.ichi2.anki.exception.StorageAccessException in project Anki-Android by ankidroid.
the class CollectionHelper method initializeAnkiDroidDirectory.
/**
* Create the AnkiDroid directory if it doesn't exist and add a .nomedia file to it if needed.
*
* The AnkiDroid directory is a user preference stored under {@link #PREF_DECK_PATH}, and a sensible
* default is chosen if the preference hasn't been created yet (i.e., on the first run).
*
* The presence of a .nomedia file indicates to media scanners that the directory must be
* excluded from their search. We need to include this to avoid media scanners including
* media files from the collection.media directory. The .nomedia file works at the directory
* level, so placing it in the AnkiDroid directory will ensure media scanners will also exclude
* the collection.media sub-directory.
*
* @param path Directory to initialize
* @throws StorageAccessException If no write access to directory
*/
public static synchronized void initializeAnkiDroidDirectory(String path) throws StorageAccessException {
// Create specified directory if it doesn't exit
File dir = new File(path);
if (!dir.exists() && !dir.mkdirs()) {
throw new StorageAccessException("Failed to create AnkiDroid directory " + path);
}
if (!dir.canWrite()) {
throw new StorageAccessException("No write access to AnkiDroid directory " + path);
}
// Add a .nomedia file to it if it doesn't exist
File nomedia = new File(dir, ".nomedia");
if (!nomedia.exists()) {
try {
nomedia.createNewFile();
} catch (IOException e) {
throw new StorageAccessException("Failed to create .nomedia file", e);
}
}
}
use of com.ichi2.anki.exception.StorageAccessException in project AnkiChinaAndroid by ankichinateam.
the class Preferences method initSubscreen.
private void initSubscreen(String action, PreferenceContext listener) {
android.preference.PreferenceScreen screen;
switch(action) {
case "com.ichi2.anki.prefs.general":
listener.addPreferencesFromResource(R.xml.preferences_general);
screen = listener.getPreferenceScreen();
if (AdaptionUtil.isRestrictedLearningDevice()) {
android.preference.CheckBoxPreference mCheckBoxPref_Vibrate = (android.preference.CheckBoxPreference) screen.findPreference("widgetVibrate");
android.preference.CheckBoxPreference mCheckBoxPref_Blink = (android.preference.CheckBoxPreference) screen.findPreference("widgetBlink");
android.preference.PreferenceCategory mCategory = (android.preference.PreferenceCategory) screen.findPreference("category_general_notification_pref");
mCategory.removePreference(mCheckBoxPref_Vibrate);
mCategory.removePreference(mCheckBoxPref_Blink);
}
// Build languages
initializeLanguageDialog(screen);
break;
case "com.ichi2.anki.prefs.reviewing":
listener.addPreferencesFromResource(R.xml.preferences_reviewing);
screen = listener.getPreferenceScreen();
// Show error toast if the user tries to disable answer button without gestures on
// android.preference.ListPreference fullscreenPreference = (android.preference.ListPreference)
// screen.findPreference("fullscreenMode");
// fullscreenPreference.setOnPreferenceChangeListener((preference, newValue) -> {
// SharedPreferences prefs = AnkiDroidApp.getSharedPrefs(Preferences.this);
// if (prefs.getBoolean("gestures", false) || !"2".equals(newValue)) {
// return true;
// } else {
// Toast.makeText(getApplicationContext(),
// R.string.full_screen_error_gestures, Toast.LENGTH_LONG).show();
// return false;
// }
// });
// Custom buttons options
android.preference.Preference customButtonsPreference = screen.findPreference("custom_buttons_link");
customButtonsPreference.setOnPreferenceClickListener(preference -> {
Intent i = getPreferenceSubscreenIntent(Preferences.this, "com.ichi2.anki.prefs.custom_buttons");
startActivity(i);
return true;
});
break;
case "com.ichi2.anki.prefs.appearance":
listener.addPreferencesFromResource(R.xml.preferences_appearance);
screen = listener.getPreferenceScreen();
backgroundImage = (android.preference.CheckBoxPreference) screen.findPreference("deckPickerBackground");
backgroundImage.setOnPreferenceClickListener(preference -> {
if (backgroundImage.isChecked()) {
try {
Intent galleryIntent = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
startActivityForResult(galleryIntent, RESULT_LOAD_IMG);
backgroundImage.setChecked(true);
} catch (Exception ex) {
Timber.e("%s", ex.getLocalizedMessage());
}
} else {
backgroundImage.setChecked(false);
String currentAnkiDroidDirectory = CollectionHelper.getCurrentAnkiDroidDirectory(this);
File imgFile = new File(currentAnkiDroidDirectory, "DeckPickerBackground.png");
if (imgFile.exists()) {
if (imgFile.delete()) {
UIUtils.showThemedToast(this, getString(R.string.background_image_removed), false);
} else {
UIUtils.showThemedToast(this, getString(R.string.error_deleting_image), false);
}
} else {
UIUtils.showThemedToast(this, getString(R.string.background_image_removed), false);
}
}
return true;
});
initializeCustomFontsDialog(screen);
break;
case "com.ichi2.anki.prefs.gestures":
listener.addPreferencesFromResource(R.xml.preferences_gestures);
break;
case "com.ichi2.anki.prefs.custom_controller_buttons":
getSupportActionBar().setTitle(R.string.pref_cat_controller);
listener.addPreferencesFromResource(R.xml.preferences_custom_controller_buttons);
break;
case "com.ichi2.anki.prefs.custom_buttons":
getSupportActionBar().setTitle(R.string.custom_buttons);
listener.addPreferencesFromResource(R.xml.preferences_custom_buttons);
screen = listener.getPreferenceScreen();
// Reset toolbar button customizations
android.preference.Preference reset_custom_buttons = screen.findPreference("reset_custom_buttons");
reset_custom_buttons.setOnPreferenceClickListener(preference -> {
SharedPreferences.Editor edit = AnkiDroidApp.getSharedPrefs(getBaseContext()).edit();
edit.remove("customButtonUndo");
edit.remove("customButtonScheduleCard");
edit.remove("customButtonMarkCard");
edit.remove("customButtonEditCard");
edit.remove("customButtonAddCard");
edit.remove("customButtonReplay");
edit.remove("customButtonSelectTts");
edit.remove("customButtonDeckOptions");
edit.remove("customButtonBury");
edit.remove("customButtonSuspend");
edit.remove("customButtonFlag");
edit.remove("customButtonDelete");
edit.remove("customButtonClearWhiteboard");
edit.remove("customButtonShowHideWhiteboard");
edit.apply();
// TODO: Should reload the preferences screen on completion
return true;
});
break;
case "com.ichi2.anki.prefs.advanced":
listener.addPreferencesFromResource(R.xml.preferences_advanced);
screen = listener.getPreferenceScreen();
// Check that input is valid before committing change in the collection path
android.preference.EditTextPreference collectionPathPreference = (android.preference.EditTextPreference) screen.findPreference("deckPath");
collectionPathPreference.setOnPreferenceChangeListener((preference, newValue) -> {
final String newPath = (String) newValue;
try {
CollectionHelper.initializeAnkiDroidDirectory(newPath);
return true;
} catch (StorageAccessException e) {
Timber.e(e, "Could not initialize directory: %s", newPath);
Toast.makeText(getApplicationContext(), R.string.dialog_collection_path_not_dir, Toast.LENGTH_LONG).show();
return false;
}
});
// Custom sync server option
// android.preference.Preference customSyncServerPreference = screen.findPreference("custom_sync_server_link");
// customSyncServerPreference.setOnPreferenceClickListener(preference -> {
// Intent i = getPreferenceSubscreenIntent(Preferences.this,
// "com.ichi2.anki.prefs.custom_sync_server");
// startActivity(i);
// return true;
// });
// Advanced statistics option
android.preference.Preference advancedStatisticsPreference = screen.findPreference("advanced_statistics_link");
advancedStatisticsPreference.setOnPreferenceClickListener(preference -> {
Intent i = getPreferenceSubscreenIntent(Preferences.this, "com.ichi2.anki.prefs.advanced_statistics");
startActivity(i);
return true;
});
setupContextMenuPreference(screen, CardBrowserContextMenu.CARD_BROWSER_CONTEXT_MENU_PREF_KEY, R.string.card_browser_context_menu);
setupContextMenuPreference(screen, AnkiCardContextMenu.ANKI_CARD_CONTEXT_MENU_PREF_KEY, R.string.context_menu_anki_card_label);
// Make it possible to test crash reporting, but only for DEBUG builds
if (BuildConfig.DEBUG && !AdaptionUtil.isUserATestClient()) {
Timber.i("Debug mode, allowing for test crashes");
android.preference.Preference triggerTestCrashPreference = new android.preference.Preference(this);
triggerTestCrashPreference.setKey("trigger_crash_preference");
triggerTestCrashPreference.setTitle("Trigger test crash");
triggerTestCrashPreference.setSummary("Touch here for an immediate test crash");
triggerTestCrashPreference.setOnPreferenceClickListener(preference -> {
Timber.w("Crash triggered on purpose from advanced preferences in debug mode");
throw new RuntimeException("This is a test crash");
});
screen.addPreference(triggerTestCrashPreference);
}
// Make it possible to test analytics, but only for DEBUG builds
if (BuildConfig.DEBUG) {
Timber.i("Debug mode, allowing for dynamic analytics config");
android.preference.Preference analyticsDebugMode = new android.preference.Preference(this);
analyticsDebugMode.setKey("analytics_debug_preference");
analyticsDebugMode.setTitle("Switch Analytics to dev mode");
analyticsDebugMode.setSummary("Touch here to use Analytics dev tag and 100% sample rate");
analyticsDebugMode.setOnPreferenceClickListener(preference -> {
UsageAnalytics.setDevMode();
return true;
});
screen.addPreference(analyticsDebugMode);
}
if (BuildConfig.DEBUG) {
Timber.i("Debug mode, allowing database lock preference");
android.preference.Preference lockDbPreference = new android.preference.Preference(this);
lockDbPreference.setKey("debug_lock_database");
lockDbPreference.setTitle("Lock Database");
lockDbPreference.setSummary("Touch here to lock the database (all threads block in-process, exception if using second process)");
lockDbPreference.setOnPreferenceClickListener(preference -> {
DatabaseLock.engage(this);
return true;
});
screen.addPreference(lockDbPreference);
}
// Force full sync option
ConfirmationPreference fullSyncPreference = (ConfirmationPreference) screen.findPreference("force_full_sync");
fullSyncPreference.setDialogMessage(R.string.force_full_sync_summary);
fullSyncPreference.setDialogTitle(R.string.force_full_sync_title);
fullSyncPreference.setOkHandler(() -> {
if (getCol() == null) {
Toast.makeText(getApplicationContext(), R.string.directory_inaccessible, Toast.LENGTH_LONG).show();
return;
}
getCol().modSchemaNoCheck();
getCol().setMod();
Toast.makeText(getApplicationContext(), android.R.string.ok, Toast.LENGTH_SHORT).show();
});
// Workaround preferences
removeUnnecessaryAdvancedPrefs(screen);
addThirdPartyAppsListener(screen);
break;
case "com.ichi2.anki.prefs.custom_sync_server":
getSupportActionBar().setTitle(R.string.custom_sync_server_title);
listener.addPreferencesFromResource(R.xml.preferences_custom_sync_server);
screen = listener.getPreferenceScreen();
android.preference.Preference syncUrlPreference = screen.findPreference("syncBaseUrl");
android.preference.Preference mSyncUrlPreference = screen.findPreference("syncMediaUrl");
syncUrlPreference.setOnPreferenceChangeListener((android.preference.Preference preference, Object newValue) -> {
String newUrl = newValue.toString();
if (!URLUtil.isValidUrl(newUrl)) {
new AlertDialog.Builder(this).setTitle(R.string.custom_sync_server_base_url_invalid).setPositiveButton(R.string.dialog_ok, null).show();
return false;
}
return true;
});
mSyncUrlPreference.setOnPreferenceChangeListener((android.preference.Preference preference, Object newValue) -> {
String newUrl = newValue.toString();
if (!URLUtil.isValidUrl(newUrl)) {
new AlertDialog.Builder(this).setTitle(R.string.custom_sync_server_media_url_invalid).setPositiveButton(R.string.dialog_ok, null).show();
return false;
}
return true;
});
break;
case "com.ichi2.anki.prefs.advanced_statistics":
getSupportActionBar().setTitle(R.string.advanced_statistics_title);
listener.addPreferencesFromResource(R.xml.preferences_advanced_statistics);
break;
}
}
Aggregations