There's sadly some Android issues which can't be worked around:
- SAF trim prefix/suffix whitespace and "filter" certain characters in filenames,
an
FileAlreadyExistsException
is thrown if that occur when creating a file. - Volumes/mounts not using
sdcardfs
are unable to change file last-modified time, so don't rely on StandardCopyOption.COPY_ATTRIBUTES, Files.setLastModifiedTime() nor Files.html.setAttribute() See report. - WatchService, which use FileObserver, doesn't seem to work on secondary external storage when accessed through SAF.
See safs-android-app for integration tests.
Add to Gradle project dependencies
:
implementation 'com.llamalab.safs:safs-android:0.2.0'
Add to AndroidManifest.xml
:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
Add to proguard-rules.pro
:
-keep class com.llamalab.safs.spi.FileSystemProvider { *; }
-keep class * extends com.llamalab.safs.spi.FileSystemProvider { *; }
-keep class * extends com.llamalab.safs.spi.FileTypeDetector { *; }
Same as java.nio.file packages except located in com.llamalab.safs
,
and instead of java.nio.channels.SeekableByteChannel
use com.llamalab.safs.channels.SeekableByteChannel
.
In addition to the standard Files there's com.llamalab.safs.android.AndroidFiles with some convenience methods, and com.llamalab.safs.android.AndroidWatchEventKinds with Android specific WatchEvent.Kind.
Before use, safs-android
need to be told to use the com.llamalab.safs.android.AndroidFileSystemProvider
as default,
otherwise it will use the safs-core provider.
It also need a reference to a Context
, assigned once, so it's easiest to do early in Application
.
import com.llamalab.safs.FileSystems;
import com.llamalab.safs.android.AndroidFileSystem;
public class MyApplication extends Application {
static {
System.setProperty("com.llamalab.safs.spi.DefaultFileSystemProvider", AndroidFileSystemProvider.class.getName());
}
public void onCreate () {
((AndroidFileSystem)FileSystems.getDefault()).setContext(this);
super.onCreate();
}
}
Before accessing a secondary external storage volume, i.e. removable SD card, on Android 5.1+,
the app has to be granted permission to it by the user, then safs-android
has to be informed.
That's done with a ACTION_OPEN_DOCUMENT_TREE intent.
public class MyActivity extends Activity {
private static final int REQUEST_CODE_OPEN_DOCUMENT = 1;
@Override
protected void onCreate (Bundle state) {
super.onCreate(state);
// Ask user to grant permission to an storage volume, or subfolder thereof
startActivityForResult(new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE), REQUEST_CODE_OPEN_DOCUMENT);
}
@Override
protected void onActivityResult (int requestCode, int resultCode, Intent resultIntent) {
if (REQUEST_CODE_OPEN_DOCUMENT != requestCode)
super.onActivityResult(requestCode, resultCode, resultIntent);
else if (RESULT_OK == resultCode) {
// Take permission grant
((AndroidFileSystem)FileSystems.getDefault()).takePersistableUriPermission(resultIntent);
}
}
}