Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3f4c205f authored by Jeff Sharkey's avatar Jeff Sharkey
Browse files

Disabled states, more UX work, bug fixes.

Fix drawable state to correctly show dimmed disabled state.  Update
disabled state for all children to grey out text.

Block multi-selection of documents not matching MIME filter.  Load
thumbnails in parallel.  Show thumbnails in list mode based on MIME
type to match spec.

Give each footer a unique view type to avoid recycler crashes.

Show breadcrumb icons in recent create paths.  Fix timestamp bug when
querying/updating recent paths.

Make ContentProviderClient.closeQuietly() really be quiet.

Bug: 10668364, 10510022, 10668701, 10534224, 10667726
Change-Id: I3c705412fb211519f15ad41a273a7533b878e9e5
parent d182bb64
Loading
Loading
Loading
Loading
+4 −1
Original line number Diff line number Diff line
@@ -344,7 +344,10 @@ public class ContentProviderClient {
    /** {@hide} */
    public static void closeQuietly(ContentProviderClient client) {
        if (client != null) {
            try {
                client.release();
            } catch (Exception ignored) {
            }
        }
    }
}
+16 −8
Original line number Diff line number Diff line
@@ -15,12 +15,20 @@
-->

<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_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: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>
+87 −27
Original line number Diff line number Diff line
@@ -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,11 +299,13 @@ public class DirectoryFragment extends Fragment {
        @Override
        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            final Cursor cursor = mAdapter.getItem(position);
            if (cursor != null) {
                final DocumentInfo doc = DocumentInfo.fromDirectoryCursor(cursor);
                if (mFilter.apply(doc)) {
                    ((DocumentsActivity) getActivity()).onDocumentPicked(doc);
                }
            }
        }
    };

    private MultiChoiceModeListener mMultiListener = new MultiChoiceModeListener() {
@@ -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);
                if (cursor != null) {
                    final String docMimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
                if (Document.MIME_TYPE_DIR.equals(docMimeType)) {

                    // 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 extends Footer {
        public LoadingFooter() {
            super(1);
        }

    private static class LoadingFooter implements Footer {
        @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,23 +717,19 @@ 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;
            }
        }

        @Override
        public boolean areAllItemsEnabled() {
            return false;
                position -= mCursorCount;
                return mFooters.get(position).getItemViewType();
            }

        @Override
        public boolean isEnabled(int position) {
            return position < mCursorCount;
        }
    }

@@ -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);
            }
        }
    }
}
+23 −4
Original line number Diff line number Diff line
@@ -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);
    }
}
+5 −5
Original line number Diff line number Diff line
@@ -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);
Loading