Crash fix for accessing DocumentInfo.derivedUri when in Recents.
Bug: 33371320
Change-Id: Ib04dfce6073dc03e4a3711f767b52de05174748a
diff --git a/src/com/android/documentsui/AbstractActionHandler.java b/src/com/android/documentsui/AbstractActionHandler.java
index 86093f9..af1860f 100644
--- a/src/com/android/documentsui/AbstractActionHandler.java
+++ b/src/com/android/documentsui/AbstractActionHandler.java
@@ -50,6 +50,7 @@
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
+import java.util.function.Consumer;
/**
* Provides support for specializing the actions (viewDocument etc.) to the host activity.
@@ -58,6 +59,7 @@
implements ActionHandler {
private static final String TAG = "AbstractActionHandler";
+ private static final int REFRESH_SPINNER_TIMEOUT = 500;
protected final T mActivity;
protected final State mState;
@@ -107,6 +109,14 @@
}
@Override
+ public void refreshDocument(DocumentInfo doc, BooleanConsumer callback) {
+ RefreshTask task = new RefreshTask(mState, doc, REFRESH_SPINNER_TIMEOUT,
+ mActivity.getApplicationContext(), mActivity::isDestroyed,
+ callback);
+ task.executeOnExecutor(mExecutors.lookup(doc == null ? null : doc.authority));
+ }
+
+ @Override
public void openSelectedInNewWindow() {
throw new UnsupportedOperationException("Can't open in new window.");
}
diff --git a/src/com/android/documentsui/ActionHandler.java b/src/com/android/documentsui/ActionHandler.java
index 5f0449c..98f9896 100644
--- a/src/com/android/documentsui/ActionHandler.java
+++ b/src/com/android/documentsui/ActionHandler.java
@@ -42,6 +42,12 @@
*/
void ejectRoot(RootInfo root, BooleanConsumer listener);
+ /**
+ * Attempts to refresh the given DocumentInfo, which should be at the top of the state stack.
+ * Returns a boolean answer to the callback, given by {@link ContentProvider#refresh}.
+ */
+ void refreshDocument(DocumentInfo doc, BooleanConsumer callback);
+
void showAppDetails(ResolveInfo info);
void openRoot(RootInfo root);
diff --git a/src/com/android/documentsui/RefreshTask.java b/src/com/android/documentsui/RefreshTask.java
index 4692082..d208346 100644
--- a/src/com/android/documentsui/RefreshTask.java
+++ b/src/com/android/documentsui/RefreshTask.java
@@ -29,12 +29,12 @@
import android.util.Log;
import com.android.documentsui.base.ApplicationScope;
+import com.android.documentsui.base.BooleanConsumer;
import com.android.documentsui.base.CheckedTask;
+import com.android.documentsui.base.DocumentInfo;
import com.android.documentsui.base.Shared;
import com.android.documentsui.base.State;
-import java.util.function.Consumer;
-
/**
* A {@link CheckedTask} that calls
* {@link ContentResolver#refresh(Uri, android.os.Bundle, android.os.CancellationSignal)} on the
@@ -46,14 +46,14 @@
private final @ApplicationScope Context mContext;
private final State mState;
- private final Uri mUri;
- private final Consumer<Boolean> mCallback;
+ private final DocumentInfo mDoc;
+ private final BooleanConsumer mCallback;
private final CancellationSignal mSignal;
- public RefreshTask(State state, Uri uri, long timeout, @ApplicationScope Context context, Check check,
- Consumer<Boolean> callback) {
+ public RefreshTask(State state, DocumentInfo doc, long timeout,
+ @ApplicationScope Context context, Check check, BooleanConsumer callback) {
super(check);
- mUri = uri;
+ mDoc = doc;
mContext = context;
mState = state;
mCallback = callback;
@@ -63,13 +63,18 @@
@Override
public @Nullable Boolean run(Void... params) {
- if (mUri == null) {
- Log.w(TAG, "Attempted to refresh on a null uri. Aborting.");
+ if (mDoc == null) {
+ Log.w(TAG, "Ignoring attempt to refresh due to null DocumentInfo.");
return false;
}
- if (mUri != mState.stack.peek().derivedUri) {
- Log.w(TAG, "Attempted to refresh on a non-top-level uri. Aborting.");
+ if (mState.stack.isEmpty()) {
+ Log.w(TAG, "Ignoring attempt to refresh due to empty stack.");
+ return false;
+ }
+
+ if (!mDoc.derivedUri.equals(mState.stack.peek().derivedUri)) {
+ Log.w(TAG, "Ignoring attempt to refresh on a non-top-level uri.");
return false;
}
@@ -78,17 +83,17 @@
// and we will update accordingly. Else, we just tell the callback that Refresh is not
// supported.
if (!Shared.ENABLE_OMC_API_FEATURES) {
- Log.w(TAG, "Attempted to call Refresh on an older Android platform. Aborting.");
+ Log.w(TAG, "Ignoring attempt to call Refresh on an older Android platform.");
return false;
}
final ContentResolver resolver = mContext.getContentResolver();
- final String authority = mUri.getAuthority();
+ final String authority = mDoc.authority;
boolean refreshSupported = false;
ContentProviderClient client = null;
try {
client = DocumentsApplication.acquireUnstableProviderOrThrow(resolver, authority);
- refreshSupported = client.refresh(mUri, null, mSignal);
+ refreshSupported = client.refresh(mDoc.derivedUri, null, mSignal);
} catch (Exception e) {
Log.w(TAG, "Failed to refresh", e);
} finally {
diff --git a/src/com/android/documentsui/TimeoutTask.java b/src/com/android/documentsui/TimeoutTask.java
index 5fb20cf..57b119e 100644
--- a/src/com/android/documentsui/TimeoutTask.java
+++ b/src/com/android/documentsui/TimeoutTask.java
@@ -19,6 +19,7 @@
import android.annotation.CallSuper;
import android.os.AsyncTask;
import android.os.Handler;
+import android.os.Looper;
import com.android.documentsui.base.CheckedTask;
import com.android.documentsui.base.DocumentInfo;
@@ -48,7 +49,9 @@
return;
}
- Handler handler = new Handler();
+ // Need to initialize handler to main Looper so it can initialize correctly in test cases
+ // Instrumentation threads don't have looper initialized
+ Handler handler = new Handler(Looper.getMainLooper());
handler.postDelayed(() -> {
if (getStatus() == AsyncTask.Status.RUNNING) {
onTimeout();
diff --git a/src/com/android/documentsui/dirlist/DirectoryFragment.java b/src/com/android/documentsui/dirlist/DirectoryFragment.java
index 26520e8..c641275 100644
--- a/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -1145,18 +1145,15 @@
cache.removeUri(mModel.getItemUri(ids[i]));
}
- final Uri uri = mState.stack.peek().derivedUri;
- RefreshTask task = new RefreshTask(mState, uri, REFRESH_SPINNER_TIMEOUT,
- getContext().getApplicationContext(), this::isDetached,
- (Boolean refreshSupported) -> {
- if (refreshSupported) {
- mRefreshLayout.setRefreshing(false);
- } else {
- // If Refresh API isn't available, we will explicitly reload the loader
- getLoaderManager().restartLoader(LOADER_ID, null, mLoaderCallbacks);
- }
- });
- task.executeOnExecutor(mActivity.getExecutorForCurrentDirectory());
+ final DocumentInfo doc = mState.stack.peek();
+ mActions.refreshDocument(doc, (boolean refreshSupported) -> {
+ if (refreshSupported) {
+ mRefreshLayout.setRefreshing(false);
+ } else {
+ // If Refresh API isn't available, we will explicitly reload the loader
+ getLoaderManager().restartLoader(LOADER_ID, null, mLoaderCallbacks);
+ }
+ });
}
private final class ModelUpdateListener implements EventListener<Model.Update> {