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

Also, reveal the illusion that we're restoring state in Files and Downloads.
Also, define a "PairedTask" class that guards calls to task methods
    with checks against isDestroyed. This also let us make all of the tasks
    static, so we get much narrower scope and tasks can even be moved to
    their own files.

Change-Id: I6a9e8706e1ab1d1f43301e73dd9858a115a6baaf
This commit is contained in:
Steve McKay
2016-02-04 12:15:22 -08:00
parent 3fd03b718d
commit 95cd85ad38
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;
@@ -87,8 +81,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;
@@ -103,9 +97,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);
@@ -114,7 +106,8 @@ public abstract class BaseActivity extends Activity
new RootsCache.OnCacheUpdateListener() {
@Override
public void onCacheUpdate() {
new HandleRootsChangedTask().execute(getCurrentRoot());
new HandleRootsChangedTask(BaseActivity.this)
.execute(getCurrentRoot());
}
});
@@ -184,7 +177,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();
@@ -224,7 +230,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());
}
}
@@ -574,115 +580,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()) {
@@ -694,17 +625,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();
}
}
}