Merge "Simplify State initialization w/ better semantics + improved Task security."

This commit is contained in:
Steve McKay
2016-02-05 04:16:37 +00:00
committed by Android (Google) Code Review
6 changed files with 310 additions and 197 deletions

View File

@@ -45,22 +45,16 @@ import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.Spinner;
import android.widget.Toolbar;
import com.android.documentsui.RecentsProvider.ResumeColumns;
import com.android.documentsui.SearchManager.SearchManagerListener;
import com.android.documentsui.State.ViewMode;
import com.android.documentsui.dirlist.DirectoryFragment;
import com.android.documentsui.model.DocumentInfo;
import com.android.documentsui.model.DocumentStack;
import com.android.documentsui.model.DurableUtils;
import com.android.documentsui.model.RootInfo;
import com.android.internal.util.Preconditions;
import libcore.io.IoUtils;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@@ -86,8 +80,8 @@ public abstract class BaseActivity extends Activity
abstract void onTaskFinished(Uri... uris);
abstract void refreshDirectory(int anim);
abstract void saveStackBlocking();
abstract State buildState();
/** Allows sub-classes to include information in a newly created State instance. */
abstract void includeState(State initialState);
public BaseActivity(@LayoutRes int layoutId, String tag) {
mLayoutId = layoutId;
@@ -102,9 +96,7 @@ public abstract class BaseActivity extends Activity
setContentView(mLayoutId);
mDrawer = DrawerController.create(this);
mState = (icicle != null)
? icicle.<State>getParcelable(EXTRA_STATE)
: buildState();
mState = getState(icicle);
Metrics.logActivityLaunch(this, mState, getIntent());
mRoots = DocumentsApplication.getRootsCache(this);
@@ -113,7 +105,8 @@ public abstract class BaseActivity extends Activity
new RootsCache.OnCacheUpdateListener() {
@Override
public void onCacheUpdate() {
new HandleRootsChangedTask().execute(getCurrentRoot());
new HandleRootsChangedTask(BaseActivity.this)
.execute(getCurrentRoot());
}
});
@@ -182,7 +175,20 @@ public abstract class BaseActivity extends Activity
super.onDestroy();
}
State buildDefaultState() {
private State getState(@Nullable Bundle icicle) {
if (icicle != null) {
State state = icicle.<State>getParcelable(EXTRA_STATE);
if (DEBUG) Log.d(mTag, "Recovered existing state object: " + state);
return state;
}
State state = createSharedState();
includeState(state);
if (DEBUG) Log.d(mTag, "Created new state object: " + state);
return state;
}
private State createSharedState() {
State state = new State();
final Intent intent = getIntent();
@@ -222,7 +228,7 @@ public abstract class BaseActivity extends Activity
if (mRoots.isRecentsRoot(root)) {
refreshCurrentRootAndDirectory(ANIM_NONE);
} else {
new PickRootTask(root).executeOnExecutor(getExecutorForCurrentDirectory());
new PickRootTask(this, root).executeOnExecutor(getExecutorForCurrentDirectory());
}
}
@@ -570,115 +576,40 @@ public abstract class BaseActivity extends Activity
}
}
final class PickRootTask extends AsyncTask<Void, Void, DocumentInfo> {
private static final class PickRootTask extends PairedTask<BaseActivity, Void, DocumentInfo> {
private RootInfo mRoot;
public PickRootTask(RootInfo root) {
public PickRootTask(BaseActivity activity, RootInfo root) {
super(activity);
mRoot = root;
}
@Override
protected DocumentInfo doInBackground(Void... params) {
return getRootDocumentBlocking(mRoot);
protected DocumentInfo run(Void... params) {
return mOwner.getRootDocumentBlocking(mRoot);
}
@Override
protected void onPostExecute(DocumentInfo result) {
if (result != null && !isDestroyed()) {
openContainerDocument(result);
protected void finish(DocumentInfo result) {
if (result != null) {
mOwner.openContainerDocument(result);
}
}
}
final class RestoreStackTask extends AsyncTask<Void, Void, Void> {
private volatile boolean mRestoredStack;
private volatile boolean mExternal;
@Override
protected Void doInBackground(Void... params) {
if (DEBUG && !mState.stack.isEmpty()) {
Log.w(mTag, "Overwriting existing stack.");
}
RootsCache roots = DocumentsApplication.getRootsCache(BaseActivity.this);
// Restore last stack for calling package
final String packageName = getCallingPackageMaybeExtra();
final Cursor cursor = getContentResolver()
.query(RecentsProvider.buildResume(packageName), null, null, null, null);
try {
if (cursor.moveToFirst()) {
mExternal = cursor.getInt(cursor.getColumnIndex(ResumeColumns.EXTERNAL)) != 0;
final byte[] rawStack = cursor.getBlob(
cursor.getColumnIndex(ResumeColumns.STACK));
DurableUtils.readFromArray(rawStack, mState.stack);
mRestoredStack = true;
}
} catch (IOException e) {
Log.w(mTag, "Failed to resume: " + e);
} finally {
IoUtils.closeQuietly(cursor);
}
if (mRestoredStack) {
// Update the restored stack to ensure we have freshest data
final Collection<RootInfo> matchingRoots = roots.getMatchingRootsBlocking(mState);
try {
mState.stack.updateRoot(matchingRoots);
mState.stack.updateDocuments(getContentResolver());
} catch (FileNotFoundException e) {
Log.w(mTag, "Failed to restore stack: " + e);
mState.stack.reset();
mRestoredStack = false;
}
}
return null;
}
@Override
protected void onPostExecute(Void result) {
if (isDestroyed()) return;
mState.restored = true;
refreshCurrentRootAndDirectory(ANIM_NONE);
onStackRestored(mRestoredStack, mExternal);
}
}
final class RestoreRootTask extends AsyncTask<Void, Void, RootInfo> {
private Uri mRootUri;
public RestoreRootTask(Uri rootUri) {
mRootUri = rootUri;
}
@Override
protected RootInfo doInBackground(Void... params) {
final String rootId = DocumentsContract.getRootId(mRootUri);
return mRoots.getRootOneshot(mRootUri.getAuthority(), rootId);
}
@Override
protected void onPostExecute(RootInfo root) {
if (isDestroyed()) return;
mState.restored = true;
if (root != null) {
onRootPicked(root);
} else {
Log.w(mTag, "Failed to find root: " + mRootUri);
finish();
}
}
}
final class HandleRootsChangedTask extends AsyncTask<RootInfo, Void, RootInfo> {
private static final class HandleRootsChangedTask
extends PairedTask<BaseActivity, RootInfo, RootInfo> {
DocumentInfo mHome;
public HandleRootsChangedTask(BaseActivity activity) {
super(activity);
}
@Override
protected RootInfo doInBackground(RootInfo... roots) {
protected RootInfo run(RootInfo... roots) {
checkArgument(roots.length == 1);
final RootInfo currentRoot = roots[0];
final Collection<RootInfo> cachedRoots = mRoots.getRootsBlocking();
final Collection<RootInfo> cachedRoots = mOwner.mRoots.getRootsBlocking();
RootInfo homeRoot = null;
for (final RootInfo root : cachedRoots) {
if (root.isHome()) {
@@ -690,17 +621,17 @@ public abstract class BaseActivity extends Activity
}
}
Preconditions.checkNotNull(homeRoot);
mHome = getRootDocumentBlocking(homeRoot);
mHome = mOwner.getRootDocumentBlocking(homeRoot);
return homeRoot;
}
@Override
protected void onPostExecute(RootInfo homeRoot) {
if (homeRoot != null && mHome != null && !isDestroyed()) {
protected void finish(RootInfo homeRoot) {
if (homeRoot != null && mHome != null) {
// Clear entire backstack and start in new root
mState.onRootChanged(homeRoot);
mSearchManager.update(homeRoot);
openContainerDocument(mHome);
mOwner.mState.onRootChanged(homeRoot);
mOwner.mSearchManager.update(homeRoot);
mOwner.openContainerDocument(mHome);
}
}
}

View File

@@ -16,6 +16,7 @@
package com.android.documentsui;
import static com.android.documentsui.Shared.DEBUG;
import static com.android.documentsui.State.ACTION_CREATE;
import static com.android.documentsui.State.ACTION_GET_CONTENT;
import static com.android.documentsui.State.ACTION_OPEN;
@@ -31,11 +32,12 @@ import android.content.ComponentName;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.database.Cursor;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Parcelable;
import android.provider.DocumentsContract;
@@ -52,7 +54,12 @@ import com.android.documentsui.model.DurableUtils;
import com.android.documentsui.model.RootInfo;
import com.android.documentsui.services.FileOperationService;
import libcore.io.IoUtils;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
public class DocumentsActivity extends BaseActivity {
@@ -94,16 +101,14 @@ public class DocumentsActivity extends BaseActivity {
// In this case, we set the activity title in AsyncTask.onPostExecute(). To prevent
// talkback from reading aloud the default title, we clear it here.
setTitle("");
new RestoreStackTask().execute();
new RestoreStackTask(this).execute();
} else {
refreshCurrentRootAndDirectory(ANIM_NONE);
}
}
@Override
State buildState() {
State state = buildDefaultState();
void includeState(State state) {
final Intent intent = getIntent();
final String action = intent.getAction();
if (Intent.ACTION_OPEN_DOCUMENT.equals(action)) {
@@ -134,8 +139,6 @@ public class DocumentsActivity extends BaseActivity {
state.transferMode = intent.getIntExtra(FileOperationService.EXTRA_OPERATION,
FileOperationService.OPERATION_COPY);
}
return state;
}
@Override
@@ -319,11 +322,13 @@ public class DocumentsActivity extends BaseActivity {
}
void onSaveRequested(DocumentInfo replaceTarget) {
new ExistingFinishTask(replaceTarget.derivedUri).executeOnExecutor(getExecutorForCurrentDirectory());
new ExistingFinishTask(this, replaceTarget.derivedUri)
.executeOnExecutor(getExecutorForCurrentDirectory());
}
void onSaveRequested(String mimeType, String displayName) {
new CreateFinishTask(mimeType, displayName).executeOnExecutor(getExecutorForCurrentDirectory());
new CreateFinishTask(this, mimeType, displayName)
.executeOnExecutor(getExecutorForCurrentDirectory());
}
@Override
@@ -343,7 +348,8 @@ public class DocumentsActivity extends BaseActivity {
openContainerDocument(doc);
} else if (mState.action == ACTION_OPEN || mState.action == ACTION_GET_CONTENT) {
// Explicit file picked, return
new ExistingFinishTask(doc.derivedUri).executeOnExecutor(getExecutorForCurrentDirectory());
new ExistingFinishTask(this, doc.derivedUri)
.executeOnExecutor(getExecutorForCurrentDirectory());
} else if (mState.action == ACTION_CREATE) {
// Replace selected file
SaveFragment.get(fm).setReplaceTarget(doc);
@@ -358,7 +364,8 @@ public class DocumentsActivity extends BaseActivity {
for (int i = 0; i < size; i++) {
uris[i] = docs.get(i).derivedUri;
}
new ExistingFinishTask(uris).executeOnExecutor(getExecutorForCurrentDirectory());
new ExistingFinishTask(this, uris)
.executeOnExecutor(getExecutorForCurrentDirectory());
}
}
@@ -373,11 +380,10 @@ public class DocumentsActivity extends BaseActivity {
// Should not be reached.
throw new IllegalStateException("Invalid mState.action.");
}
new PickFinishTask(result).executeOnExecutor(getExecutorForCurrentDirectory());
new PickFinishTask(this, result).executeOnExecutor(getExecutorForCurrentDirectory());
}
@Override
void saveStackBlocking() {
void writeStackToRecentsBlocking() {
final ContentResolver resolver = getContentResolver();
final ContentValues values = new ContentValues();
@@ -438,69 +444,138 @@ public class DocumentsActivity extends BaseActivity {
finish();
}
public static DocumentsActivity get(Fragment fragment) {
return (DocumentsActivity) fragment.getActivity();
}
private final class PickFinishTask extends AsyncTask<Void, Void, Void> {
/**
* Restores the stack from Recents for the specified package.
*/
private static final class RestoreStackTask
extends PairedTask<DocumentsActivity, Void, Void> {
private volatile boolean mRestoredStack;
private volatile boolean mExternal;
private Context mContext;
private State mState;
public RestoreStackTask(DocumentsActivity activity) {
super(activity);
mState = activity.mState;
}
@Override
protected Void run(Void... params) {
if (DEBUG && !mState.stack.isEmpty()) {
Log.w(TAG, "Overwriting existing stack.");
}
RootsCache roots = DocumentsApplication.getRootsCache(mContext);
String packageName = mOwner.getCallingPackageMaybeExtra();
Uri resumeUri = RecentsProvider.buildResume(packageName);
Cursor cursor = mOwner.getContentResolver().query(resumeUri, null, null, null, null);
try {
if (cursor.moveToFirst()) {
mExternal = cursor.getInt(cursor.getColumnIndex(ResumeColumns.EXTERNAL)) != 0;
final byte[] rawStack = cursor.getBlob(
cursor.getColumnIndex(ResumeColumns.STACK));
DurableUtils.readFromArray(rawStack, mState.stack);
mRestoredStack = true;
}
} catch (IOException e) {
Log.w(TAG, "Failed to resume: " + e);
} finally {
IoUtils.closeQuietly(cursor);
}
if (mRestoredStack) {
// Update the restored stack to ensure we have freshest data
final Collection<RootInfo> matchingRoots = roots.getMatchingRootsBlocking(mState);
try {
mState.stack.updateRoot(matchingRoots);
mState.stack.updateDocuments(mOwner.getContentResolver());
} catch (FileNotFoundException e) {
Log.w(TAG, "Failed to restore stack for package: " + packageName
+ " because of error: "+ e);
mState.stack.reset();
mRestoredStack = false;
}
}
return null;
}
@Override
protected void finish(Void result) {
mState.restored = true;
mOwner.refreshCurrentRootAndDirectory(ANIM_NONE);
mOwner.onStackRestored(mRestoredStack, mExternal);
}
}
private static final class PickFinishTask extends PairedTask<DocumentsActivity, Void, Void> {
private final Uri mUri;
public PickFinishTask(Uri uri) {
public PickFinishTask(DocumentsActivity activity, Uri uri) {
super(activity);
mUri = uri;
}
@Override
protected Void doInBackground(Void... params) {
saveStackBlocking();
protected Void run(Void... params) {
mOwner.writeStackToRecentsBlocking();
return null;
}
@Override
protected void onPostExecute(Void result) {
onTaskFinished(mUri);
protected void finish(Void result) {
mOwner.onTaskFinished(mUri);
}
}
final class ExistingFinishTask extends AsyncTask<Void, Void, Void> {
private static final class ExistingFinishTask extends PairedTask<DocumentsActivity, Void, Void> {
private final Uri[] mUris;
public ExistingFinishTask(Uri... uris) {
public ExistingFinishTask(DocumentsActivity activity, Uri... uris) {
super(activity);
mUris = uris;
}
@Override
protected Void doInBackground(Void... params) {
saveStackBlocking();
protected Void run(Void... params) {
mOwner.writeStackToRecentsBlocking();
return null;
}
@Override
protected void onPostExecute(Void result) {
onTaskFinished(mUris);
protected void finish(Void result) {
mOwner.onTaskFinished(mUris);
}
}
/**
* Task that creates a new document in the background.
*/
final class CreateFinishTask extends AsyncTask<Void, Void, Uri> {
private static final class CreateFinishTask extends PairedTask<DocumentsActivity, Void, Uri> {
private final String mMimeType;
private final String mDisplayName;
public CreateFinishTask(String mimeType, String displayName) {
public CreateFinishTask(DocumentsActivity activity, String mimeType, String displayName) {
super(activity);
mMimeType = mimeType;
mDisplayName = displayName;
}
@Override
protected void onPreExecute() {
setPending(true);
protected void prepare() {
mOwner.setPending(true);
}
@Override
protected Uri doInBackground(Void... params) {
final ContentResolver resolver = getContentResolver();
final DocumentInfo cwd = getCurrentDirectory();
protected Uri run(Void... params) {
final ContentResolver resolver = mOwner.getContentResolver();
final DocumentInfo cwd = mOwner.getCurrentDirectory();
ContentProviderClient client = null;
Uri childUri = null;
@@ -516,22 +591,22 @@ public class DocumentsActivity extends BaseActivity {
}
if (childUri != null) {
saveStackBlocking();
mOwner.writeStackToRecentsBlocking();
}
return childUri;
}
@Override
protected void onPostExecute(Uri result) {
protected void finish(Uri result) {
if (result != null) {
onTaskFinished(result);
mOwner.onTaskFinished(result);
} else {
Snackbars.makeSnackbar(
DocumentsActivity.this, R.string.save_error, Snackbar.LENGTH_SHORT).show();
mOwner, R.string.save_error, Snackbar.LENGTH_SHORT).show();
}
setPending(false);
mOwner.setPending(false);
}
}
}

View File

@@ -24,8 +24,6 @@ import android.app.Fragment;
import android.app.FragmentManager;
import android.content.ActivityNotFoundException;
import android.content.ClipData;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
@@ -37,10 +35,8 @@ import android.view.Menu;
import android.view.MenuItem;
import android.widget.Toolbar;
import com.android.documentsui.RecentsProvider.ResumeColumns;
import com.android.documentsui.dirlist.DirectoryFragment;
import com.android.documentsui.model.DocumentInfo;
import com.android.documentsui.model.DurableUtils;
import com.android.documentsui.model.RootInfo;
import com.android.internal.util.Preconditions;
@@ -72,23 +68,19 @@ public class DownloadsActivity extends BaseActivity {
// talkback from reading aloud the default title, we clear it here.
setTitle("");
final Uri rootUri = getIntent().getData();
new RestoreRootTask(rootUri).executeOnExecutor(getExecutorForCurrentDirectory());
new RestoreRootTask(this, rootUri).executeOnExecutor(getExecutorForCurrentDirectory());
} else {
refreshCurrentRootAndDirectory(ANIM_NONE);
}
}
@Override
State buildState() {
State state = buildDefaultState();
void includeState(State state) {
state.action = ACTION_MANAGE;
state.acceptMimes = new String[] { "*/*" };
state.allowMultiple = true;
state.showSize = true;
state.excludedAuthorities = getExcludedAuthorities();
return state;
}
@Override
@@ -169,21 +161,6 @@ public class DownloadsActivity extends BaseActivity {
@Override
public void onDocumentsPicked(List<DocumentInfo> docs) {}
@Override
void saveStackBlocking() {
final ContentResolver resolver = getContentResolver();
final ContentValues values = new ContentValues();
final byte[] rawStack = DurableUtils.writeToArrayOrNull(mState.stack);
// Remember location for next app launch
final String packageName = getCallingPackageMaybeExtra();
values.clear();
values.put(ResumeColumns.STACK, rawStack);
values.put(ResumeColumns.EXTERNAL, 0);
resolver.insert(RecentsProvider.buildResume(packageName), values);
}
@Override
void onTaskFinished(Uri... uris) {
Log.d(TAG, "onFinished() " + Arrays.toString(uris));

View File

@@ -30,7 +30,6 @@ import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Intent;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Parcelable;
import android.provider.DocumentsContract;
@@ -98,19 +97,19 @@ public class FilesActivity extends BaseActivity {
refreshCurrentRootAndDirectory(ANIM_NONE);
} else if (intent.getAction() == Intent.ACTION_VIEW) {
checkArgument(uri != null);
new OpenUriForViewTask().executeOnExecutor(
new OpenUriForViewTask(this).executeOnExecutor(
ProviderExecutor.forAuthority(uri.getAuthority()), uri);
} else if (DocumentsContract.isRootUri(this, uri)) {
if (DEBUG) Log.d(TAG, "Launching with root URI.");
// If we've got a specific root to display, restore that root using a dedicated
// authority. That way a misbehaving provider won't result in an ANR.
new RestoreRootTask(uri).executeOnExecutor(
new RestoreRootTask(this, uri).executeOnExecutor(
ProviderExecutor.forAuthority(uri.getAuthority()));
} else {
if (DEBUG) Log.d(TAG, "Launching into Home directory.");
// If all else fails, try to load "Home" directory.
final Uri homeUri = DocumentsContract.buildHomeUri();
new RestoreRootTask(homeUri).executeOnExecutor(
new RestoreRootTask(this, homeUri).executeOnExecutor(
ProviderExecutor.forAuthority(homeUri.getAuthority()));
}
@@ -134,9 +133,7 @@ public class FilesActivity extends BaseActivity {
}
@Override
State buildState() {
State state = buildDefaultState();
void includeState(State state) {
final Intent intent = getIntent();
state.action = State.ACTION_BROWSE;
@@ -149,8 +146,6 @@ public class FilesActivity extends BaseActivity {
if (stack != null) {
state.stack = stack;
}
return state;
}
@Override
@@ -363,13 +358,15 @@ public class FilesActivity extends BaseActivity {
}
}
@Override
void saveStackBlocking() {
// Turns out only DocumentsActivity was ever calling saveStackBlocking.
// There may be a case where we want to contribute entries from
// Behavior here in FilesActivity, but it isn't yet obvious.
// TODO: Contribute to recents, or remove this.
void writeStackToRecentsBlocking() {
final ContentResolver resolver = getContentResolver();
final ContentValues values = new ContentValues();
final byte[] rawStack = DurableUtils.writeToArrayOrNull(
getDisplayState().stack);
final byte[] rawStack = DurableUtils.writeToArrayOrNull(mState.stack);
// Remember location for next app launch
final String packageName = getCallingPackageMaybeExtra();
@@ -408,12 +405,19 @@ public class FilesActivity extends BaseActivity {
* to know which root to select. Also, the stack doesn't contain intermediate directories.
* It's primarly used for opening ZIP archives from Downloads app.
*/
final class OpenUriForViewTask extends AsyncTask<Uri, Void, Void> {
private static final class OpenUriForViewTask extends PairedTask<FilesActivity, Uri, Void> {
private final State mState;
public OpenUriForViewTask(FilesActivity activity) {
super(activity);
mState = activity.mState;
}
@Override
protected Void doInBackground(Uri... params) {
protected Void run(Uri... params) {
final Uri uri = params[0];
final RootsCache rootsCache = DocumentsApplication.getRootsCache(FilesActivity.this);
final RootsCache rootsCache = DocumentsApplication.getRootsCache(mOwner);
final String authority = uri.getAuthority();
final Collection<RootInfo> roots =
@@ -426,20 +430,17 @@ public class FilesActivity extends BaseActivity {
final RootInfo root = roots.iterator().next();
mState.stack.root = root;
try {
mState.stack.add(DocumentInfo.fromUri(getContentResolver(), uri));
mState.stack.add(DocumentInfo.fromUri(mOwner.getContentResolver(), uri));
} catch (FileNotFoundException e) {
Log.e(TAG, "Failed to resolve DocumentInfo from Uri: " + uri);
}
mState.stack.add(getRootDocumentBlocking(root));
mState.stack.add(mOwner.getRootDocumentBlocking(root));
return null;
}
@Override
protected void onPostExecute(Void result) {
if (isDestroyed()) {
return;
}
refreshCurrentRootAndDirectory(ANIM_NONE);
protected void finish(Void result) {
mOwner.refreshCurrentRootAndDirectory(ANIM_NONE);
}
}
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.documentsui;
import android.app.Activity;
import android.os.AsyncTask;
/**
* An {@link AsyncTask} that guards work with checks that a paired {@link Activity}
* is still alive. Instances of this class make no progress.
*
* <p>Use this type of task for greater safety when executing tasks that might complete
* after an Activity is destroyed.
*
* <p>Also useful as tasks can be static, limiting scope, but still have access to
* the owning class (by way the A template and the mActivity field).
*
* @template Owner Activity type.
* @template Input input type
* @template Output output type
*/
abstract class PairedTask<Owner extends Activity, Input, Output>
extends AsyncTask<Input, Void, Output> {
protected final Owner mOwner;
public PairedTask(Owner owner) {
mOwner = owner;
}
/** Called prior to run being executed. Analogous to {@link AsyncTask#onPreExecute} */
void prepare() {}
/** Analogous to {@link AsyncTask#doInBackground} */
abstract Output run(Input... input);
/** Analogous to {@link AsyncTask#onPostExecute} */
abstract void finish(Output output);
@Override
final protected void onPreExecute() {
if (mOwner.isDestroyed()) {
return;
}
prepare();
}
@Override
final protected Output doInBackground(Input... input) {
if (mOwner.isDestroyed()) {
return null;
}
return run(input);
}
@Override
final protected void onPostExecute(Output result) {
if (mOwner.isDestroyed()) {
return;
}
finish(result);
}
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.documentsui;
import android.net.Uri;
import android.provider.DocumentsContract;
import android.util.Log;
import com.android.documentsui.model.RootInfo;
final class RestoreRootTask extends PairedTask<BaseActivity, Void, RootInfo> {
private static final String TAG = "RestoreRootTask";
private final Uri mRootUri;
public RestoreRootTask(BaseActivity activity, Uri rootUri) {
super(activity);
mRootUri = rootUri;
}
@Override
protected RootInfo run(Void... params) {
String rootId = DocumentsContract.getRootId(mRootUri);
return mOwner.mRoots.getRootOneshot(mRootUri.getAuthority(), rootId);
}
@Override
protected void finish(RootInfo root) {
mOwner.mState.restored = true;
if (root != null) {
mOwner.onRootPicked(root);
} else {
Log.w(TAG, "Failed to find root: " + mRootUri);
mOwner.finish();
}
}
}