Merge "Freshen restored stacks, more UX bug fixes." into klp-dev

This commit is contained in:
Jeff Sharkey
2013-09-24 20:34:39 +00:00
committed by Android (Google) Code Review
12 changed files with 199 additions and 61 deletions

View File

@@ -16,18 +16,16 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="false" android:state_selected="true" android:drawable="@*android:drawable/list_selector_disabled_holo_light" />
<item android:state_enabled="false" android:state_focused="true" android:drawable="@*android:drawable/list_selector_disabled_holo_light" />
<item android:state_enabled="false" android:state_pressed="true" android:drawable="@*android:drawable/list_selector_disabled_holo_light" />
<item android:state_window_focused="false" android:drawable="@android:color/transparent" />
<item android:state_activated="true" android:state_pressed="true" android:drawable="@*android:drawable/list_activated_holo" />
<item android:state_activated="true" android:drawable="@*android:drawable/list_activated_holo" />
<item android:state_focused="true" android:state_activated="true" android:drawable="@*android:drawable/list_selected_holo_light" />
<item android:state_focused="false" android:state_activated="true" android:drawable="@*android:drawable/list_selected_holo_light" />
<item android:state_focused="true" android:drawable="@*android:drawable/list_focused_holo" />
<item android:state_selected="true" android:drawable="@*android:drawable/list_focused_holo" />
<item android:state_pressed="true" android:state_focused="true" android:drawable="@*android:drawable/list_selector_background_transition_holo_light" />
<item android:state_pressed="true" android:drawable="@*android:drawable/list_selector_background_transition_holo_light" />
<item android:state_focused="true" android:state_enabled="false" android:state_pressed="true" android:drawable="@*android:drawable/list_selector_disabled_holo_light" />
<item android:state_focused="true" android:state_enabled="false" android:drawable="@*android:drawable/list_selector_disabled_holo_light" />
<item android:state_focused="true" android:state_pressed="true" android:drawable="@*android:drawable/list_selector_background_transition_holo_light" />
<item android:state_focused="false" android:state_pressed="true" android:drawable="@*android:drawable/list_selector_background_transition_holo_light" />
<item android:state_focused="true" android:drawable="@*android:drawable/list_focused_holo" />
<item android:drawable="@android:color/transparent" />

View File

@@ -42,6 +42,7 @@
android:paddingEnd="@dimen/grid_padding_horiz"
android:paddingTop="@dimen/grid_padding"
android:paddingBottom="@dimen/grid_padding"
android:clipToPadding="false"
android:scrollbarStyle="outsideOverlay"
android:listSelector="@android:color/transparent"
android:visibility="gone" />

View File

@@ -17,8 +17,8 @@
<resources>
<dimen name="icon_size">32dp</dimen>
<dimen name="root_icon_size">24dp</dimen>
<dimen name="grid_width">180dp</dimen>
<dimen name="grid_height">180dp</dimen>
<dimen name="grid_width">160dp</dimen>
<dimen name="grid_height">170dp</dimen>
<dimen name="grid_padding">4dp</dimen>
<dimen name="grid_padding_horiz">4dp</dimen>

View File

@@ -62,6 +62,8 @@
<!-- Toast shown when saving a document failed with an error [CHAR LIMIT=48] -->
<string name="save_error">Failed to save document</string>
<!-- Toast shown when creating a folder failed with an error [CHAR LIMIT=48] -->
<string name="create_error">Failed to create folder</string>
<!-- Title of storage root location that contains recently modified or used documents [CHAR LIMIT=24] -->
<string name="root_recent">Recent</string>

View File

@@ -76,7 +76,7 @@ public class CreateDirectoryFragment extends DialogFragment {
final DocumentInfo childDoc = DocumentInfo.fromUri(resolver, childUri);
activity.onDocumentPicked(childDoc);
} catch (Exception e) {
Toast.makeText(context, R.string.save_error, Toast.LENGTH_SHORT).show();
Toast.makeText(context, R.string.create_error, Toast.LENGTH_SHORT).show();
}
}
});

View File

@@ -715,8 +715,16 @@ public class DirectoryFragment extends Fragment {
final FrameLayout grid = (FrameLayout) convertView;
final int gridPadding = getResources()
.getDimensionPixelSize(R.dimen.grid_padding);
grid.setForeground(new InsetDrawable(grid.getForeground(), gridPadding));
grid.setBackground(new InsetDrawable(grid.getBackground(), gridPadding));
// Tricksy hobbitses! We need to fully clear the drawable so
// the view doesn't clobber the new InsetDrawable callback
// when setting back later.
final Drawable fg = grid.getForeground();
final Drawable bg = grid.getBackground();
grid.setForeground(null);
grid.setBackground(null);
grid.setForeground(new InsetDrawable(fg, gridPadding));
grid.setBackground(new InsetDrawable(bg, gridPadding));
} else {
throw new IllegalStateException();
}

View File

@@ -285,6 +285,7 @@ public class DocumentsActivity extends Activity {
private class RestoreStackTask extends AsyncTask<Void, Void, Void> {
private volatile boolean mRestoredStack;
private volatile boolean mExternal;
@Override
protected Void doInBackground(Void... params) {
@@ -298,6 +299,7 @@ public class DocumentsActivity extends Activity {
cursor.getColumnIndex(ResumeColumns.STACK));
DurableUtils.readFromArray(rawStack, mState.stack);
mRestoredStack = true;
mExternal = cursor.getInt(cursor.getColumnIndex(ResumeColumns.EXTERNAL)) != 0;
}
} catch (IOException e) {
Log.w(TAG, "Failed to resume", e);
@@ -305,12 +307,17 @@ public class DocumentsActivity extends Activity {
IoUtils.closeQuietly(cursor);
}
// If restored root isn't valid, fall back to recents
final RootInfo root = getCurrentRoot();
final Collection<RootInfo> matchingRoots = mRoots.getMatchingRootsBlocking(mState);
if (!matchingRoots.contains(root)) {
mState.stack.reset();
mRestoredStack = false;
if (mRestoredStack) {
// Update the restored stack to ensure we have freshest data
final Collection<RootInfo> matchingRoots = mRoots.getMatchingRootsBlocking(mState);
try {
mState.stack.updateRoot(matchingRoots);
mState.stack.updateDocuments(getContentResolver());
} catch (FileNotFoundException e) {
Log.w(TAG, "Failed to restore stack: " + e);
mState.stack.reset();
mRestoredStack = false;
}
}
return null;
@@ -321,10 +328,22 @@ public class DocumentsActivity extends Activity {
if (isDestroyed()) return;
mState.restored = true;
// Only open drawer when not restoring stack, and when not showing
// visual content.
if (!mRestoredStack
&& !MimePredicate.mimeMatches(MimePredicate.VISUAL_MIMES, mState.acceptMimes)) {
// Show drawer when no stack restored, but only when requesting
// non-visual content. However, if we last used an external app,
// drawer is always shown.
boolean showDrawer = false;
if (!mRestoredStack) {
showDrawer = true;
}
if (MimePredicate.mimeMatches(MimePredicate.VISUAL_MIMES, mState.acceptMimes)) {
showDrawer = false;
}
if (mExternal && mState.action == ACTION_GET_CONTENT) {
showDrawer = true;
}
if (showDrawer) {
setRootsDrawerOpen(true);
}
@@ -340,6 +359,7 @@ public class DocumentsActivity extends Activity {
mState.showSize = true;
} else {
mState.showSize = SettingsActivity.getDisplayFileSize(this);
invalidateOptionsMenu();
}
}
@@ -779,10 +799,10 @@ public class DocumentsActivity extends Activity {
} else {
DirectoryFragment.showRecentsOpen(fm, anim);
// Start recents in relevant mode
final boolean acceptImages = MimePredicate.mimeMatches(
"image/*", mState.acceptMimes);
mState.userMode = acceptImages ? MODE_GRID : MODE_LIST;
// Start recents in grid when requesting visual things
final boolean visualMimes = MimePredicate.mimeMatches(
MimePredicate.VISUAL_MIMES, mState.acceptMimes);
mState.userMode = visualMimes ? MODE_GRID : MODE_LIST;
mState.derivedMode = mState.userMode;
}
} else {
@@ -814,9 +834,17 @@ public class DocumentsActivity extends Activity {
}
public void onStackPicked(DocumentStack stack) {
mState.stack = stack;
mState.stackTouched = true;
onCurrentDirectoryChanged(ANIM_SIDE);
try {
// Update the restored stack to ensure we have freshest data
stack.updateDocuments(getContentResolver());
mState.stack = stack;
mState.stackTouched = true;
onCurrentDirectoryChanged(ANIM_SIDE);
} catch (FileNotFoundException e) {
Log.w(TAG, "Failed to restore stack: " + e);
}
}
public void onRootPicked(RootInfo root, boolean closeDrawer) {
@@ -858,6 +886,14 @@ public class DocumentsActivity extends Activity {
// Only relay back results when not canceled; otherwise stick around to
// let the user pick another app/backend.
if (requestCode == CODE_FORWARD && resultCode != RESULT_CANCELED) {
// Remember that we last picked via external app
final String packageName = getCallingPackage();
final ContentValues values = new ContentValues();
values.put(ResumeColumns.EXTERNAL, 1);
getContentResolver().insert(RecentsProvider.buildResume(packageName), values);
// Pass back result to original caller
setResult(resultCode, data);
finish();
} else {
@@ -945,6 +981,7 @@ public class DocumentsActivity extends Activity {
final String packageName = getCallingPackage();
values.clear();
values.put(ResumeColumns.STACK, rawStack);
values.put(ResumeColumns.EXTERNAL, 0);
resolver.insert(RecentsProvider.buildResume(packageName), values);
final Intent intent = new Intent();

View File

@@ -45,8 +45,10 @@ import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import com.android.documentsui.DocumentsActivity.State;
import com.android.documentsui.RecentsProvider.RecentColumns;
import com.android.documentsui.model.DocumentStack;
import com.android.documentsui.model.RootInfo;
import com.google.android.collect.Lists;
import libcore.io.IoUtils;
@@ -55,6 +57,7 @@ import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
@@ -89,10 +92,13 @@ public class RecentsCreateFragment extends Fragment {
mAdapter = new DocumentStackAdapter();
mListView.setAdapter(mAdapter);
final RootsCache roots = DocumentsApplication.getRootsCache(context);
final State state = ((DocumentsActivity) getActivity()).getDisplayState();
mCallbacks = new LoaderCallbacks<List<DocumentStack>>() {
@Override
public Loader<List<DocumentStack>> onCreateLoader(int id, Bundle args) {
return new RecentsCreateLoader(context);
return new RecentsCreateLoader(context, roots, state);
}
@Override
@@ -131,12 +137,18 @@ public class RecentsCreateFragment extends Fragment {
};
public static class RecentsCreateLoader extends UriDerivativeLoader<Uri, List<DocumentStack>> {
public RecentsCreateLoader(Context context) {
private final RootsCache mRoots;
private final State mState;
public RecentsCreateLoader(Context context, RootsCache roots, State state) {
super(context, RecentsProvider.buildRecent());
mRoots = roots;
mState = state;
}
@Override
public List<DocumentStack> loadInBackground(Uri uri, CancellationSignal signal) {
final Collection<RootInfo> matchingRoots = mRoots.getMatchingRootsBlocking(mState);
final ArrayList<DocumentStack> result = Lists.newArrayList();
final ContentResolver resolver = getContext().getContentResolver();
@@ -149,6 +161,12 @@ public class RecentsCreateFragment extends Fragment {
try {
final DocumentStack stack = new DocumentStack();
stack.read(new DataInputStream(new ByteArrayInputStream(rawStack)));
// Only update root here to avoid spinning up all
// providers; we update the stack during the actual
// restore. This also filters away roots that don't
// match current filter.
stack.updateRoot(matchingRoots);
result.add(stack);
} catch (IOException e) {
Log.w(TAG, "Failed to resolve stack: " + e);

View File

@@ -72,6 +72,7 @@ public class RecentsProvider extends ContentProvider {
public static final String PACKAGE_NAME = "package_name";
public static final String STACK = "stack";
public static final String TIMESTAMP = "timestamp";
public static final String EXTERNAL = "external";
}
public static Uri buildRecent() {
@@ -97,9 +98,10 @@ public class RecentsProvider extends ContentProvider {
private static final int VERSION_INIT = 1;
private static final int VERSION_AS_BLOB = 3;
private static final int VERSION_ADD_EXTERNAL = 4;
public DatabaseHelper(Context context) {
super(context, DB_NAME, null, VERSION_AS_BLOB);
super(context, DB_NAME, null, VERSION_ADD_EXTERNAL);
}
@Override
@@ -121,9 +123,10 @@ public class RecentsProvider extends ContentProvider {
")");
db.execSQL("CREATE TABLE " + TABLE_RESUME + " (" +
ResumeColumns.PACKAGE_NAME + " TEXT PRIMARY KEY ON CONFLICT REPLACE," +
ResumeColumns.STACK + " BLOB," +
ResumeColumns.TIMESTAMP + " INTEGER" +
ResumeColumns.PACKAGE_NAME + " TEXT NOT NULL PRIMARY KEY," +
ResumeColumns.STACK + " BLOB DEFAULT NULL," +
ResumeColumns.TIMESTAMP + " INTEGER," +
ResumeColumns.EXTERNAL + " INTEGER NOT NULL DEFAULT 0" +
")");
}
@@ -176,6 +179,7 @@ public class RecentsProvider extends ContentProvider {
@Override
public Uri insert(Uri uri, ContentValues values) {
final SQLiteDatabase db = mHelper.getWritableDatabase();
final ContentValues key = new ContentValues();
switch (sMatcher.match(uri)) {
case URI_RECENT:
values.put(RecentColumns.TIMESTAMP, System.currentTimeMillis());
@@ -188,7 +192,6 @@ public class RecentsProvider extends ContentProvider {
final String rootId = uri.getPathSegments().get(2);
final String documentId = uri.getPathSegments().get(3);
final ContentValues key = new ContentValues();
key.put(StateColumns.AUTHORITY, authority);
key.put(StateColumns.ROOT_ID, rootId);
key.put(StateColumns.DOCUMENT_ID, documentId);
@@ -201,10 +204,15 @@ public class RecentsProvider extends ContentProvider {
return uri;
case URI_RESUME:
final String packageName = uri.getPathSegments().get(1);
values.put(ResumeColumns.PACKAGE_NAME, packageName);
values.put(ResumeColumns.TIMESTAMP, System.currentTimeMillis());
db.insert(TABLE_RESUME, null, values);
final String packageName = uri.getPathSegments().get(1);
key.put(ResumeColumns.PACKAGE_NAME, packageName);
// Ensure that row exists, then update with changed values
db.insertWithOnConflict(TABLE_RESUME, null, key, SQLiteDatabase.CONFLICT_IGNORE);
db.update(TABLE_RESUME, values, ResumeColumns.PACKAGE_NAME + "=?",
new String[] { packageName });
return uri;
default:
throw new UnsupportedOperationException("Unsupported Uri " + uri);

View File

@@ -27,6 +27,7 @@ import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import android.widget.TextView;
import libcore.io.IoUtils;
@@ -52,6 +53,9 @@ public class TestActivity extends Activity {
final LinearLayout view = new LinearLayout(context);
view.setOrientation(LinearLayout.VERTICAL);
mResult = new TextView(context);
view.addView(mResult);
final CheckBox multiple = new CheckBox(context);
multiple.setText("ALLOW_MULTIPLE");
view.addView(multiple);
@@ -155,6 +159,23 @@ public class TestActivity extends Activity {
});
view.addView(button);
button = new Button(context);
button.setText("CREATE_DOC image/png");
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("image/png");
intent.putExtra(Intent.EXTRA_TITLE, "mypicture.png");
if (localOnly.isChecked()) {
intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
}
startActivityForResult(intent, CODE_WRITE);
}
});
view.addView(button);
button = new Button(context);
button.setText("GET_CONTENT */*");
button.setOnClickListener(new OnClickListener() {
@@ -174,10 +195,10 @@ public class TestActivity extends Activity {
});
view.addView(button);
mResult = new TextView(context);
view.addView(mResult);
final ScrollView scroll = new ScrollView(context);
scroll.addView(view);
setContentView(view);
setContentView(scroll);
}
@Override

View File

@@ -23,6 +23,7 @@ import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
import android.provider.DocumentsContract;
import android.provider.DocumentsProvider;
import android.provider.DocumentsContract.Document;
import com.android.documentsui.RootCursorWrapper;
@@ -141,23 +142,42 @@ public class DocumentInfo implements Durable, Parcelable {
}
public static DocumentInfo fromCursor(Cursor cursor, String authority) {
final DocumentInfo doc = new DocumentInfo();
doc.authority = authority;
doc.documentId = getCursorString(cursor, Document.COLUMN_DOCUMENT_ID);
doc.mimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
doc.documentId = getCursorString(cursor, Document.COLUMN_DOCUMENT_ID);
doc.mimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
doc.displayName = getCursorString(cursor, Document.COLUMN_DISPLAY_NAME);
doc.lastModified = getCursorLong(cursor, Document.COLUMN_LAST_MODIFIED);
doc.flags = getCursorInt(cursor, Document.COLUMN_FLAGS);
doc.summary = getCursorString(cursor, Document.COLUMN_SUMMARY);
doc.size = getCursorLong(cursor, Document.COLUMN_SIZE);
doc.icon = getCursorInt(cursor, Document.COLUMN_ICON);
doc.deriveFields();
return doc;
final DocumentInfo info = new DocumentInfo();
info.updateFromCursor(cursor, authority);
return info;
}
public static DocumentInfo fromUri(ContentResolver resolver, Uri uri) throws FileNotFoundException {
public void updateFromCursor(Cursor cursor, String authority) {
this.authority = authority;
this.documentId = getCursorString(cursor, Document.COLUMN_DOCUMENT_ID);
this.mimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
this.documentId = getCursorString(cursor, Document.COLUMN_DOCUMENT_ID);
this.mimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
this.displayName = getCursorString(cursor, Document.COLUMN_DISPLAY_NAME);
this.lastModified = getCursorLong(cursor, Document.COLUMN_LAST_MODIFIED);
this.flags = getCursorInt(cursor, Document.COLUMN_FLAGS);
this.summary = getCursorString(cursor, Document.COLUMN_SUMMARY);
this.size = getCursorLong(cursor, Document.COLUMN_SIZE);
this.icon = getCursorInt(cursor, Document.COLUMN_ICON);
this.deriveFields();
}
public static DocumentInfo fromUri(ContentResolver resolver, Uri uri)
throws FileNotFoundException {
final DocumentInfo info = new DocumentInfo();
info.updateFromUri(resolver, uri);
return info;
}
/**
* Update a possibly stale restored document against a live
* {@link DocumentsProvider}.
*/
public void updateSelf(ContentResolver resolver) throws FileNotFoundException {
updateFromUri(resolver, derivedUri);
}
public void updateFromUri(ContentResolver resolver, Uri uri) throws FileNotFoundException {
final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
uri.getAuthority());
Cursor cursor = null;
@@ -166,7 +186,7 @@ public class DocumentInfo implements Durable, Parcelable {
if (!cursor.moveToFirst()) {
throw new FileNotFoundException("Missing details for " + uri);
}
return fromCursor(cursor, uri.getAuthority());
updateFromCursor(cursor, uri.getAuthority());
} catch (Throwable t) {
throw asFileNotFoundException(t);
} finally {

View File

@@ -16,10 +16,15 @@
package com.android.documentsui.model;
import android.content.ContentResolver;
import android.provider.DocumentsProvider;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.ProtocolException;
import java.util.Collection;
import java.util.LinkedList;
/**
@@ -46,6 +51,26 @@ public class DocumentStack extends LinkedList<DocumentInfo> implements Durable {
return size() == 0;
}
public void updateRoot(Collection<RootInfo> matchingRoots) throws FileNotFoundException {
for (RootInfo root : matchingRoots) {
if (root.equals(this.root)) {
this.root = root;
return;
}
}
throw new FileNotFoundException("Failed to find matching root for " + root);
}
/**
* Update a possibly stale restored stack against a live
* {@link DocumentsProvider}.
*/
public void updateDocuments(ContentResolver resolver) throws FileNotFoundException {
for (DocumentInfo info : this) {
info.updateSelf(resolver);
}
}
@Override
public void reset() {
clear();