Kaydet (Commit) 494297af authored tarafından Christian Lohmaier's avatar Christian Lohmaier

pass context as parameter instead of risk of leaking memory

Also adjust to dynamic permissions after bumping target-SDK.

There still is some confusion about the concept of "external storage" in
the code. LocalDocuments already is "external storage" - clean that up a
little and use AppCompat function instead of using a legacy class for
ExternalDocuments provider.
Doesn't help for broken ROMs though, that would need guessing pathname
for a mounted SD (in addition to separate storage partition of builtin
storage).

Also c6e8c96d incorrectly changed the
conditional around, making the whole ExternalDocumentsProvider useless/a
copy of the Local one (i.e. the primary, first returned by the system).
Real fix for tdf#99539 likely was 66be4fee

Change-Id: I88ca7742c0f2e89d63c338c8852ad88be0a46e4b
Reviewed-on: https://gerrit.libreoffice.org/45572Tested-by: 's avatarJenkins <ci@libreoffice.org>
Reviewed-by: 's avatarChristian Lohmaier <lohmaier+LibreOffice@googlemail.com>
üst 63860c39
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
<item android:id="@+id/menu_provider_documents" <item android:id="@+id/menu_provider_documents"
android:title="@string/local_documents" android:title="@string/local_documents"
android:icon="@drawable/ic_insert_drive_file_black_24dp" /> android:icon="@drawable/ic_folder_black_24dp" />
<item android:id="@+id/menu_provider_filesystem" <item android:id="@+id/menu_provider_filesystem"
android:title="@string/local_file_system" android:title="@string/local_file_system"
......
...@@ -69,7 +69,7 @@ ...@@ -69,7 +69,7 @@
<!-- Document provider names --> <!-- Document provider names -->
<string name="document_locations">Document locations</string> <string name="document_locations">Document locations</string>
<string name="close_document_locations">Close document locations</string> <string name="close_document_locations">Close document locations</string>
<string name="local_documents">Local documents</string> <string name="local_documents">Documents directory</string>
<string name="local_file_system">Local file system</string> <string name="local_file_system">Local file system</string>
<string name="external_sd_file_system">External SD</string> <string name="external_sd_file_system">External SD</string>
<string name="otg_file_system">OTG device (experimental)</string> <string name="otg_file_system">OTG device (experimental)</string>
......
...@@ -323,7 +323,7 @@ public class LibreOfficeMainActivity extends AppCompatActivity implements Settin ...@@ -323,7 +323,7 @@ public class LibreOfficeMainActivity extends AppCompatActivity implements Settin
try { try {
// rebuild the IFile object from the data passed in the Intent // rebuild the IFile object from the data passed in the Intent
IFile mStorageFile = DocumentProviderFactory.getInstance() IFile mStorageFile = DocumentProviderFactory.getInstance()
.getProvider(providerId).createFromUri(documentUri); .getProvider(providerId).createFromUri(LibreOfficeMainActivity.this, documentUri);
// call document provider save operation // call document provider save operation
mStorageFile.saveDocument(mInputFile); mStorageFile.saveDocument(mInputFile);
} }
......
...@@ -13,7 +13,6 @@ import java.util.HashSet; ...@@ -13,7 +13,6 @@ import java.util.HashSet;
import java.util.Set; import java.util.Set;
import org.libreoffice.storage.external.ExtsdDocumentsProvider; import org.libreoffice.storage.external.ExtsdDocumentsProvider;
import org.libreoffice.storage.external.LegacyExtSDDocumentsProvider;
import org.libreoffice.storage.external.OTGDocumentsProvider; import org.libreoffice.storage.external.OTGDocumentsProvider;
import org.libreoffice.storage.local.LocalDocumentsDirectoryProvider; import org.libreoffice.storage.local.LocalDocumentsDirectoryProvider;
import org.libreoffice.storage.local.LocalDocumentsProvider; import org.libreoffice.storage.local.LocalDocumentsProvider;
...@@ -21,7 +20,6 @@ import org.libreoffice.storage.owncloud.OwnCloudProvider; ...@@ -21,7 +20,6 @@ import org.libreoffice.storage.owncloud.OwnCloudProvider;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener; import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.os.Build;
/** /**
* Keeps the instances of the available IDocumentProviders in the system. * Keeps the instances of the available IDocumentProviders in the system.
...@@ -68,13 +66,7 @@ public final class DocumentProviderFactory { ...@@ -68,13 +66,7 @@ public final class DocumentProviderFactory {
instance.providers[OTG_PROVIDER_INDEX] = new OTGDocumentsProvider(OTG_PROVIDER_INDEX, context); instance.providers[OTG_PROVIDER_INDEX] = new OTGDocumentsProvider(OTG_PROVIDER_INDEX, context);
instance.providers[4] = new OwnCloudProvider(4, 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);
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 // initialize document provider names list
instance.providerNames = new String[instance.providers.length]; instance.providerNames = new String[instance.providers.length];
...@@ -121,7 +113,7 @@ public final class DocumentProviderFactory { ...@@ -121,7 +113,7 @@ public final class DocumentProviderFactory {
* @return default provider. * @return default provider.
*/ */
public IDocumentProvider getDefaultProvider() { public IDocumentProvider getDefaultProvider() {
return providers[1]; return providers[0];
} }
public Set<OnSharedPreferenceChangeListener> getChangeListeners() { public Set<OnSharedPreferenceChangeListener> getChangeListeners() {
......
...@@ -9,6 +9,8 @@ ...@@ -9,6 +9,8 @@
package org.libreoffice.storage; package org.libreoffice.storage;
import android.content.Context;
import java.net.URI; import java.net.URI;
/** /**
...@@ -22,19 +24,22 @@ public interface IDocumentProvider { ...@@ -22,19 +24,22 @@ public interface IDocumentProvider {
* *
* @return Content root element. * @return Content root element.
* @throws RuntimeException in case of error. * @throws RuntimeException in case of error.
* @param context
*/ */
IFile getRootDirectory(); IFile getRootDirectory(Context context);
/** /**
* Transforms some URI into the IFile object that represents that content. * Transforms some URI into the IFile object that represents that content.
* *
*
* @param context
* @param uri * @param uri
* URI pointing to some content object that has been previously * URI pointing to some content object that has been previously
* retrieved with IFile.getUri(). * retrieved with IFile.getUri().
* @return IFile object pointing to the content represented by uri. * @return IFile object pointing to the content represented by uri.
* @throws RuntimeException in case of error. * @throws RuntimeException in case of error.
*/ */
IFile createFromUri(URI uri); IFile createFromUri(Context context, URI uri);
/** /**
* Get internationalized name for this provider. This name is intended to be * Get internationalized name for this provider. This name is intended to be
...@@ -59,6 +64,7 @@ public interface IDocumentProvider { ...@@ -59,6 +64,7 @@ public interface IDocumentProvider {
* Checks if the Document Provider is available or not. * Checks if the Document Provider is available or not.
* *
* @return A boolean value based on provider availability. * @return A boolean value based on provider availability.
* @param context
*/ */
boolean checkProviderAvailability(); boolean checkProviderAvailability(Context context);
} }
...@@ -9,6 +9,8 @@ ...@@ -9,6 +9,8 @@
package org.libreoffice.storage; package org.libreoffice.storage;
import android.content.Context;
import java.io.File; import java.io.File;
import java.io.FileFilter; import java.io.FileFilter;
import java.net.URI; import java.net.URI;
...@@ -91,8 +93,9 @@ public interface IFile { ...@@ -91,8 +93,9 @@ public interface IFile {
* Returns the pparent of this file. * Returns the pparent of this file.
* *
* @return this file's parent or null if it does not have it. * @return this file's parent or null if it does not have it.
* @param context
*/ */
IFile getParent(); IFile getParent(Context context);
/** /**
* Returns the document wrapped by this IFile as a local file. The result * Returns the document wrapped by this IFile as a local file. The result
......
...@@ -64,7 +64,7 @@ public class BrowserSelectorActivity extends AppCompatActivity { ...@@ -64,7 +64,7 @@ public class BrowserSelectorActivity extends AppCompatActivity {
IExternalDocumentProvider provider = IExternalDocumentProvider provider =
(IExternalDocumentProvider) DocumentProviderFactory.getInstance() (IExternalDocumentProvider) DocumentProviderFactory.getInstance()
.getProvider(providerIndex); .getProvider(providerIndex);
String previousDirectoryPath = preferences.getString(preferenceKey, provider.guessRootURI()); String previousDirectoryPath = preferences.getString(preferenceKey, provider.guessRootURI(this));
Intent i = new Intent(this, DirectoryBrowserActivity.class); Intent i = new Intent(this, DirectoryBrowserActivity.class);
i.putExtra(DirectoryBrowserActivity.DIRECTORY_PATH_EXTRA, previousDirectoryPath); i.putExtra(DirectoryBrowserActivity.DIRECTORY_PATH_EXTRA, previousDirectoryPath);
startActivityForResult(i, REQUEST_INTERNAL_BROWSER); startActivityForResult(i, REQUEST_INTERNAL_BROWSER);
......
...@@ -102,11 +102,11 @@ public class ExternalFile implements IFile{ ...@@ -102,11 +102,11 @@ public class ExternalFile implements IFile{
} }
@Override @Override
public IFile getParent() { public IFile getParent(Context context) {
// this is the root node // this is the root node
if(docFile.getParentFile() == null) return null; if(docFile.getParentFile() == null) return null;
return new ExternalFile(provider, docFile.getParentFile(), context); return new ExternalFile(provider, docFile.getParentFile(), this.context);
} }
@Override @Override
......
package org.libreoffice.storage.external; package org.libreoffice.storage.external;
import android.annotation.TargetApi;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener; import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
...@@ -8,7 +7,9 @@ import android.net.Uri; ...@@ -8,7 +7,9 @@ import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Environment; import android.os.Environment;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.support.v4.content.ContextCompat;
import android.support.v4.provider.DocumentFile; import android.support.v4.provider.DocumentFile;
import android.util.Log;
import org.libreoffice.R; import org.libreoffice.R;
import org.libreoffice.storage.DocumentProviderSettingsActivity; import org.libreoffice.storage.DocumentProviderSettingsActivity;
...@@ -34,45 +35,49 @@ public class ExtsdDocumentsProvider implements IExternalDocumentProvider, ...@@ -34,45 +35,49 @@ public class ExtsdDocumentsProvider implements IExternalDocumentProvider,
private int id; private int id;
private File cacheDir; private File cacheDir;
private Context context;
private String rootPathURI; private String rootPathURI;
public ExtsdDocumentsProvider(int id, Context context) { public ExtsdDocumentsProvider(int id, Context context) {
this.id = id; this.id = id;
this.context = context; setupRootPathUri(context);
setupRootPathUri(); setupCache(context);
setupCache();
} }
private void setupRootPathUri() { private void setupRootPathUri(Context context) {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
String rootURIGuess = guessRootURI();
rootPathURI = preferences.getString( rootPathURI = preferences.getString(
DocumentProviderSettingsActivity.KEY_PREF_EXTERNAL_SD_PATH_URI, rootURIGuess); DocumentProviderSettingsActivity.KEY_PREF_EXTERNAL_SD_PATH_URI, guessRootURI(context));
} }
//Android 4.4 specific public String guessRootURI(Context context) {
@TargetApi(Build.VERSION_CODES.KITKAT) // TODO: unfortunately the getExternalFilesDirs function relies on devices to actually
public String guessRootURI() { // follow guidelines re external storage. Of course device manufacturers don't and as such
File[] options = context.getExternalFilesDirs(null); // you cannot rely on it returning the actual paths (neither the compat, nor the native variant)
File internalSD = Environment.getExternalStorageDirectory(); File[] possibleRemovables = ContextCompat.getExternalFilesDirs(context,null);
String internalSDPath = internalSD.getAbsolutePath(); // the primary dir that is already covered by the "LocalDocumentsProvider"
// might be emulated/part of internal memory or actual SD card
for (File option: options) { // TODO: change to not confuse android's "external storage" with "expandable storage"
String primaryExternal = Environment.getExternalStorageDirectory().getAbsolutePath();
for (File option: possibleRemovables) {
// Returned paths may be null if a storage device is unavailable. // Returned paths may be null if a storage device is unavailable.
if (null == option) { continue; } if (null == option) {
Log.w(LOGTAG,"path was a null option :-/"); continue; }
String optionPath = option.getAbsolutePath(); String optionPath = option.getAbsolutePath();
if(optionPath.contains(primaryExternal)) {
Log.v(LOGTAG, "did get file path - but is same as primary storage ("+ primaryExternal +")");
continue;
}
if(optionPath.contains(internalSDPath)) return option.toURI().toString();
return option.toURI().toString();
} }
return ""; // TODO: do some manual probing of possible directories (/storage/sdcard1 and similar)
Log.i(LOGTAG, "no secondary storage reported");
return null;
} }
private void setupCache() { private void setupCache(Context context) {
// TODO: probably we should do smarter cache management // TODO: probably we should do smarter cache management
cacheDir = new File(context.getExternalCacheDir(), "externalFiles"); cacheDir = new File(context.getExternalCacheDir(), "externalFiles");
if (cacheDir.exists()) { if (cacheDir.exists()) {
...@@ -94,41 +99,42 @@ public class ExtsdDocumentsProvider implements IExternalDocumentProvider, ...@@ -94,41 +99,42 @@ public class ExtsdDocumentsProvider implements IExternalDocumentProvider,
} }
@Override @Override
public IFile getRootDirectory() { public IFile getRootDirectory(Context context) {
if(android.os.Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) { if(android.os.Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
return android4RootDirectory(); return android4RootDirectory(context);
} else { } else {
return android5RootDirectory(); return android5RootDirectory(context);
} }
} }
private ExternalFile android4RootDirectory() { private ExternalFile android4RootDirectory(Context context) {
try{ try{
File f = new File(new URI(rootPathURI)); File f = new File(new URI(rootPathURI));
return new ExternalFile(this, DocumentFile.fromFile(f), context); return new ExternalFile(this, DocumentFile.fromFile(f), context);
} catch (Exception e) { } catch (Exception e) {
//invalid rootPathURI //invalid rootPathURI
throw buildRuntimeExceptionForInvalidFileURI(); throw buildRuntimeExceptionForInvalidFileURI(context);
} }
} }
private ExternalFile android5RootDirectory() { private ExternalFile android5RootDirectory(Context context) {
try { try {
return new ExternalFile(this, return new ExternalFile(this,
DocumentFile.fromTreeUri(context, Uri.parse(rootPathURI)), DocumentFile.fromTreeUri(context, Uri.parse(rootPathURI)),
context); context);
} catch (Exception e) { } catch (Exception e) {
//invalid rootPathURI //invalid rootPathURI
throw buildRuntimeExceptionForInvalidFileURI(); throw buildRuntimeExceptionForInvalidFileURI(context);
} }
} }
private RuntimeException buildRuntimeExceptionForInvalidFileURI() { private RuntimeException buildRuntimeExceptionForInvalidFileURI(Context context) {
// ToDo: discarding the original excpeption / catch-all handling is bad style
return new RuntimeException(context.getString(R.string.ext_document_provider_error)); return new RuntimeException(context.getString(R.string.ext_document_provider_error));
} }
@Override @Override
public IFile createFromUri(URI javaURI) { public IFile createFromUri(Context context, URI javaURI) {
//TODO: refactor when new DocumentFile API exist //TODO: refactor when new DocumentFile API exist
//uri must be of a DocumentFile file, not directory. //uri must be of a DocumentFile file, not directory.
Uri androidUri = Uri.parse(javaURI.toString()); Uri androidUri = Uri.parse(javaURI.toString());
...@@ -148,8 +154,13 @@ public class ExtsdDocumentsProvider implements IExternalDocumentProvider, ...@@ -148,8 +154,13 @@ public class ExtsdDocumentsProvider implements IExternalDocumentProvider,
} }
@Override @Override
public boolean checkProviderAvailability() { public boolean checkProviderAvailability(Context context) {
return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED) && Environment.isExternalStorageRemovable(); // too many devices (or I am just unlucky) don't report the mounted state properly, and other
// devices also consider dedicated part of internal storage to be "mounted" so cannot use
// getExternalStorageState().equals(Environment.MEDIA_MOUNTED) && isExternalStorageRemovable()
// but they refer to the primary external storage anyway, so what currently is covered by the
// "LocalDocumentsProvider"
return rootPathURI!=null;
} }
@Override @Override
......
package org.libreoffice.storage.external; package org.libreoffice.storage.external;
import android.content.Context;
import org.libreoffice.storage.IDocumentProvider; import org.libreoffice.storage.IDocumentProvider;
...@@ -13,7 +15,8 @@ public interface IExternalDocumentProvider extends IDocumentProvider { ...@@ -13,7 +15,8 @@ public interface IExternalDocumentProvider extends IDocumentProvider {
* browsing using the internal DirectoryBrowser. * browsing using the internal DirectoryBrowser.
* *
* @return a guess of the root file's URI. * @return a guess of the root file's URI.
* @param context
*/ */
String guessRootURI(); String guessRootURI(Context context);
} }
package org.libreoffice.storage.external;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Environment;
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 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 boolean checkProviderAvailability() {
return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED) && Environment.isExternalStorageRemovable();
}
@Override
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
if (key.equals(DocumentProviderSettingsActivity.KEY_PREF_EXTERNAL_SD_PATH_URI)) {
rootPathURI = preferences.getString(key, "");
}
}
}
...@@ -4,6 +4,7 @@ import android.content.Context; ...@@ -4,6 +4,7 @@ import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.util.Log;
import org.libreoffice.R; import org.libreoffice.R;
import org.libreoffice.storage.DocumentProviderSettingsActivity; import org.libreoffice.storage.DocumentProviderSettingsActivity;
...@@ -22,24 +23,22 @@ public class OTGDocumentsProvider implements IExternalDocumentProvider, ...@@ -22,24 +23,22 @@ public class OTGDocumentsProvider implements IExternalDocumentProvider,
private static final String LOGTAG = OTGDocumentsProvider.class.getSimpleName(); private static final String LOGTAG = OTGDocumentsProvider.class.getSimpleName();
private Context context;
private String rootPathURI; private String rootPathURI;
private int id; private int id;
public OTGDocumentsProvider(int id, Context context) { public OTGDocumentsProvider(int id, Context context) {
this.context = context;
this.id = id; this.id = id;
setupRootPath(); setupRootPath(context);
} }
private void setupRootPath() { private void setupRootPath(Context context) {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
rootPathURI = preferences.getString( rootPathURI = preferences.getString(
DocumentProviderSettingsActivity.KEY_PREF_OTG_PATH_URI, ""); DocumentProviderSettingsActivity.KEY_PREF_OTG_PATH_URI, "");
} }
@Override @Override
public IFile createFromUri(URI uri) { public IFile createFromUri(Context context, URI uri) {
return new LocalFile(uri); return new LocalFile(uri);
} }
...@@ -54,15 +53,16 @@ public class OTGDocumentsProvider implements IExternalDocumentProvider, ...@@ -54,15 +53,16 @@ public class OTGDocumentsProvider implements IExternalDocumentProvider,
} }
@Override @Override
public IFile getRootDirectory() { public IFile getRootDirectory(Context context) {
// TODO: handle this with more fine-grained exceptions
if(rootPathURI.equals("")) { if(rootPathURI.equals("")) {
Log.e(LOGTAG, "rootPathURI is empty");
throw new RuntimeException(context.getString(R.string.ext_document_provider_error)); throw new RuntimeException(context.getString(R.string.ext_document_provider_error));
} }
File f = IOUtils.getFileFromURIString(rootPathURI); File f = IOUtils.getFileFromURIString(rootPathURI);
if(IOUtils.isInvalidFile(f)) { if(IOUtils.isInvalidFile(f)) {
//missing device Log.e(LOGTAG, "rootPathURI is invalid - missing device?");
throw new RuntimeException(context.getString(R.string.otg_missing_error)); throw new RuntimeException(context.getString(R.string.otg_missing_error));
} }
...@@ -78,12 +78,12 @@ public class OTGDocumentsProvider implements IExternalDocumentProvider, ...@@ -78,12 +78,12 @@ public class OTGDocumentsProvider implements IExternalDocumentProvider,
} }
@Override @Override
public String guessRootURI() { public String guessRootURI(Context context) {
return ""; return "";
} }
@Override @Override
public boolean checkProviderAvailability() { public boolean checkProviderAvailability(Context context) {
// check if system supports USB Host // check if system supports USB Host
return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_USB_HOST); return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_USB_HOST);
} }
......
...@@ -14,7 +14,13 @@ import java.io.File; ...@@ -14,7 +14,13 @@ import java.io.File;
import org.libreoffice.storage.IFile; import org.libreoffice.storage.IFile;
import org.libreoffice.R; import org.libreoffice.R;
import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Environment; import android.os.Environment;
import android.support.v4.content.ContextCompat;
import android.util.Log;
/** /**
* A convenience IDocumentProvider to browse the /sdcard/Documents directory. * A convenience IDocumentProvider to browse the /sdcard/Documents directory.
...@@ -29,11 +35,31 @@ public class LocalDocumentsDirectoryProvider extends LocalDocumentsProvider { ...@@ -29,11 +35,31 @@ public class LocalDocumentsDirectoryProvider extends LocalDocumentsProvider {
super(id); super(id);
} }
private static File getDocumentsDir() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
// DIRECTORY_DOCUMENTS is 19 or later only
return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS);
} else {
return new File(Environment.getExternalStorageDirectory() + "/Documents");
}
}
@Override @Override
public IFile getRootDirectory() { public IFile getRootDirectory(Context context) {
File documentsDirectory = new File( File documentsDirectory = getDocumentsDir();
Environment.getExternalStorageDirectory(), "Documents"); if (!documentsDirectory.exists()) {
documentsDirectory.mkdirs(); // might be a little counter-intuitive: if we were granted READ permission already, we're also granted the write-permission
// when we ask for it, since they are both in the same storage group (for 5.1 and lower it is granted at install-time already)
// seehttps://developer.android.com/guide/topics/permissions/requesting.html#perm-groups
if (ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
if(!documentsDirectory.mkdirs()) {
// fallback to the toplevel dir - might be due to the dir not mounted/used as USB-Mass-Storage or similar
// TODO: handle unavailability of the storage/failure of the mkdir properly
Log.e("LocalDocumentsProvider", "not sure how we ended up here - if we have read permissions to use it in the first place, we also should have the write-permissions..");
documentsDirectory = Environment.getExternalStorageDirectory();
}
}
}
return new LocalFile(documentsDirectory); return new LocalFile(documentsDirectory);
} }
...@@ -41,4 +67,10 @@ public class LocalDocumentsDirectoryProvider extends LocalDocumentsProvider { ...@@ -41,4 +67,10 @@ public class LocalDocumentsDirectoryProvider extends LocalDocumentsProvider {
public int getNameResource() { public int getNameResource() {
return R.string.local_documents; return R.string.local_documents;
} }
@Override
public boolean checkProviderAvailability(Context context) {
File documentsDirectory = getDocumentsDir();
return documentsDirectory.exists() || ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
}
} }
...@@ -16,6 +16,7 @@ import org.libreoffice.storage.IFile; ...@@ -16,6 +16,7 @@ import org.libreoffice.storage.IFile;
import org.libreoffice.R; import org.libreoffice.R;
import android.content.Context;
import android.os.Environment; import android.os.Environment;
/** /**
...@@ -30,12 +31,12 @@ public class LocalDocumentsProvider implements IDocumentProvider { ...@@ -30,12 +31,12 @@ public class LocalDocumentsProvider implements IDocumentProvider {
} }
@Override @Override
public IFile getRootDirectory() { public IFile getRootDirectory(Context context) {
return new LocalFile(Environment.getExternalStorageDirectory()); return new LocalFile(Environment.getExternalStorageDirectory());
} }
@Override @Override
public IFile createFromUri(URI uri) { public IFile createFromUri(Context context, URI uri) {
return new LocalFile(uri); return new LocalFile(uri);
} }
...@@ -50,7 +51,7 @@ public class LocalDocumentsProvider implements IDocumentProvider { ...@@ -50,7 +51,7 @@ public class LocalDocumentsProvider implements IDocumentProvider {
} }
@Override @Override
public boolean checkProviderAvailability() { public boolean checkProviderAvailability(Context context) {
return true; return true;
} }
} }
...@@ -9,6 +9,8 @@ ...@@ -9,6 +9,8 @@
package org.libreoffice.storage.local; package org.libreoffice.storage.local;
import android.content.Context;
import java.io.File; import java.io.File;
import java.io.FileFilter; import java.io.FileFilter;
import java.net.URI; import java.net.URI;
...@@ -75,7 +77,7 @@ public class LocalFile implements IFile { ...@@ -75,7 +77,7 @@ public class LocalFile implements IFile {
} }
@Override @Override
public IFile getParent() { public IFile getParent(Context context) {
return new LocalFile(file.getParentFile()); return new LocalFile(file.getParentFile());
} }
......
package org.libreoffice.storage.owncloud; package org.libreoffice.storage.owncloud;
import android.content.Context;
import java.io.File; import java.io.File;
import java.io.FileFilter; import java.io.FileFilter;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
...@@ -123,12 +125,12 @@ public class OwnCloudFile implements IFile { ...@@ -123,12 +125,12 @@ public class OwnCloudFile implements IFile {
} }
@Override @Override
public IFile getParent() { public IFile getParent(Context context) {
if (parentPath == null) if (parentPath == null)
// this is the root node // this is the root node
return null; return null;
return provider.createFromUri(URI.create(parentPath)); return provider.createFromUri(context, URI.create(parentPath));
} }
@Override @Override
......
...@@ -72,12 +72,12 @@ public class OwnCloudProvider implements IDocumentProvider, ...@@ -72,12 +72,12 @@ public class OwnCloudProvider implements IDocumentProvider,
} }
@Override @Override
public IFile getRootDirectory() { public IFile getRootDirectory(Context context) {
return createFromUri(URI.create(FileUtils.PATH_SEPARATOR)); return createFromUri(context, URI.create(FileUtils.PATH_SEPARATOR));
} }
@Override @Override
public IFile createFromUri(URI uri) { public IFile createFromUri(Context context, URI uri) {
ReadRemoteFileOperation refreshOperation = new ReadRemoteFileOperation( ReadRemoteFileOperation refreshOperation = new ReadRemoteFileOperation(
uri.getPath()); uri.getPath());
RemoteOperationResult result = refreshOperation.execute(client); RemoteOperationResult result = refreshOperation.execute(client);
...@@ -179,7 +179,7 @@ public class OwnCloudProvider implements IDocumentProvider, ...@@ -179,7 +179,7 @@ public class OwnCloudProvider implements IDocumentProvider,
} }
@Override @Override
public boolean checkProviderAvailability() { public boolean checkProviderAvailability(Context context) {
return true; return true;
} }
} }
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
package org.libreoffice.ui; package org.libreoffice.ui;
import android.Manifest;
import android.app.Activity; import android.app.Activity;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
...@@ -18,6 +19,7 @@ import android.content.DialogInterface; ...@@ -18,6 +19,7 @@ import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.pm.ShortcutInfo; import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager; import android.content.pm.ShortcutManager;
import android.graphics.drawable.Icon; import android.graphics.drawable.Icon;
...@@ -30,6 +32,7 @@ import android.preference.PreferenceManager; ...@@ -30,6 +32,7 @@ import android.preference.PreferenceManager;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.design.widget.FloatingActionButton; import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.NavigationView; import android.support.design.widget.NavigationView;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat; import android.support.v4.content.ContextCompat;
import android.support.v4.view.ViewCompat; import android.support.v4.view.ViewCompat;
import android.support.v4.widget.DrawerLayout; import android.support.v4.widget.DrawerLayout;
...@@ -91,6 +94,8 @@ public class LibreOfficeUIActivity extends AppCompatActivity implements Settings ...@@ -91,6 +94,8 @@ public class LibreOfficeUIActivity extends AppCompatActivity implements Settings
private int viewMode; private int viewMode;
private int sortMode; private int sortMode;
private boolean showHiddenFiles; private boolean showHiddenFiles;
// dynamic permissions IDs
private static int PERMISSION_READ_EXTERNAL_STORAGE = 0;
FileFilter fileFilter; FileFilter fileFilter;
FilenameFilter filenameFilter; FilenameFilter filenameFilter;
...@@ -160,9 +165,20 @@ public class LibreOfficeUIActivity extends AppCompatActivity implements Settings ...@@ -160,9 +165,20 @@ public class LibreOfficeUIActivity extends AppCompatActivity implements Settings
filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED); filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
registerReceiver(mUSBReceiver, filter); registerReceiver(mUSBReceiver, filter);
// init UI and populate with contents from the provider // init UI and populate with contents from the provider
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
// TODO: remove local document providers if really is denied, code right now assumes it is granted/
// there is no onRequestPermissionsResult evaluating the callback
// without the read permissions, LO could only load documents passed via intent from other apps
Log.i(LOGTAG, "no permission to read external storage - asking for permission");
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
PERMISSION_READ_EXTERNAL_STORAGE);
}
switchToDocumentProvider(documentProviderFactory.getDefaultProvider()); switchToDocumentProvider(documentProviderFactory.getDefaultProvider());
createUI(); createUI();
getAnimations(); fabOpenAnimation = AnimationUtils.loadAnimation(this, R.anim.fab_open);
fabCloseAnimation = AnimationUtils.loadAnimation(this, R.anim.fab_close);
} }
public void createUI() { public void createUI() {
...@@ -199,7 +215,7 @@ public class LibreOfficeUIActivity extends AppCompatActivity implements Settings ...@@ -199,7 +215,7 @@ public class LibreOfficeUIActivity extends AppCompatActivity implements Settings
final ArrayList<IFile> recentFiles = new ArrayList<IFile>(); final ArrayList<IFile> recentFiles = new ArrayList<IFile>();
for (String recentFileString : recentFileStrings) { for (String recentFileString : recentFileStrings) {
try { try {
recentFiles.add(documentProvider.createFromUri(new URI(recentFileString))); recentFiles.add(documentProvider.createFromUri(this, new URI(recentFileString)));
} catch (URISyntaxException e) { } catch (URISyntaxException e) {
e.printStackTrace(); e.printStackTrace();
} catch (RuntimeException e){ } catch (RuntimeException e){
...@@ -227,20 +243,16 @@ public class LibreOfficeUIActivity extends AppCompatActivity implements Settings ...@@ -227,20 +243,16 @@ public class LibreOfficeUIActivity extends AppCompatActivity implements Settings
// Loop through the document providers menu items and check if they are available or not // Loop through the document providers menu items and check if they are available or not
for (int index = 0; index < providerNames.size(); index++) { for (int index = 0; index < providerNames.size(); index++) {
MenuItem item = navigationDrawer.getMenu().getItem(index); MenuItem item = navigationDrawer.getMenu().getItem(index);
boolean isDocumentProviderAvailable = checkDocumentProviderAvailability(documentProviderFactory.getProvider(index)); item.setEnabled(documentProviderFactory.getProvider(index).checkProviderAvailability(this));
if (!isDocumentProviderAvailable){
item.setEnabled(false);
}
} }
final Context context = this; //needed for anonymous method below
navigationDrawer.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() { navigationDrawer.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {
@Override @Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) { public boolean onNavigationItemSelected(@NonNull MenuItem item) {
switch (item.getItemId()) { switch (item.getItemId()) {
case R.id.menu_storage_preferences: { case R.id.menu_storage_preferences: {
startActivity(new Intent(context, DocumentProviderSettingsActivity.class)); startActivity(new Intent(LibreOfficeUIActivity.this, DocumentProviderSettingsActivity.class));
return true; return true;
} }
...@@ -300,11 +312,6 @@ public class LibreOfficeUIActivity extends AppCompatActivity implements Settings ...@@ -300,11 +312,6 @@ public class LibreOfficeUIActivity extends AppCompatActivity implements Settings
drawerToggle.syncState(); drawerToggle.syncState();
} }
private void getAnimations() {
fabOpenAnimation = AnimationUtils.loadAnimation(this, R.anim.fab_open);
fabCloseAnimation = AnimationUtils.loadAnimation(this, R.anim.fab_close);
}
private void expandFabMenu() { private void expandFabMenu() {
ViewCompat.animate(editFAB).rotation(45.0F).withLayer().setDuration(300).setInterpolator(new OvershootInterpolator(10.0F)).start(); ViewCompat.animate(editFAB).rotation(45.0F).withLayer().setDuration(300).setInterpolator(new OvershootInterpolator(10.0F)).start();
drawLayout.startAnimation(fabOpenAnimation); drawLayout.startAnimation(fabOpenAnimation);
...@@ -331,10 +338,6 @@ public class LibreOfficeUIActivity extends AppCompatActivity implements Settings ...@@ -331,10 +338,6 @@ public class LibreOfficeUIActivity extends AppCompatActivity implements Settings
isFabMenuOpen = false; isFabMenuOpen = false;
} }
private boolean checkDocumentProviderAvailability(IDocumentProvider provider) {
return provider.checkProviderAvailability();
}
@Override @Override
protected void onPostCreate(Bundle savedInstanceState) { protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState); super.onPostCreate(savedInstanceState);
...@@ -429,7 +432,7 @@ public class LibreOfficeUIActivity extends AppCompatActivity implements Settings ...@@ -429,7 +432,7 @@ public class LibreOfficeUIActivity extends AppCompatActivity implements Settings
// a different thread // a different thread
try { try {
documentProvider = provider[0]; documentProvider = provider[0];
homeDirectory = documentProvider.getRootDirectory(); homeDirectory = documentProvider.getRootDirectory(LibreOfficeUIActivity.this);
currentDirectory = homeDirectory; currentDirectory = homeDirectory;
filePaths = currentDirectory.listFiles(FileUtilities filePaths = currentDirectory.listFiles(FileUtilities
.getFileFilter(filterMode)); .getFileFilter(filterMode));
...@@ -444,7 +447,7 @@ public class LibreOfficeUIActivity extends AppCompatActivity implements Settings ...@@ -444,7 +447,7 @@ public class LibreOfficeUIActivity extends AppCompatActivity implements Settings
} }
}); });
startActivity(new Intent(activity, DocumentProviderSettingsActivity.class)); startActivity(new Intent(activity, DocumentProviderSettingsActivity.class));
Log.e(LOGTAG, e.getMessage(), e.getCause()); Log.e(LOGTAG, "failed to switch document provider "+ e.getMessage(), e.getCause());
} }
return null; return null;
} }
...@@ -613,7 +616,7 @@ public class LibreOfficeUIActivity extends AppCompatActivity implements Settings ...@@ -613,7 +616,7 @@ public class LibreOfficeUIActivity extends AppCompatActivity implements Settings
protected IFile doInBackground(Void... dir) { protected IFile doInBackground(Void... dir) {
// this operation may imply network access and must be run in // this operation may imply network access and must be run in
// a different thread // a different thread
return currentDirectory.getParent(); return currentDirectory.getParent(LibreOfficeUIActivity.this);
} }
@Override @Override
...@@ -830,10 +833,10 @@ public class LibreOfficeUIActivity extends AppCompatActivity implements Settings ...@@ -830,10 +833,10 @@ public class LibreOfficeUIActivity extends AppCompatActivity implements Settings
Intent i = this.getIntent(); Intent i = this.getIntent();
if (i.hasExtra(CURRENT_DIRECTORY_KEY)) { if (i.hasExtra(CURRENT_DIRECTORY_KEY)) {
try { try {
currentDirectory = documentProvider.createFromUri(new URI( currentDirectory = documentProvider.createFromUri(this, new URI(
i.getStringExtra(CURRENT_DIRECTORY_KEY))); i.getStringExtra(CURRENT_DIRECTORY_KEY)));
} catch (URISyntaxException e) { } catch (URISyntaxException e) {
currentDirectory = documentProvider.getRootDirectory(); currentDirectory = documentProvider.getRootDirectory(this);
} }
Log.d(LOGTAG, CURRENT_DIRECTORY_KEY); Log.d(LOGTAG, CURRENT_DIRECTORY_KEY);
} }
...@@ -883,10 +886,10 @@ public class LibreOfficeUIActivity extends AppCompatActivity implements Settings ...@@ -883,10 +886,10 @@ public class LibreOfficeUIActivity extends AppCompatActivity implements Settings
.getProvider(savedInstanceState.getInt(DOC_PROVIDER_KEY)); .getProvider(savedInstanceState.getInt(DOC_PROVIDER_KEY));
} }
try { try {
currentDirectory = documentProvider.createFromUri(new URI( currentDirectory = documentProvider.createFromUri(this, new URI(
savedInstanceState.getString(CURRENT_DIRECTORY_KEY))); savedInstanceState.getString(CURRENT_DIRECTORY_KEY)));
} catch (URISyntaxException e) { } catch (URISyntaxException e) {
currentDirectory = documentProvider.getRootDirectory(); currentDirectory = documentProvider.getRootDirectory(this);
} }
filterMode = savedInstanceState.getInt(FILTER_MODE_KEY, FileUtilities.ALL); filterMode = savedInstanceState.getInt(FILTER_MODE_KEY, FileUtilities.ALL);
viewMode = savedInstanceState.getInt(EXPLORER_VIEW_TYPE_KEY, GRID_VIEW); viewMode = savedInstanceState.getInt(EXPLORER_VIEW_TYPE_KEY, GRID_VIEW);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment