Merge "Disabled states, more UX work, bug fixes." into klp-dev
This commit is contained in:
@@ -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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user