Merge "Disabled states, more UX work, bug fixes." into klp-dev

This commit is contained in:
Jeff Sharkey
2013-09-10 00:08:17 +00:00
committed by Android (Google) Code Review
7 changed files with 141 additions and 46 deletions

View File

@@ -344,7 +344,10 @@ public class ContentProviderClient {
/** {@hide} */
public static void closeQuietly(ContentProviderClient client) {
if (client != null) {
client.release();
try {
client.release();
} catch (Exception ignored) {
}
}
}
}

View File

@@ -15,12 +15,20 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<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_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" />
<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_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: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:drawable="@android:color/transparent" />
</selector>

View File

@@ -106,6 +106,11 @@ public class DirectoryFragment extends Fragment {
private static final String EXTRA_DOC = "doc";
private static final String EXTRA_QUERY = "query";
/**
* MIME types that should always show thumbnails in list mode.
*/
private static final String[] LIST_THUMBNAIL_MIMES = new String[] { "image/*", "video/*" };
private static AtomicInteger sLoaderId = new AtomicInteger(4000);
private final int mLoaderId = sLoaderId.incrementAndGet();
@@ -294,9 +299,11 @@ public class DirectoryFragment extends Fragment {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
final Cursor cursor = mAdapter.getItem(position);
final DocumentInfo doc = DocumentInfo.fromDirectoryCursor(cursor);
if (mFilter.apply(doc)) {
((DocumentsActivity) getActivity()).onDocumentPicked(doc);
if (cursor != null) {
final DocumentInfo doc = DocumentInfo.fromDirectoryCursor(cursor);
if (mFilter.apply(doc)) {
((DocumentsActivity) getActivity()).onDocumentPicked(doc);
}
}
}
};
@@ -367,10 +374,20 @@ public class DirectoryFragment extends Fragment {
public void onItemCheckedStateChanged(
ActionMode mode, int position, long id, boolean checked) {
if (checked) {
// Directories cannot be checked
// Directories and footer items cannot be checked
boolean valid = false;
final Cursor cursor = mAdapter.getItem(position);
final String docMimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
if (Document.MIME_TYPE_DIR.equals(docMimeType)) {
if (cursor != null) {
final String docMimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
// Only valid if non-directory matches filter
final State state = getDisplayState(DirectoryFragment.this);
valid = !Document.MIME_TYPE_DIR.equals(docMimeType)
&& MimePredicate.mimeMatches(state.acceptMimes, docMimeType);
}
if (!valid) {
mCurrentView.setItemChecked(position, false);
}
}
@@ -441,11 +458,25 @@ public class DirectoryFragment extends Fragment {
return ((DocumentsActivity) fragment.getActivity()).getDisplayState();
}
private interface Footer {
public View getView(View convertView, ViewGroup parent);
private static abstract class Footer {
private final int mItemViewType;
public Footer(int itemViewType) {
mItemViewType = itemViewType;
}
public abstract View getView(View convertView, ViewGroup parent);
public int getItemViewType() {
return mItemViewType;
}
}
private static class LoadingFooter implements Footer {
private static class LoadingFooter extends Footer {
public LoadingFooter() {
super(1);
}
@Override
public View getView(View convertView, ViewGroup parent) {
final Context context = parent.getContext();
@@ -457,11 +488,12 @@ public class DirectoryFragment extends Fragment {
}
}
private class MessageFooter implements Footer {
private class MessageFooter extends Footer {
private final int mIcon;
private final String mMessage;
public MessageFooter(int icon, String message) {
public MessageFooter(int itemViewType, int icon, String message) {
super(itemViewType);
mIcon = icon;
mMessage = message;
}
@@ -506,11 +538,11 @@ public class DirectoryFragment extends Fragment {
if (extras != null) {
final String info = extras.getString(DocumentsContract.EXTRA_INFO);
if (info != null) {
mFooters.add(new MessageFooter(R.drawable.ic_dialog_alert, info));
mFooters.add(new MessageFooter(2, R.drawable.ic_dialog_alert, info));
}
final String error = extras.getString(DocumentsContract.EXTRA_ERROR);
if (error != null) {
mFooters.add(new MessageFooter(R.drawable.ic_dialog_alert, error));
mFooters.add(new MessageFooter(3, R.drawable.ic_dialog_alert, error));
}
if (extras.getBoolean(DocumentsContract.EXTRA_LOADING, false)) {
mFooters.add(new LoadingFooter());
@@ -532,7 +564,11 @@ public class DirectoryFragment extends Fragment {
return getDocumentView(position, convertView, parent);
} else {
position -= mCursorCount;
return mFooters.get(position).getView(convertView, parent);
convertView = mFooters.get(position).getView(convertView, parent);
// Only the view itself is disabled; contents inside shouldn't
// be dimmed.
convertView.setEnabled(false);
return convertView;
}
}
@@ -581,7 +617,11 @@ public class DirectoryFragment extends Fragment {
oldTask.cancel(false);
}
if ((docFlags & Document.FLAG_SUPPORTS_THUMBNAIL) != 0) {
final boolean supportsThumbnail = (docFlags & Document.FLAG_SUPPORTS_THUMBNAIL) != 0;
final boolean allowThumbnail = (state.mode == MODE_GRID)
|| MimePredicate.mimeMatches(LIST_THUMBNAIL_MIMES, docMimeType);
if (supportsThumbnail && allowThumbnail) {
final Uri uri = DocumentsContract.buildDocumentUri(docAuthority, docId);
final Bitmap cachedResult = thumbs.get(uri);
if (cachedResult != null) {
@@ -590,7 +630,7 @@ public class DirectoryFragment extends Fragment {
final ThumbnailAsyncTask task = new ThumbnailAsyncTask(icon, mThumbSize);
icon.setImageBitmap(null);
icon.setTag(task);
task.execute(uri);
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, uri);
}
} else if (docIcon != 0) {
icon.setImageDrawable(IconUtils.loadPackageIcon(context, docAuthority, docIcon));
@@ -642,6 +682,18 @@ public class DirectoryFragment extends Fragment {
line2.setVisibility(hasLine2 ? View.VISIBLE : View.GONE);
final boolean enabled = Document.MIME_TYPE_DIR.equals(docMimeType)
|| MimePredicate.mimeMatches(state.acceptMimes, docMimeType);
if (enabled) {
setEnabledRecursive(convertView, true);
icon.setAlpha(1f);
icon1.setAlpha(1f);
} else {
setEnabledRecursive(convertView, false);
icon.setAlpha(0.5f);
icon1.setAlpha(0.5f);
}
return convertView;
}
@@ -665,24 +717,20 @@ public class DirectoryFragment extends Fragment {
return position;
}
@Override
public int getViewTypeCount() {
return 4;
}
@Override
public int getItemViewType(int position) {
if (position < mCursorCount) {
return 0;
} else {
return IGNORE_ITEM_VIEW_TYPE;
position -= mCursorCount;
return mFooters.get(position).getItemViewType();
}
}
@Override
public boolean areAllItemsEnabled() {
return false;
}
@Override
public boolean isEnabled(int position) {
return position < mCursorCount;
}
}
private static class ThumbnailAsyncTask extends AsyncTask<Uri, Void, Bitmap> {
@@ -772,4 +820,16 @@ public class DirectoryFragment extends Fragment {
return commonType[0] + "/" + commonType[1];
}
private void setEnabledRecursive(View v, boolean enabled) {
if (v.isEnabled() == enabled) return;
v.setEnabled(enabled);
if (v instanceof ViewGroup) {
final ViewGroup vg = (ViewGroup) v;
for (int i = vg.getChildCount() - 1; i >= 0; i--) {
setEnabledRecursive(vg.getChildAt(i), enabled);
}
}
}
}

View File

@@ -26,10 +26,14 @@ import android.content.ContentResolver;
import android.content.Context;
import android.content.Loader;
import android.database.Cursor;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.TextUtils.TruncateAt;
import android.text.style.ImageSpan;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
@@ -181,20 +185,29 @@ public class RecentsCreateFragment extends Fragment {
final ImageView icon = (ImageView) convertView.findViewById(android.R.id.icon);
final TextView title = (TextView) convertView.findViewById(android.R.id.title);
final View line2 = convertView.findViewById(R.id.line2);
final DocumentStack stack = getItem(position);
icon.setImageDrawable(stack.root.loadIcon(context));
final StringBuilder builder = new StringBuilder();
for (int i = stack.size() - 1; i >= 0; i--) {
final Drawable crumb = context.getResources()
.getDrawable(R.drawable.ic_breadcrumb_arrow);
crumb.setBounds(0, 0, crumb.getIntrinsicWidth(), crumb.getIntrinsicHeight());
final SpannableStringBuilder builder = new SpannableStringBuilder();
builder.append(stack.root.title);
appendDrawable(builder, crumb);
for (int i = stack.size() - 2; i >= 0; i--) {
builder.append(stack.get(i).displayName);
if (i > 0) {
builder.append(" \u232a ");
appendDrawable(builder, crumb);
}
}
title.setText(builder.toString());
title.setText(builder);
title.setEllipsize(TruncateAt.MIDDLE);
line2.setVisibility(View.GONE);
return convertView;
}
@@ -213,4 +226,10 @@ public class RecentsCreateFragment extends Fragment {
return getItem(position).hashCode();
}
}
private static void appendDrawable(SpannableStringBuilder b, Drawable d) {
final int length = b.length();
b.append("\u232a");
b.setSpan(new ImageSpan(d), length, b.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}

View File

@@ -149,9 +149,9 @@ public class RecentsProvider extends ContentProvider {
final SQLiteDatabase db = mHelper.getReadableDatabase();
switch (sMatcher.match(uri)) {
case URI_RECENT:
return db.query(TABLE_RECENT, projection,
RecentColumns.TIMESTAMP + "<" + MAX_HISTORY_IN_MILLIS, null, null, null,
null);
final long cutoff = System.currentTimeMillis() - MAX_HISTORY_IN_MILLIS;
return db.query(TABLE_RECENT, projection, RecentColumns.TIMESTAMP + ">" + cutoff,
null, null, null, null);
case URI_STATE:
final String authority = uri.getPathSegments().get(1);
final String rootId = uri.getPathSegments().get(2);
@@ -180,8 +180,8 @@ public class RecentsProvider extends ContentProvider {
case URI_RECENT:
values.put(RecentColumns.TIMESTAMP, System.currentTimeMillis());
db.insert(TABLE_RECENT, null, values);
db.delete(
TABLE_RECENT, RecentColumns.TIMESTAMP + ">" + MAX_HISTORY_IN_MILLIS, null);
final long cutoff = System.currentTimeMillis() - MAX_HISTORY_IN_MILLIS;
db.delete(TABLE_RECENT, RecentColumns.TIMESTAMP + "<" + cutoff, null);
return uri;
case URI_STATE:
final String authority = uri.getPathSegments().get(1);

View File

@@ -30,7 +30,7 @@ public class RootsCacheTest extends AndroidTestCase {
private static RootInfo buildForMimeTypes(String... mimeTypes) {
final RootInfo root = new RootInfo();
root.mimeTypes = mimeTypes;
root.derivedMimeTypes = mimeTypes;
return root;
}

View File

@@ -125,6 +125,9 @@ public class TestDocumentsProvider extends DocumentsProvider {
includeFile(result, "_networkfile1");
includeFile(result, "_networkfile2");
includeFile(result, "_networkfile3");
includeFile(result, "_networkfile4");
includeFile(result, "_networkfile5");
includeFile(result, "_networkfile6");
return true;
} else {
return false;
@@ -162,6 +165,8 @@ public class TestDocumentsProvider extends DocumentsProvider {
includeFile(result, MY_DOC_NULL);
includeFile(result, "localfile1");
includeFile(result, "localfile2");
includeFile(result, "localfile3");
includeFile(result, "localfile4");
synchronized (this) {
// Try picking up an existing network fetch