I created a test Android app for being informed about a folder created with Storage Access Framework being updated.
The app creates the folder by means of the SAF picker.
When the user has created the folder the uri is used for creating a DocumentFile object.
Then every 2 seconds the lastModified() method is asked for a long integer that is a timestamp.
That number is logged so I can see when the folder is modified.
When I create a folder on the device storage the app correctly logs events of modification, like creating a subfolder, copying a file into the main folder and so on.
This works only when something happens inside the folder, not subfolders. But I am not interested in that level of modifications. I just need to know when something happens inside the main folder.
But when I perform the same operations on a SAF folder created on the cloud storage (the user can access a cloud root and then create the folder over there) modifications are not logged.
That is, the lastModified() method doesn't yield an updated value. I get the same original value at every time.
What's wrong with my code? Do I have to refresh something or call some other method first?
This is the app code:
package com.example.safevents;
import android.app.Activity;
...
... //other imports
public class MainActivity extends AppCompatActivity {
Activity activity;
private ScheduledExecutorService scheduleTaskExecutor;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
activity=this;
FloatingActionButton fab = findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
.setAction("Action", null).show();
openPickerForFolderCreation(activity,0);
}
});
}
@Override
protected void onDestroy()
{
super.onDestroy();
}
@Override
protected void onActivityResult(int requestCode, int resultCode,Intent returnedIntent)
{
Log.d("SAF","picker returned "+resultCode+" "+requestCode);
if (returnedIntent!=null) {
Log.d("SAF", "intent=" + returnedIntent.toString());
if (returnedIntent.getData() != null)
Log.d("SAF", "uri=" + returnedIntent.getData().toString());
if (resultCode == -1)
if (requestCode == 0) {
Uri uri = takePermanentReadWritePermissions(activity, returnedIntent.getData(), returnedIntent.getFlags());
Log.d("SAF", "uri=" + uri.toString());
Log.d("SAF", "read/write permissions=" + arePermissionsGranted(activity, uri.toString()));
final DocumentFile df = DocumentFile.fromSingleUri(activity, uri);
SAFEvents.getInstance().mainFolder = df;
scheduleTaskExecutor = Executors.newScheduledThreadPool(5);
scheduleTaskExecutor.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
runOnUiThread(new Runnable() {
@Override
public void run() {
Log.d("folder modified", String.valueOf(df.lastModified()));
}
});
}
}, 0, 2, TimeUnit.SECONDS);
}
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
}
here are some methods:
public void openPickerForFolderCreation(Activity activity, int requestCode) {
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
intent.setType("vnd.android.document/directory");
intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
activity.startActivityForResult(intent, requestCode);
}
public boolean arePermissionsGranted(Activity activity, String uriString) {
Uri uri = Uri.parse(uriString);
ContentResolver resolver = activity.getContentResolver();
List<UriPermission> list = resolver.getPersistedUriPermissions();
for (int i = 0; i < list.size(); i++) {
Log.d("SAF","checking permissions of "+list.get(i).getUri().toString());
if (((Uri.decode(list.get(i).getUri().toString())).equals(Uri.decode(uriString))) && list.get(i).isWritePermission() && list.get(i).isReadPermission()) {
return true;
}
}
return false;
}
public Uri takePermanentReadWritePermissions(Activity activity, Uri uri, int flags) {
int takeFlags = flags
&
(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
);
ContentResolver resolver = activity.getContentResolver();
resolver.takePersistableUriPermission(uri, takeFlags);
return uri;
}
a singleton is for keeping the reference, if it is necessary
package com.example.safevents;
import androidx.documentfile.provider.DocumentFile;
public class SAFEvents {
private static final SAFEvents ourInstance = new SAFEvents();
DocumentFile mainFolder;
public static SAFEvents getInstance() {
return ourInstance;
}
private SAFEvents() {
}
}