summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--android/source/AndroidManifest.xml10
-rw-r--r--android/source/res/drawable/ic_menu_back.pngbin0 -> 1900 bytes
-rw-r--r--android/source/res/layout/activity_directory_browser.xml6
-rw-r--r--android/source/res/layout/fragment_directory_browser.xml71
-rw-r--r--android/source/res/values/strings.xml18
-rw-r--r--android/source/res/xml/documentprovider_preferences.xml13
-rw-r--r--android/source/src/java/org/libreoffice/storage/DocumentProviderFactory.java19
-rw-r--r--android/source/src/java/org/libreoffice/storage/DocumentProviderSettingsActivity.java41
-rw-r--r--android/source/src/java/org/libreoffice/storage/IOUtils.java59
-rw-r--r--android/source/src/java/org/libreoffice/storage/external/BrowserSelectorActivity.java152
-rw-r--r--android/source/src/java/org/libreoffice/storage/external/DirectoryBrowserActivity.java42
-rw-r--r--android/source/src/java/org/libreoffice/storage/external/DirectoryBrowserFragment.java199
-rw-r--r--android/source/src/java/org/libreoffice/storage/external/ExternalFile.java149
-rw-r--r--android/source/src/java/org/libreoffice/storage/external/ExtsdDocumentsProvider.java152
-rw-r--r--android/source/src/java/org/libreoffice/storage/external/IExternalDocumentProvider.java19
-rw-r--r--android/source/src/java/org/libreoffice/storage/external/LegacyExtSDDocumentsProvider.java97
-rw-r--r--android/source/src/java/org/libreoffice/storage/external/OTGDocumentsProvider.java84
17 files changed, 1125 insertions, 6 deletions
diff --git a/android/source/AndroidManifest.xml b/android/source/AndroidManifest.xml
index d49771ab9d3f..25e824074ac9 100644
--- a/android/source/AndroidManifest.xml
+++ b/android/source/AndroidManifest.xml
@@ -113,6 +113,16 @@
</intent-filter>
</activity>
+ <activity android:name=".storage.external.BrowserSelectorActivity"
+ android:theme="@style/LibreOfficeTheme">
+ </activity>
+
+ <activity android:name=".storage.external.DirectoryBrowserActivity"
+ android:label="@string/directory_browser_label"
+ android:theme="@style/LibreOfficeTheme"
+ android:windowSoftInputMode="stateHidden">
+ </activity>
+
</application>
</manifest>
diff --git a/android/source/res/drawable/ic_menu_back.png b/android/source/res/drawable/ic_menu_back.png
new file mode 100644
index 000000000000..d3191caffd13
--- /dev/null
+++ b/android/source/res/drawable/ic_menu_back.png
Binary files differ
diff --git a/android/source/res/layout/activity_directory_browser.xml b/android/source/res/layout/activity_directory_browser.xml
new file mode 100644
index 000000000000..b03c6bbb1224
--- /dev/null
+++ b/android/source/res/layout/activity_directory_browser.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/fragment_container"
+ android:layout_width="match_parent" android:layout_height="match_parent">
+
+</FrameLayout> \ No newline at end of file
diff --git a/android/source/res/layout/fragment_directory_browser.xml b/android/source/res/layout/fragment_directory_browser.xml
new file mode 100644
index 000000000000..fcf7fc6c9b47
--- /dev/null
+++ b/android/source/res/layout/fragment_directory_browser.xml
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical" android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="#FFFFFF">
+
+ <LinearLayout
+ android:id="@+id/browser_header"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <ImageView
+ android:id="@+id/up_image"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:src="@drawable/ic_menu_back"
+ android:scaleType="fitCenter"
+ android:adjustViewBounds="true"
+ android:contentDescription="@string/up_description"/>
+
+ <EditText
+ android:id="@+id/directory_header"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:singleLine="true"
+ android:scrollHorizontally="true"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:inputType="text"/>
+
+ <Button
+ android:id="@+id/directory_search_button"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:text="@string/search_label"/>
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/browser_footer"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:layout_alignParentBottom="true">
+
+ <Button
+ android:id="@+id/cancel_button"
+ android:layout_height="wrap_content"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:text="@string/cancel_label"/>
+
+ <Button
+ android:id="@+id/confirm_button"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="@string/confirm_label"/>
+ </LinearLayout>
+
+ <ListView
+ android:id="@+id/directory_list"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/browser_header"
+ android:layout_above="@id/browser_footer">
+ </ListView>
+
+
+</RelativeLayout> \ No newline at end of file
diff --git a/android/source/res/values/strings.xml b/android/source/res/values/strings.xml
index 403f5b900c3f..c4d1bae404c5 100644
--- a/android/source/res/values/strings.xml
+++ b/android/source/res/values/strings.xml
@@ -51,12 +51,18 @@
<string name="close_document_locations">Close document locations</string>
<string name="local_documents">Local documents</string>
<string name="local_file_system">Local file system</string>
+ <string name="external_sd_file_system">External SD</string>
+ <string name="otg_file_system">OTG device (experimental)</string>
<string name="owncloud">ownCloud</string>
<string name="owncloud_wrong_connection">Cannot connect to ownCloud server. Check your configuration.</string>
<string name="owncloud_unauthorized">Cannot log into ownCloud server. Check your configuration.</string>
<string name="owncloud_unspecified_error">Unspecified error connecting to ownCloud server. Check your configuration and/or try later.</string>
+ <string name="ext_document_provider_error">Invalid root file. Check your configuration.</string>
+ <string name="legacy_extsd_missing_error">Invalid root file. Check your external sd card and/or configuration.</string>
+ <string name="otg_missing_error">Invalid root file. Check your OTG device and/or configuration.</string>
+
<!-- Edit action names -->
<string name="action_bold">Bold</string>
<string name="action_underline">Underline</string>
@@ -75,6 +81,10 @@
<!-- Document provider settings -->
<string name="storage_provider_settings">Storage provider settings</string>
<string name="owncloud_settings">ownCloud settings</string>
+ <string name="physical_storage_settings">Physical storage settings</string>
+ <string name="external_sd_path">External SD path</string>
+ <string name="otg_device_path">OTG device path</string>
+ <string name="otg_warning">Experimental Feature: Use only if OTG device is writable.</string>
<string name="server_url">Server URL</string>
<string name="server_url_and_port">URL and port of the ownCloud server.</string>
<string name="user_name">User name</string>
@@ -82,5 +92,13 @@
<string name="action_undo">Undo</string>
<string name="action_redo">Redo</string>
+ <!-- Directory browser strings -->
+ <string name="up_description">To parent directory</string>
+ <string name="confirm_label">Confirm</string>
+ <string name="cancel_label">Cancel</string>
+ <string name="search_label">Go</string>
+ <string name="directory_browser_label">Choose Directory</string>
+ <string name="bad_directory">Invalid directory path</string>
+
</resources>
diff --git a/android/source/res/xml/documentprovider_preferences.xml b/android/source/res/xml/documentprovider_preferences.xml
index a359d14c4460..4a66c6377847 100644
--- a/android/source/res/xml/documentprovider_preferences.xml
+++ b/android/source/res/xml/documentprovider_preferences.xml
@@ -23,4 +23,17 @@
android:title="@string/password"
android:defaultValue="" />
</PreferenceCategory>
+ <PreferenceCategory
+ android:title="@string/physical_storage_settings">
+ <PreferenceScreen
+ android:title="@string/external_sd_path"
+ android:key="pref_extsd_path_uri">
+ </PreferenceScreen>
+ <PreferenceScreen
+ android:title="@string/otg_device_path"
+ android:key="pref_otg_path_uri"
+ android:summary="@string/otg_warning">
+ </PreferenceScreen>
+ </PreferenceCategory>
+
</PreferenceScreen>
diff --git a/android/source/src/java/org/libreoffice/storage/DocumentProviderFactory.java b/android/source/src/java/org/libreoffice/storage/DocumentProviderFactory.java
index b8c05341d18a..f73a2b0d543a 100644
--- a/android/source/src/java/org/libreoffice/storage/DocumentProviderFactory.java
+++ b/android/source/src/java/org/libreoffice/storage/DocumentProviderFactory.java
@@ -12,12 +12,16 @@ package org.libreoffice.storage;
import java.util.HashSet;
import java.util.Set;
+import org.libreoffice.storage.external.ExtsdDocumentsProvider;
+import org.libreoffice.storage.external.LegacyExtSDDocumentsProvider;
+import org.libreoffice.storage.external.OTGDocumentsProvider;
import org.libreoffice.storage.local.LocalDocumentsDirectoryProvider;
import org.libreoffice.storage.local.LocalDocumentsProvider;
import org.libreoffice.storage.owncloud.OwnCloudProvider;
import android.content.Context;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
+import android.os.Build;
/**
* Keeps the instances of the available IDocumentProviders in the system.
@@ -29,6 +33,8 @@ import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
* DocumentProviderFactory.getInstance().
*/
public final class DocumentProviderFactory {
+ public static int EXTSD_PROVIDER_INDEX = 2;
+ public static int OTG_PROVIDER_INDEX = 3;
/**
* Private factory instance for the Singleton pattern.
@@ -56,10 +62,19 @@ public final class DocumentProviderFactory {
instance = new DocumentProviderFactory();
// initialize document providers list
- instance.providers = new IDocumentProvider[3];
+ instance.providers = new IDocumentProvider[5];
instance.providers[0] = new LocalDocumentsDirectoryProvider(0);
instance.providers[1] = new LocalDocumentsProvider(1);
- instance.providers[2] = new OwnCloudProvider(2, context);
+ instance.providers[OTG_PROVIDER_INDEX] = new OTGDocumentsProvider(OTG_PROVIDER_INDEX, context);
+ instance.providers[4] = new OwnCloudProvider(4, context);
+
+ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ instance.providers[EXTSD_PROVIDER_INDEX]
+ = new ExtsdDocumentsProvider(EXTSD_PROVIDER_INDEX, context);
+ } else {
+ instance.providers[EXTSD_PROVIDER_INDEX]
+ = new LegacyExtSDDocumentsProvider(EXTSD_PROVIDER_INDEX, context);
+ }
// initialize document provider names list
instance.providerNames = new String[instance.providers.length];
diff --git a/android/source/src/java/org/libreoffice/storage/DocumentProviderSettingsActivity.java b/android/source/src/java/org/libreoffice/storage/DocumentProviderSettingsActivity.java
index e98534a44756..7b2160a3f509 100644
--- a/android/source/src/java/org/libreoffice/storage/DocumentProviderSettingsActivity.java
+++ b/android/source/src/java/org/libreoffice/storage/DocumentProviderSettingsActivity.java
@@ -12,25 +12,30 @@ package org.libreoffice.storage;
import java.util.Set;
import org.libreoffice.R;
+import org.libreoffice.storage.external.BrowserSelectorActivity;
import android.app.Activity;
+import android.content.Intent;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.os.Bundle;
+import android.preference.Preference;
import android.preference.PreferenceFragment;
import android.preference.PreferenceManager;
+import android.preference.PreferenceScreen;
public class DocumentProviderSettingsActivity extends Activity {
public static final String KEY_PREF_OWNCLOUD_SERVER = "pref_server_url";
public static final String KEY_PREF_OWNCLOUD_USER_NAME = "pref_user_name";
public static final String KEY_PREF_OWNCLOUD_PASSWORD = "pref_password";
+ public static final String KEY_PREF_EXTERNAL_SD_PATH_URI = "pref_extsd_path_uri";
+ public static final String KEY_PREF_OTG_PATH_URI = "pref_otg_path_uri";
private Set<OnSharedPreferenceChangeListener> listeners;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
-
// Display the fragment as the main content.
getFragmentManager().beginTransaction()
.replace(android.R.id.content, new SettingsFragment()).commit();
@@ -39,7 +44,6 @@ public class DocumentProviderSettingsActivity extends Activity {
@Override
protected void onResume() {
super.onResume();
-
listeners = DocumentProviderFactory.getInstance().getChangeListeners();
for (OnSharedPreferenceChangeListener listener : listeners) {
PreferenceManager.getDefaultSharedPreferences(this)
@@ -50,7 +54,6 @@ public class DocumentProviderSettingsActivity extends Activity {
@Override
protected void onPause() {
super.onPause();
-
for (OnSharedPreferenceChangeListener listener : listeners) {
PreferenceManager.getDefaultSharedPreferences(this)
.unregisterOnSharedPreferenceChangeListener(listener);
@@ -61,9 +64,39 @@ public class DocumentProviderSettingsActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
-
// Load the preferences from an XML resource
addPreferencesFromResource(R.xml.documentprovider_preferences);
+
+ PreferenceScreen extSDPreference =
+ (PreferenceScreen)findPreference(KEY_PREF_EXTERNAL_SD_PATH_URI);
+ PreferenceScreen otgPreference =
+ (PreferenceScreen)findPreference(KEY_PREF_OTG_PATH_URI);
+
+ extSDPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ startBrowserSelectorActivity(KEY_PREF_EXTERNAL_SD_PATH_URI,
+ BrowserSelectorActivity.MODE_EXT_SD);
+ return true;
+ }
+ });
+ otgPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ startBrowserSelectorActivity(KEY_PREF_OTG_PATH_URI,
+ BrowserSelectorActivity.MODE_OTG);
+ return true;
+ }
+ });
+
}
+
+ private void startBrowserSelectorActivity(String prefKey, String mode) {
+ Intent i = new Intent(getActivity(), BrowserSelectorActivity.class);
+ i.putExtra(BrowserSelectorActivity.PREFERENCE_KEY_EXTRA, prefKey);
+ i.putExtra(BrowserSelectorActivity.MODE_EXTRA, mode);
+ startActivity(i);
+ }
+
}
}
diff --git a/android/source/src/java/org/libreoffice/storage/IOUtils.java b/android/source/src/java/org/libreoffice/storage/IOUtils.java
new file mode 100644
index 000000000000..0cb7b2e0ecec
--- /dev/null
+++ b/android/source/src/java/org/libreoffice/storage/IOUtils.java
@@ -0,0 +1,59 @@
+package org.libreoffice.storage;
+
+import android.content.Context;
+import android.util.Log;
+
+import org.libreoffice.R;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+
+/**
+ * File IO related methods.
+ */
+public class IOUtils {
+ private static final int BUFFER_SIZE = 1024 * 8;
+ private static final String LOGTAG = IOUtils.class.getSimpleName();
+
+ public static File getFileFromURIString(String URIpath) throws IllegalArgumentException{
+ try{
+ return new File(new URI(URIpath));
+ } catch (URISyntaxException e) {
+ //should not happen as all URIs are system generated
+ Log.wtf(LOGTAG, e.getReason());
+ return null;
+ }
+ }
+
+ public static boolean isInvalidFile(File f) {
+ return f == null || !f.exists() || f.getTotalSpace() == 0
+ || !f.canRead() || !f.canWrite();
+ }
+
+ public static int copy(InputStream input, OutputStream output) throws Exception {
+ byte[] buffer = new byte[BUFFER_SIZE];
+
+ BufferedInputStream in = new BufferedInputStream(input, BUFFER_SIZE);
+ BufferedOutputStream out = new BufferedOutputStream(output, BUFFER_SIZE);
+
+ int count = 0, n = 0;
+ try {
+ while ((n = in.read(buffer, 0, BUFFER_SIZE)) != -1) {
+ out.write(buffer, 0, n);
+ count += n;
+ }
+ out.flush();
+ } finally {
+ if (out != null) out.close();
+ if (in != null) in.close();
+ }
+
+ return count;
+ }
+
+}
diff --git a/android/source/src/java/org/libreoffice/storage/external/BrowserSelectorActivity.java b/android/source/src/java/org/libreoffice/storage/external/BrowserSelectorActivity.java
new file mode 100644
index 000000000000..fe12804e620c
--- /dev/null
+++ b/android/source/src/java/org/libreoffice/storage/external/BrowserSelectorActivity.java
@@ -0,0 +1,152 @@
+package org.libreoffice.storage.external;
+
+import android.annotation.TargetApi;
+import android.content.ContentResolver;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.UriPermission;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.preference.PreferenceManager;
+import android.support.v7.app.AppCompatActivity;
+import android.util.Log;
+
+import org.libreoffice.storage.DocumentProviderFactory;
+
+import java.util.Set;
+
+/**
+ * Activity to select which directory browser to use.
+ * Android 5+ will use the DocumentTree intent to locate a browser.
+ * Android 4+ & OTG will use the internal directory browser.
+ */
+public class BrowserSelectorActivity extends AppCompatActivity {
+ public static final String PREFERENCE_KEY_EXTRA = "org.libreoffice.pref_key_extra";
+ public static final String MODE_EXTRA = "org.libreoffice.mode_extra";
+ public static final String MODE_OTG = "OTG";
+ public static final String MODE_EXT_SD = "EXT_SD";
+
+ private static final String LOGTAG = BrowserSelectorActivity.class.getSimpleName();
+ private static final int REQUEST_DOCUMENT_TREE = 1;
+ private static final int REQUEST_INTERNAL_BROWSER = 2;
+ private Set<SharedPreferences.OnSharedPreferenceChangeListener> listeners;
+ private String preferenceKey;
+ private SharedPreferences preferences;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ preferenceKey = getIntent().getStringExtra(PREFERENCE_KEY_EXTRA);
+ preferences = PreferenceManager.getDefaultSharedPreferences(this);
+ String mode = getIntent().getStringExtra(MODE_EXTRA);
+
+ if(mode.equals(MODE_EXT_SD)) {
+ findSDCard();
+ } else if (mode.equals(MODE_OTG)) {
+ findOTGDevice();
+ }
+ }
+
+ private void findOTGDevice() {
+ useInternalBrowser(DocumentProviderFactory.OTG_PROVIDER_INDEX);
+ }
+
+ private void findSDCard() {
+ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ useDocumentTreeBrowser();
+ } else {
+ useInternalBrowser(DocumentProviderFactory.EXTSD_PROVIDER_INDEX);
+ }
+ }
+
+ private void useInternalBrowser(int providerIndex) {
+ IExternalDocumentProvider provider =
+ (IExternalDocumentProvider) DocumentProviderFactory.getInstance()
+ .getProvider(providerIndex);
+ String previousDirectoryPath = preferences.getString(preferenceKey, provider.guessRootURI());
+ Intent i = new Intent(this, DirectoryBrowserActivity.class);
+ i.putExtra(DirectoryBrowserActivity.DIRECTORY_PATH_EXTRA, previousDirectoryPath);
+ startActivityForResult(i, REQUEST_INTERNAL_BROWSER);
+ }
+
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ private void useDocumentTreeBrowser() {
+ Intent i = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
+ startActivityForResult(i, REQUEST_DOCUMENT_TREE);
+ }
+
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ //listeners are registered here as onActivityResult is called before onResume
+ super.onActivityResult(requestCode, resultCode, data);
+
+ registerListeners();
+ if(resultCode == RESULT_OK) {
+ switch(requestCode) {
+ case REQUEST_DOCUMENT_TREE:
+ Uri treeUri = data.getData();
+ preferences.edit()
+ .putString(preferenceKey, treeUri.toString())
+ .apply();
+
+ updatePersistedUriPermission(treeUri);
+ getContentResolver().takePersistableUriPermission(treeUri,
+ Intent.FLAG_GRANT_READ_URI_PERMISSION |
+ Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+ break;
+
+ case REQUEST_INTERNAL_BROWSER:
+ Uri fileUri = data.getData();
+ preferences.edit()
+ .putString(preferenceKey, fileUri.toString())
+ .apply();
+ break;
+ default:
+ }
+ }
+ unregisterListeners();
+ Log.d(LOGTAG, "Preference saved: " +
+ preferences.getString(preferenceKey, "Directory not saved."));
+ finish();
+ }
+
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ private void updatePersistedUriPermission(Uri treeUri) {
+ freePreviousUriPermissions();
+
+ //TODO: Use non-emulator Android 5+ device to check if needed
+ /*this.grantUriPermission(this.getPackageName(),
+ treeUri,
+ Intent.FLAG_GRANT_READ_URI_PERMISSION |
+ Intent.FLAG_GRANT_WRITE_URI_PERMISSION); */
+
+ getContentResolver().takePersistableUriPermission(treeUri,
+ Intent.FLAG_GRANT_READ_URI_PERMISSION |
+ Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+ }
+
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ private void freePreviousUriPermissions() {
+ ContentResolver cr = getContentResolver();
+ for (UriPermission uriPermission : cr.getPersistedUriPermissions()) {
+ cr.releasePersistableUriPermission(uriPermission.getUri(), 0);
+ }
+ }
+
+ private void registerListeners() {
+ listeners = DocumentProviderFactory.getInstance().getChangeListeners();
+ for (SharedPreferences.OnSharedPreferenceChangeListener listener : listeners) {
+ PreferenceManager.getDefaultSharedPreferences(this)
+ .registerOnSharedPreferenceChangeListener(listener);
+ }
+ }
+
+ private void unregisterListeners() {
+ for (SharedPreferences.OnSharedPreferenceChangeListener listener : listeners) {
+ PreferenceManager.getDefaultSharedPreferences(this)
+ .unregisterOnSharedPreferenceChangeListener(listener);
+ }
+ }
+}
diff --git a/android/source/src/java/org/libreoffice/storage/external/DirectoryBrowserActivity.java b/android/source/src/java/org/libreoffice/storage/external/DirectoryBrowserActivity.java
new file mode 100644
index 000000000000..224526adb17b
--- /dev/null
+++ b/android/source/src/java/org/libreoffice/storage/external/DirectoryBrowserActivity.java
@@ -0,0 +1,42 @@
+package org.libreoffice.storage.external;
+
+
+import android.app.Fragment;
+import android.app.FragmentManager;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v7.app.AppCompatActivity;
+
+import org.libreoffice.R;
+
+/**
+ * Container for DirectoryBrowserFragment
+ */
+public class DirectoryBrowserActivity extends AppCompatActivity {
+ public static final String DIRECTORY_PATH_EXTRA = "org.libreoffie.directory_path_extra";
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Intent data = getIntent();
+ String initialPath = data.getStringExtra(DIRECTORY_PATH_EXTRA);
+
+ setContentView(R.layout.activity_directory_browser);
+ FragmentManager fm = getFragmentManager();
+ Fragment fragment = DirectoryBrowserFragment.newInstance(initialPath);
+ fm.beginTransaction()
+ .add(R.id.fragment_container, fragment)
+ .commit();
+ }
+
+ @Override
+ public void onBackPressed() {
+ FragmentManager fm = getFragmentManager();
+ if(fm.getBackStackEntryCount() > 0) {
+ fm.popBackStack();
+ } else {
+ super.onBackPressed();
+ }
+ }
+}
diff --git a/android/source/src/java/org/libreoffice/storage/external/DirectoryBrowserFragment.java b/android/source/src/java/org/libreoffice/storage/external/DirectoryBrowserFragment.java
new file mode 100644
index 000000000000..27cbfbce14be
--- /dev/null
+++ b/android/source/src/java/org/libreoffice/storage/external/DirectoryBrowserFragment.java
@@ -0,0 +1,199 @@
+package org.libreoffice.storage.external;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Environment;
+import android.support.annotation.Nullable;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import org.libreoffice.R;
+import org.libreoffice.storage.IOUtils;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Comparator;
+
+/**
+ * A simple directory browser.
+ */
+public class DirectoryBrowserFragment extends Fragment {
+ private static final String LOGTAG = DirectoryBrowserFragment.class.getSimpleName();
+ private static final String INITIAL_PATH_URI_KEY = "initial_path";
+ private File currentDirectory;
+ private FileArrayAdapter directoryAdapter;
+
+ public static DirectoryBrowserFragment newInstance(String initialPathURI) {
+ Bundle args = new Bundle();
+ args.putString(INITIAL_PATH_URI_KEY, initialPathURI);
+ DirectoryBrowserFragment fragment = new DirectoryBrowserFragment();
+ fragment.setArguments(args);
+ Log.d(LOGTAG, "Saved path: " + initialPathURI);
+
+ return fragment;
+ }
+
+ @Override
+ public void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ String initialPathURI = getArguments().getString(INITIAL_PATH_URI_KEY);
+ setupCurrentDirectory(initialPathURI);
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ View v = inflater.inflate(R.layout.fragment_directory_browser, container, false);
+
+ final EditText directoryHeader = (EditText)v.findViewById(R.id.directory_header);
+ Button directorySearchButton = (Button)v.findViewById(R.id.directory_search_button);
+ Button positiveButton = (Button)v.findViewById(R.id.confirm_button);
+ Button negativeButton = (Button)v.findViewById(R.id.cancel_button);
+ ImageView upImage = (ImageView)v.findViewById(R.id.up_image);
+ ListView directoryListView = (ListView) v.findViewById(R.id.directory_list);
+
+ directoryHeader.setText(currentDirectory.getPath());
+ directorySearchButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ String currentPath = currentDirectory.getAbsolutePath();
+ String enteredPath = directoryHeader.getText().toString();
+ File testDirectory = new File(enteredPath);
+ if(enteredPath.equals(currentPath)) ;
+ else if (isInvalidFileDirectory(testDirectory)) {
+ Toast.makeText(getActivity(), R.string.bad_directory, Toast.LENGTH_SHORT)
+ .show();
+ }
+ else {
+ changeDirectory(testDirectory);
+ }
+ }
+ });
+
+ positiveButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Intent data = new Intent();
+ data.setData(Uri.fromFile(currentDirectory));
+ getActivity().setResult(Activity.RESULT_OK, data);
+ getActivity().finish();
+ }
+ });
+
+ negativeButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ getActivity().setResult(Activity.RESULT_CANCELED, null);
+ getActivity().finish();
+ }
+ });
+
+ upImage.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ changeDirectory(currentDirectory.getParentFile());
+ }
+ });
+
+ directoryAdapter = new FileArrayAdapter(getActivity(), new ArrayList<File>());
+ directoryAdapter.populateFileList(currentDirectory);
+ directoryListView.setAdapter(directoryAdapter);
+ directoryListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ changeDirectory(directoryAdapter.getItem(position));
+ }
+ });
+
+ return v;
+ }
+
+ private void changeDirectory(File destination) {
+ if(destination == null) {
+ Toast.makeText(getActivity(), "Unable to go further.", Toast.LENGTH_SHORT)
+ .show();
+ } else {
+ Fragment fragment = DirectoryBrowserFragment.newInstance(destination.toURI().toString());
+ getActivity().getFragmentManager().beginTransaction()
+ .replace(R.id.fragment_container, fragment)
+ .addToBackStack(null)
+ .commit();
+ }
+ }
+
+ private void setupCurrentDirectory(String initialPathURI) {
+ File initialDirectory = null;
+ if(initialPathURI != null && !initialPathURI.isEmpty()) {
+ initialDirectory = IOUtils.getFileFromURIString(initialPathURI);
+ }
+
+ if(isInvalidFileDirectory(initialDirectory)) {
+ initialDirectory = Environment.getExternalStorageDirectory();
+ }
+ currentDirectory = initialDirectory;
+ }
+
+ private boolean isInvalidFileDirectory(File f) {
+ return f == null || !f.exists() || !f.isDirectory() ||!f.canRead();
+ }
+
+ private class FileArrayAdapter extends ArrayAdapter<File> {
+ private Comparator<File> caseInsensitiveNaturalOrderComparator;
+
+ public FileArrayAdapter(Context context, ArrayList<File> files) {
+ super(context, 0, files);
+ caseInsensitiveNaturalOrderComparator = new AlphabeticalFileComparator();
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ if (convertView == null) {
+ convertView = getActivity().getLayoutInflater()
+ .inflate(android.R.layout.simple_list_item_1, null);
+ }
+
+ File f = this.getItem(position);
+ TextView tv = (TextView) convertView.findViewById(android.R.id.text1);
+ tv.setText(f.getName());
+
+ return convertView;
+ }
+
+ public void sortAlphabetically() {
+ this.sort(caseInsensitiveNaturalOrderComparator);
+ }
+
+ public void populateFileList(File directory) {
+ for(File f : directory.listFiles()){
+ if(f.isDirectory()){
+ directoryAdapter.add(f);
+ }
+ }
+ directoryAdapter.sortAlphabetically();
+ }
+ }
+
+ private class AlphabeticalFileComparator implements Comparator<File> {
+ @Override
+ public int compare(File lhs, File rhs) {
+ String lhsName = lhs.getName();
+ String rhsName = rhs.getName();
+
+ return lhsName.compareToIgnoreCase(rhsName);
+ }
+ }
+}
diff --git a/android/source/src/java/org/libreoffice/storage/external/ExternalFile.java b/android/source/src/java/org/libreoffice/storage/external/ExternalFile.java
new file mode 100644
index 000000000000..638111068ddd
--- /dev/null
+++ b/android/source/src/java/org/libreoffice/storage/external/ExternalFile.java
@@ -0,0 +1,149 @@
+package org.libreoffice.storage.external;
+
+import android.content.Context;
+import android.support.v4.provider.DocumentFile;
+import android.util.Log;
+
+import org.libreoffice.storage.IFile;
+import org.libreoffice.storage.IOUtils;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * Implementation of IFile for the external file system, for Android 4.4+
+ *
+ * Uses the DocumentFile class.
+ *
+ * The DocumentFile class obfuscates the path of the files it wraps,
+ * preventing usage of LOK's documentLoad method. A copy of the DocumentFile's contents
+ * will be created in the cache when files are opened, allowing use of documentLoad.
+ */
+public class ExternalFile implements IFile{
+ private final static String LOGTAG = "ExternalFile";
+
+ private ExtsdDocumentsProvider provider;
+ private DocumentFile docFile;
+ private File duplicateFile;
+ private Context context;
+
+ public ExternalFile(ExtsdDocumentsProvider provider, DocumentFile docFile, Context context) {
+ this.provider = provider;
+ this.context = context;
+ this.docFile = docFile;
+ }
+
+ @Override
+ public URI getUri() {
+ try{
+ return new URI(docFile.toString());
+ } catch (URISyntaxException e) {
+ Log.e(LOGTAG, e.getMessage(), e.getCause());
+ return null;
+ }
+ }
+
+ @Override
+ public String getName() {
+ return docFile.getName();
+ }
+
+ @Override
+ public boolean isDirectory() {
+ return docFile.isDirectory();
+ }
+
+ @Override
+ public long getSize() {
+ return docFile.length();
+ }
+
+ @Override
+ public Date getLastModified() {
+ return new Date(docFile.lastModified());
+ }
+
+ @Override
+ public List<IFile> listFiles() {
+ List<IFile> children = new ArrayList<IFile>();
+ for (DocumentFile child : docFile.listFiles()) {
+ children.add(new ExternalFile(provider, child, context));
+ }
+ return children;
+ }
+
+ @Override
+ public List<IFile> listFiles(FileFilter filter) {
+ // TODO: no filtering yet
+ return listFiles();
+ }
+
+ @Override
+ public IFile getParent() {
+ // this is the root node
+ if(docFile.getParentFile() == null) return null;
+
+ return new ExternalFile(provider, docFile.getParentFile(), context);
+ }
+
+ @Override
+ public File getDocument() {
+ if(isDirectory()) {
+ return null;
+ } else {
+ duplicateFile = duplicateInCache();
+ return duplicateFile;
+ }
+ }
+
+ private File duplicateInCache() {
+ try{
+ InputStream istream = context.getContentResolver().
+ openInputStream(docFile.getUri());
+
+ File storageFolder = provider.getCacheDir();
+ File fileCopy = new File(storageFolder, docFile.getName());
+ OutputStream ostream = new FileOutputStream(fileCopy);
+
+ IOUtils.copy(istream, ostream);
+ return fileCopy;
+ } catch (Exception e) {
+ Log.e(LOGTAG, e.getMessage(), e.getCause());
+ return null;
+ }
+ }
+
+ @Override
+ public void saveDocument(File file) {
+ try{
+ OutputStream ostream = context.getContentResolver().
+ openOutputStream(docFile.getUri());
+ InputStream istream = new FileInputStream(file);
+
+ IOUtils.copy(istream, ostream);
+
+ } catch (Exception e) {
+ Log.e(LOGTAG, e.getMessage(), e.getCause());
+ }
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ if (this == object)
+ return true;
+ if (!(object instanceof ExternalFile))
+ return false;
+ ExternalFile file = (ExternalFile) object;
+ return file.getUri().equals(getUri());
+ }
+
+}
diff --git a/android/source/src/java/org/libreoffice/storage/external/ExtsdDocumentsProvider.java b/android/source/src/java/org/libreoffice/storage/external/ExtsdDocumentsProvider.java
new file mode 100644
index 000000000000..09e993bd1eb2
--- /dev/null
+++ b/android/source/src/java/org/libreoffice/storage/external/ExtsdDocumentsProvider.java
@@ -0,0 +1,152 @@
+package org.libreoffice.storage.external;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Environment;
+import android.preference.PreferenceManager;
+import android.support.v4.provider.DocumentFile;
+
+import org.libreoffice.R;
+import org.libreoffice.storage.DocumentProviderSettingsActivity;
+import org.libreoffice.storage.IFile;
+
+import java.io.File;
+import java.net.URI;
+
+/**
+ * Implementation of IDocumentProvider for the external file system, for android 4.4+
+ *
+ * The DocumentFile class is required when accessing files in external storage
+ * for Android 4.4+. The ExternalFile class is used to handle this.
+ *
+ * Android 4.4 & 5+ use different types of root directory paths,
+ * 5 using a DirectoryTree Uri and 4.4 using a normal File path.
+ * As such, different methods are required to obtain the rootDirectory IFile.
+ * 4.4 has to guess the location of the rootDirectory as well.
+ */
+public class ExtsdDocumentsProvider implements IExternalDocumentProvider,
+ OnSharedPreferenceChangeListener{
+ private static final String LOGTAG = ExtsdDocumentsProvider.class.getSimpleName();
+
+ private int id;
+ private File cacheDir;
+ private Context context;
+ private String rootPathURI;
+
+ public ExtsdDocumentsProvider(int id, Context context) {
+ this.id = id;
+ this.context = context;
+ setupRootPathUri();
+ setupCache();
+ }
+
+ private void setupRootPathUri() {
+ SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
+ String rootURIGuess = guessRootURI();
+ rootPathURI = preferences.getString(
+ DocumentProviderSettingsActivity.KEY_PREF_EXTERNAL_SD_PATH_URI, rootURIGuess);
+ }
+
+ //Android 4.4 specific
+ @TargetApi(Build.VERSION_CODES.KITKAT)
+ public String guessRootURI() {
+ File[] options = context.getExternalFilesDirs(null);
+ File internalSD = Environment.getExternalStorageDirectory();
+ String internalSDPath = internalSD.getAbsolutePath();
+
+ for (File option: options) {
+ String optionPath = option.getAbsolutePath();
+ if(!optionPath.contains(internalSDPath))
+ return option.toURI().toString();
+ }
+
+ return "";
+ }
+
+ private void setupCache() {
+ // TODO: probably we should do smarter cache management
+ cacheDir = new File(context.getExternalCacheDir(), "externalFiles");
+ if (cacheDir.exists()) {
+ deleteRecursive(cacheDir);
+ }
+ cacheDir.mkdirs();
+ }
+
+ private static void deleteRecursive(File file) {
+ if (file.isDirectory()) {
+ for (File child : file.listFiles())
+ deleteRecursive(child);
+ }
+ file.delete();
+ }
+
+ public File getCacheDir() {
+ return cacheDir;
+ }
+
+ @Override
+ public IFile getRootDirectory() {
+ if(android.os.Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
+ return android4RootDirectory();
+ } else {
+ return android5RootDirectory();
+ }
+ }
+
+ private ExternalFile android4RootDirectory() {
+ try{
+ File f = new File(new URI(rootPathURI));
+ return new ExternalFile(this, DocumentFile.fromFile(f), context);
+ } catch (Exception e) {
+ //invalid rootPathURI
+ throw buildRuntimeExceptionForInvalidFileURI();
+ }
+ }
+
+ private ExternalFile android5RootDirectory() {
+ try {
+ return new ExternalFile(this,
+ DocumentFile.fromTreeUri(context, Uri.parse(rootPathURI)),
+ context);
+ } catch (Exception e) {
+ //invalid rootPathURI
+ throw buildRuntimeExceptionForInvalidFileURI();
+ }
+ }
+
+ private RuntimeException buildRuntimeExceptionForInvalidFileURI() {
+ return new RuntimeException(context.getString(R.string.ext_document_provider_error));
+ }
+
+ @Override
+ public IFile createFromUri(URI javaURI) {
+ //TODO: refactor when new DocumentFile API exist
+ //uri must be of a DocumentFile file, not directory.
+ Uri androidUri = Uri.parse(javaURI.toString());
+ return new ExternalFile(this,
+ DocumentFile.fromSingleUri(context, androidUri),
+ context);
+ }
+
+ @Override
+ public int getNameResource() {
+ return R.string.external_sd_file_system;
+ }
+
+ @Override
+ public int getId() {
+ return id;
+ }
+
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
+ if (key.equals(DocumentProviderSettingsActivity.KEY_PREF_EXTERNAL_SD_PATH_URI)) {
+ rootPathURI = preferences.getString(key, "");
+ }
+ }
+
+}
diff --git a/android/source/src/java/org/libreoffice/storage/external/IExternalDocumentProvider.java b/android/source/src/java/org/libreoffice/storage/external/IExternalDocumentProvider.java
new file mode 100644
index 000000000000..34bbcbc4bad6
--- /dev/null
+++ b/android/source/src/java/org/libreoffice/storage/external/IExternalDocumentProvider.java
@@ -0,0 +1,19 @@
+package org.libreoffice.storage.external;
+
+import org.libreoffice.storage.IDocumentProvider;
+
+
+/**
+ * Interface for external document providers.
+ */
+public interface IExternalDocumentProvider extends IDocumentProvider {
+
+ /**
+ * Used to obtain the default directory to display when
+ * browsing using the internal DirectoryBrowser.
+ *
+ * @return a guess of the root file's URI.
+ */
+ String guessRootURI();
+
+}
diff --git a/android/source/src/java/org/libreoffice/storage/external/LegacyExtSDDocumentsProvider.java b/android/source/src/java/org/libreoffice/storage/external/LegacyExtSDDocumentsProvider.java
new file mode 100644
index 000000000000..ae5ddde40141
--- /dev/null
+++ b/android/source/src/java/org/libreoffice/storage/external/LegacyExtSDDocumentsProvider.java
@@ -0,0 +1,97 @@
+package org.libreoffice.storage.external;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.preference.PreferenceManager;
+import android.text.TextUtils;
+import android.util.Log;
+
+import org.libreoffice.R;
+import org.libreoffice.storage.DocumentProviderSettingsActivity;
+import org.libreoffice.storage.IFile;
+import org.libreoffice.storage.IOUtils;
+import org.libreoffice.storage.local.LocalFile;
+
+import java.io.File;
+import java.net.URI;
+
+/**
+ * Legacy document provider for External SD cards, for Android 4.3 and below.
+ *
+ * Uses the the LocalFile class.
+ */
+public class LegacyExtSDDocumentsProvider implements IExternalDocumentProvider,
+ SharedPreferences.OnSharedPreferenceChangeListener{
+ private static final String LOGTAG = LegacyExtSDDocumentsProvider.class.getSimpleName();
+
+ private int id;
+ private Context context;
+ private String rootPathURI;
+
+ public LegacyExtSDDocumentsProvider(int id, Context context) {
+ this.id = id;
+ this.context = context;
+ setupRootPathUri();
+ }
+
+ private void setupRootPathUri() {
+ SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
+ String rootURIGuess = guessRootURI();
+
+ rootPathURI = preferences.getString(
+ DocumentProviderSettingsActivity.KEY_PREF_EXTERNAL_SD_PATH_URI, rootURIGuess);
+ }
+
+ public String guessRootURI() {
+ //hacky method of obtaining extsdcard root
+ final String value = System.getenv("SECONDARY_STORAGE");
+ Log.d(LOGTAG, "guesses: " + value);
+ if (!TextUtils.isEmpty(value)) {
+ final String[] paths = value.split(":");
+ for (String path : paths) {
+ File file = new File(path);
+ if(path.contains("ext") && file.isDirectory()) {
+ return file.toURI().toString();
+ }
+ }
+ }
+ return "";
+ }
+
+ @Override
+ public IFile getRootDirectory() {
+ if(rootPathURI.equals("")) {
+ throw new RuntimeException(context.getString(R.string.ext_document_provider_error));
+ }
+
+ File f = IOUtils.getFileFromURIString(rootPathURI);
+ if(IOUtils.isInvalidFile(f)) {
+ //missing device
+ throw new RuntimeException(context.getString(R.string.legacy_extsd_missing_error));
+ }
+ return new LocalFile(f);
+ }
+
+ @Override
+ public IFile createFromUri(URI uri) {
+ return new LocalFile(uri);
+ }
+
+ @Override
+ public int getNameResource() {
+ return R.string.external_sd_file_system;
+ }
+
+ @Override
+ public int getId() {
+ return id;
+ }
+
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
+ if (key.equals(DocumentProviderSettingsActivity.KEY_PREF_EXTERNAL_SD_PATH_URI)) {
+ rootPathURI = preferences.getString(key, "");
+ }
+ }
+
+}
diff --git a/android/source/src/java/org/libreoffice/storage/external/OTGDocumentsProvider.java b/android/source/src/java/org/libreoffice/storage/external/OTGDocumentsProvider.java
new file mode 100644
index 000000000000..37e9be7d1a32
--- /dev/null
+++ b/android/source/src/java/org/libreoffice/storage/external/OTGDocumentsProvider.java
@@ -0,0 +1,84 @@
+package org.libreoffice.storage.external;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.preference.PreferenceManager;
+import android.util.Log;
+
+import org.libreoffice.R;
+import org.libreoffice.storage.DocumentProviderSettingsActivity;
+import org.libreoffice.storage.IFile;
+import org.libreoffice.storage.IOUtils;
+import org.libreoffice.storage.local.LocalFile;
+
+import java.io.File;
+import java.net.URI;
+
+/**
+ * TODO: OTG currently uses LocalFile. Change to an IFile that handles abrupt OTG unmounting
+ */
+public class OTGDocumentsProvider implements IExternalDocumentProvider,
+ SharedPreferences.OnSharedPreferenceChangeListener {
+
+ private static final String LOGTAG = OTGDocumentsProvider.class.getSimpleName();
+
+ private Context context;
+ private String rootPathURI;
+ private int id;
+
+ public OTGDocumentsProvider(int id, Context context) {
+ this.context = context;
+ this.id = id;
+ setupRootPath();
+ }
+
+ private void setupRootPath() {
+ SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
+ rootPathURI = preferences.getString(
+ DocumentProviderSettingsActivity.KEY_PREF_OTG_PATH_URI, "");
+ }
+
+ @Override
+ public IFile createFromUri(URI uri) {
+ return new LocalFile(uri);
+ }
+
+ @Override
+ public int getNameResource() {
+ return R.string.otg_file_system;
+ }
+
+ @Override
+ public int getId() {
+ return id;
+ }
+
+ @Override
+ public IFile getRootDirectory() {
+
+ if(rootPathURI.equals("")) {
+ throw new RuntimeException(context.getString(R.string.ext_document_provider_error));
+ }
+
+ File f = IOUtils.getFileFromURIString(rootPathURI);
+ if(IOUtils.isInvalidFile(f)) {
+ //missing device
+ throw new RuntimeException(context.getString(R.string.otg_missing_error, context));
+ }
+
+ return new LocalFile(f);
+ }
+
+
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+ if (key.equals(DocumentProviderSettingsActivity.KEY_PREF_OTG_PATH_URI)) {
+ rootPathURI = sharedPreferences.getString(key, "");
+ }
+ }
+
+ @Override
+ public String guessRootURI() {
+ return "";
+ }
+}