Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,8 @@ can be found within the above link.
```
/storage/emulated/0/Android/data/com.kamwithk.ankiconnectandroid/files/
```

* Alternatively locate a different folder. This will require later customisation.

* After locating the two folders, copy `android.db` from the desktop's add-on folder
into Ankiconnect Android's data folder.
Expand All @@ -161,6 +163,14 @@ can be found within the above link.
```
/storage/emulated/0/Android/data/com.kamwithk.ankiconnectandroid/files/android.db
```
* If you chose a different folder than the default, additional customisation is necessary on the settings page.
* Locate the `Local Audio Settings` section
* If you selected a different storage device such as an external SD card, you may need to update the `Choose Local Audio Storage Device` option.
* The first option will always be the internal storage. If you think you have more than one storage device, it may be that android treats it as one, in which case there will only be one option.
* If you selected a different directory such as `Documents`, `Audio` etc, you may need to update the `Choose Local Audio Directory` setting.
* If you selected a user-owned directory like the aforementioned `Documents`, `Audio` and similar, you may also need to allow `Manage All Files` permission for Ankiconnect Android.
* The `Manage All Files` permission is only necessary for folders outside of the app-owned `/Android/data/com.kamwithk.ankiconnectandroid/` directory.
* If you make a mistake you can always reset the settings using `Reset Local Audio settings`. This will reset all the local audio settings and revert it to the default.

4. Setup local audio on Firefox Browser's Yomitan. (Warning: this URL is different than the one on desktop!)
* Click on `Configure audio playback sources` and under the `Audio` section
Expand Down
1 change: 1 addition & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<!--
This permission is required to open another activity from an app in the background
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,32 @@

import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ListView;
import android.widget.Toast;

import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.preference.EditTextPreference;
import androidx.preference.ListPreference;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;

import com.google.android.material.snackbar.Snackbar;

import org.jsoup.internal.StringUtil;

import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;


public class SettingsActivity extends AppCompatActivity {

Expand All @@ -39,10 +53,13 @@ protected void onCreate(Bundle savedInstanceState) {
}

public static class SettingsFragment extends PreferenceFragmentCompat {

private static final String DEFAULT_DIRECTORY_PATH = "/Android/data/com.kamwithk.ankiconnectandroid/files";
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
setPreferencesFromResource(R.xml.root_preferences, rootKey);


Preference preference = findPreference("access_overlay_perms");
if (preference != null) {
// custom handler of preference: open permissions screen
Expand All @@ -54,6 +71,85 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {

}

preference = findPreference("access_manage_all_files_perms");
if (preference != null) {
// custom handler of preference: open permissions screen
preference.setOnPreferenceClickListener(p -> {
Intent permIntent = new Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION);
startActivity(permIntent);
return true;
});

}


EditTextPreference corsHostPreference = findPreference("cors_hostname");
if (corsHostPreference != null) {
corsHostPreference.setOnBindEditTextListener(editText -> editText.setHint("e.g. http://example.com")); }



preference = findPreference("storage_location");
if (preference != null) {
Context context = getContext();
if (context == null) {
Toast.makeText(getContext(), "Cannot get local audio folder, as context is null.", Toast.LENGTH_LONG).show();
} else {
String[] dirs = Arrays.stream(context.getExternalFilesDirs(null)).map(File::getAbsolutePath).map(s -> s.replace(DEFAULT_DIRECTORY_PATH, "")).toArray(String[]::new);
((ListPreference) preference).setEntries(dirs);
((ListPreference) preference).setEntryValues(dirs);

if((StringUtil.isBlank(((ListPreference) preference).getValue()))){
preference.setDefaultValue(dirs[0]); // The first value is equivalent to context.getExternalFilesDir(null)
((ListPreference) preference).setValueIndex(0);
}
}
}
preference = findPreference("storage_dir_path");
if (preference != null) {
Context context = getContext();
if (context == null) {
Toast.makeText(getContext(), "Cannot get local audio folder, as context is null.", Toast.LENGTH_LONG).show();
} else {
preference.setDefaultValue(DEFAULT_DIRECTORY_PATH);
preference.setOnPreferenceChangeListener((p, i) -> {
ListPreference storagePreference = findPreference("storage_location");
Path fullPath = Paths.get(storagePreference.getValue(), i.toString());

if (!Files.exists(fullPath)){
Snackbar.make(context, getView(), "Not a valid directory\n"+fullPath.toString(), Snackbar.LENGTH_LONG)
.show();
return false;
}
return true;
});

}
}

preference = findPreference("reset_storage_settings");
if (preference != null) {
// custom handler of preference: open permissions screen
preference.setOnPreferenceClickListener(p -> {

Context context = getContext();
if (context == null) {
Toast.makeText(getContext(), "Cannot get local audio folder, as context is null.", Toast.LENGTH_LONG).show();
} else {
ListPreference storageDevicePreference = findPreference("storage_location");
EditTextPreference storageDirPreference = findPreference("storage_dir_path");

String[] dirs = Arrays.stream(context.getExternalFilesDirs(null)).map(File::getAbsolutePath).map(s -> s.replace(DEFAULT_DIRECTORY_PATH, "")).toArray(String[]::new);

storageDevicePreference.setValue(dirs[0]);
storageDevicePreference.setValueIndex(0);
storageDirPreference.setText(DEFAULT_DIRECTORY_PATH);

}
return true;
});
}

preference = findPreference("get_dir_path");
if (preference != null) {
// custom handler of preference: open permissions screen
Expand All @@ -63,7 +159,13 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
if (context == null) {
Toast.makeText(getContext(), "Cannot get local audio folder, as context is null.", Toast.LENGTH_LONG).show();
} else {
Toast.makeText(getContext(), "Local audio folder: " + context.getExternalFilesDir(null), Toast.LENGTH_LONG).show();
ListPreference storageDevicePreference = findPreference("storage_location");
EditTextPreference storageDirPreference = findPreference("storage_dir_path");

Path fullPath = Paths.get(storageDevicePreference.getValue(), storageDirPreference.getText());

Toast.makeText(getContext(), "Local audio folder: " + fullPath.toString(), Toast.LENGTH_LONG).show();

// TODO snackbar?
// getView() seems to be null...
// Snackbar snackbar = Snackbar.make(getView().findViewById(R.id.settings),
Expand All @@ -73,12 +175,7 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
}
return true;
});

}

EditTextPreference corsHostPreference = findPreference("cors_hostname");
if (corsHostPreference != null) {
corsHostPreference.setOnBindEditTextListener(editText -> editText.setHint("e.g. http://example.com")); }
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
import static fi.iki.elonen.NanoHTTPD.newFixedLengthResponse;

import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;

import androidx.preference.PreferenceManager;
import androidx.room.Room;
import androidx.sqlite.db.SimpleSQLiteQuery;

Expand All @@ -27,6 +29,9 @@
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Type;
import java.net.URLDecoder;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
Expand Down Expand Up @@ -76,9 +81,21 @@ public LocalAudioAPIRouting(Context context) {

private EntriesDatabase getDB() {
// TODO global instance?
File databasePath = new File(context.getExternalFilesDir(null), "android.db");
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
final File externalFilesDir = context.getExternalFilesDir(null);
final String preferredStorageDevicePath = sharedPreferences.getString("storage_location", "");
final String preferredDirectoryPath = sharedPreferences.getString("storage_dir_path", "");

Path preferredPath = Paths.get(preferredStorageDevicePath, preferredDirectoryPath, "android.db");

// If the preferences point to a file store that no longer is available
// Attempt the default externalFilesDir set by the OS.
if (!Files.isReadable(preferredPath)){
preferredPath = Paths.get(externalFilesDir.getAbsolutePath(), "android.db");
}

EntriesDatabase db = Room.databaseBuilder(context,
EntriesDatabase.class, databasePath.toString()).build();
EntriesDatabase.class, preferredPath.toString()).build();
return db;
}

Expand Down
3 changes: 2 additions & 1 deletion app/src/main/res/layout/settings_activity.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="wrap_content">

<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/settingsToolbar"
Expand All @@ -20,5 +20,6 @@
android:id="@+id/settings"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="100dp"
app:layout_constraintTop_toBottomOf="@+id/settingsToolbar" />
</androidx.constraintlayout.widget.ConstraintLayout>
9 changes: 9 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,21 @@

<string name="action_settings">Settings</string>
<string name="settings_overlay_permissions_title">Access Overlay Permissions</string>
<string name="settings_manage_all_files_permissions_title">Access Manage All Files Permissions</string>
<string name="settings_manage_all_files_permissions_summary">If a custom local audio directory is chosen, this permission may be required.</string>
<string name="settings_permissions_header">Permissions</string>
<string name="settings_overlay_permissions_summary">Allows Ankiconnect Android to open AnkiDroid\'s card browser.</string>
<string name="settings_preferred_storage_header">Local Audio Settings</string>
<string name="settings_other_header">Other</string>
<string name="settings_forvo_language_title">Forvo language</string>
<string name="settings_forvo_language_summary">Sets the language to use for Forvo audio.</string>
<string name="settings_forvo_language_dialog_title">Select Language</string>
<string name="settings_preferred_storage_location_title">Choose Local Audio Storage Device</string>
<string name="settings_preferred_storage_location_summary">Allows you to choose which storage device to use, such as an external SD card. May only have one option if the external storage device is treated as internal or none is found.</string>
<string name="settings_preferred_storage_dir_path_title">Choose Local Audio Directory</string>
<string name="settings_preferred_storage_dir_path_summary">Allows you to choose which directory the local audio file is in. If a user-owned folder like Documents or Audio is chosen, the Manage All Files permission may also need to be granted to ensure read access to the file.</string>
<string name="reset_preferred_storage_settings_title">Reset Local Audio settings</string>
<string name="reset_preferred_storage_settings_summary">Resets the local audio settings back to default.</string>
<string name="get_dir_path_title">Print Local Audio Directory</string>
<string name="get_dir_path_title_summary">Prints the expected directory path where the local audio is searched in.</string>
<string name="dialog_notif_perm_info">This app uses a persistent notification to inform you that the server is running. Please enable notifications to see this.</string>
Expand Down
71 changes: 53 additions & 18 deletions app/src/main/res/xml/root_preferences.xml
Original file line number Diff line number Diff line change
@@ -1,31 +1,71 @@
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
xmlns:app="http://schemas.android.com/apk/res-auto"
app:iconSpaceReserved="false">

<PreferenceCategory app:title="@string/settings_permissions_header">
<PreferenceCategory app:iconSpaceReserved="false" app:title="@string/settings_permissions_header">

<Preference
app:key="access_overlay_perms"
app:title="@string/settings_overlay_permissions_title"
app:summary="@string/settings_overlay_permissions_summary" />
app:iconSpaceReserved="false"
app:key="access_overlay_perms"
app:title="@string/settings_overlay_permissions_title"
app:summary="@string/settings_overlay_permissions_summary" />
<Preference
app:iconSpaceReserved="false"
app:key="access_manage_all_files_perms"
app:title="@string/settings_manage_all_files_permissions_title"
app:summary="@string/settings_manage_all_files_permissions_summary" />

</PreferenceCategory>

<PreferenceCategory
android:title="Access from other sites"
app:summary="Allow access from an external site">
android:title="Access from other sites"
app:summary="Allow access from an external site"
app:iconSpaceReserved="false">

<EditTextPreference
app:dialogMessage="Enter the hostname you want to allow access from."
app:dialogTitle="Enter CORS Host"
app:key="cors_host"
app:title="CORS Host"
app:useSimpleSummaryProvider="true" />
app:iconSpaceReserved="false"
app:dialogMessage="Enter the hostname you want to allow access from."
app:dialogTitle="Enter CORS Host"
app:key="cors_host"
app:title="CORS Host"
app:useSimpleSummaryProvider="true" />

</PreferenceCategory>

<PreferenceCategory app:title="@string/settings_other_header">
<PreferenceCategory
app:iconSpaceReserved="false"
app:title="@string/settings_preferred_storage_header">

<ListPreference
app:iconSpaceReserved="false"
app:key="storage_location"
app:title="@string/settings_preferred_storage_location_title"
app:summary="@string/settings_preferred_storage_location_summary"/>
<EditTextPreference
app:iconSpaceReserved="false"
app:key="storage_dir_path"
app:title="@string/settings_preferred_storage_dir_path_title"
app:summary="@string/settings_preferred_storage_dir_path_summary"
app:defaultValue="/Android/data/com.kamwithk.ankiconnectandroid/files"/>
<Preference
app:iconSpaceReserved="false"
app:key="reset_storage_settings"
app:title="@string/reset_preferred_storage_settings_title"
app:summary="@string/reset_preferred_storage_settings_summary"/>
<Preference
app:iconSpaceReserved="false"
app:key="get_dir_path"
app:title="@string/get_dir_path_title"
app:summary="@string/get_dir_path_title_summary" />

</PreferenceCategory>

<PreferenceCategory
app:iconSpaceReserved="false"
app:title="@string/settings_other_header">

<ListPreference
app:iconSpaceReserved="false"
app:key="forvo_language"
app:title="@string/settings_forvo_language_title"
app:summary="@string/settings_forvo_language_summary"
Expand All @@ -34,10 +74,5 @@
android:entries="@array/forvo_language_entries"
android:entryValues="@array/forvo_language_values" />

<Preference
app:key="get_dir_path"
app:title="@string/get_dir_path_title"
app:summary="@string/get_dir_path_title_summary" />

</PreferenceCategory>
</PreferenceScreen>