More UX work for thumbnails, search, management.
Hide non-finished downloads from normal picker UI, but keep them around in management mode. Uses a Uri query parameter and a hidden API on DocumentsProvider. Scale thumbnails to fit viewport, always show MIME icon while waiting on thumbnails, and crossfade between them. Cancel thumbnail tasks when views are recycled. Filter directories out of search results for now. Also leave sort ordering from backend intact, since it's custom ranking. Fix SearchView interaction to dismiss properly and restore across orientation and drawer state changes. Hide most actions when drawer is open. Invalidate RootInfo cache when locale changes. Apply sort ordering when showing recent create directories. Hide recent summary string when icon is enough for user to disambiguate. Bug: 10667184, 10665663 Change-Id: I331d3272a08c497f88dc659d9e112231cb35aa69
This commit is contained in:
@@ -459,6 +459,7 @@ public final class DocumentsContract {
|
||||
private static final String PATH_SEARCH = "search";
|
||||
|
||||
private static final String PARAM_QUERY = "query";
|
||||
private static final String PARAM_MANAGE = "manage";
|
||||
|
||||
/**
|
||||
* Build Uri representing the roots of a document provider. When queried, a
|
||||
@@ -583,6 +584,16 @@ public final class DocumentsContract {
|
||||
return searchDocumentsUri.getQueryParameter(PARAM_QUERY);
|
||||
}
|
||||
|
||||
/** {@hide} */
|
||||
public static Uri setManageMode(Uri uri) {
|
||||
return uri.buildUpon().appendQueryParameter(PARAM_MANAGE, "true").build();
|
||||
}
|
||||
|
||||
/** {@hide} */
|
||||
public static boolean isManageMode(Uri uri) {
|
||||
return uri.getBooleanQueryParameter(PARAM_MANAGE, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return list of all documents that the calling package has "open." These
|
||||
* are Uris matching {@link DocumentsContract} to which persistent
|
||||
|
||||
@@ -167,6 +167,14 @@ public abstract class DocumentsProvider extends ContentProvider {
|
||||
String parentDocumentId, String[] projection, String sortOrder)
|
||||
throws FileNotFoundException;
|
||||
|
||||
/** {@hide} */
|
||||
@SuppressWarnings("unused")
|
||||
public Cursor queryChildDocumentsForManage(
|
||||
String parentDocumentId, String[] projection, String sortOrder)
|
||||
throws FileNotFoundException {
|
||||
throw new UnsupportedOperationException("Manage not supported");
|
||||
}
|
||||
|
||||
/**
|
||||
* Return documents that that match the given query, starting the search at
|
||||
* the given directory.
|
||||
@@ -262,7 +270,12 @@ public abstract class DocumentsProvider extends ContentProvider {
|
||||
case MATCH_DOCUMENT:
|
||||
return queryDocument(getDocumentId(uri), projection);
|
||||
case MATCH_CHILDREN:
|
||||
return queryChildDocuments(getDocumentId(uri), projection, sortOrder);
|
||||
if (DocumentsContract.isManageMode(uri)) {
|
||||
return queryChildDocumentsForManage(
|
||||
getDocumentId(uri), projection, sortOrder);
|
||||
} else {
|
||||
return queryChildDocuments(getDocumentId(uri), projection, sortOrder);
|
||||
}
|
||||
case MATCH_SEARCH:
|
||||
return querySearchDocuments(
|
||||
getDocumentId(uri), getSearchDocumentsQuery(uri), projection);
|
||||
|
||||
@@ -32,12 +32,26 @@
|
||||
android:layout_weight="1"
|
||||
android:background="#fff">
|
||||
|
||||
<ImageView
|
||||
<FrameLayout
|
||||
android:id="@android:id/icon"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:scaleType="centerInside"
|
||||
android:contentDescription="@null" />
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/icon_mime"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:scaleType="centerInside"
|
||||
android:contentDescription="@null" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/icon_thumb"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:scaleType="centerCrop"
|
||||
android:contentDescription="@null" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<ImageView
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -25,15 +25,29 @@
|
||||
android:paddingBottom="8dip"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
<FrameLayout
|
||||
android:id="@android:id/icon"
|
||||
android:layout_width="@dimen/icon_size"
|
||||
android:layout_height="@dimen/icon_size"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginEnd="20dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:scaleType="centerInside"
|
||||
android:contentDescription="@null" />
|
||||
android:layout_gravity="center_vertical">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/icon_mime"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:scaleType="centerInside"
|
||||
android:contentDescription="@null" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/icon_thumb"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:scaleType="centerCrop"
|
||||
android:contentDescription="@null" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dip"
|
||||
|
||||
@@ -14,6 +14,13 @@
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@android:id/title"
|
||||
style="?android:attr/listSeparatorTextViewStyle" />
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="12dp">
|
||||
|
||||
<TextView
|
||||
android:id="@android:id/title"
|
||||
style="?android:attr/listSeparatorTextViewStyle" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
@@ -38,9 +38,11 @@ import android.content.Loader;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.os.CancellationSignal;
|
||||
import android.provider.DocumentsContract;
|
||||
import android.provider.DocumentsContract.Document;
|
||||
import android.text.format.DateUtils;
|
||||
@@ -56,6 +58,7 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AbsListView;
|
||||
import android.widget.AbsListView.MultiChoiceModeListener;
|
||||
import android.widget.AbsListView.RecyclerListener;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
import android.widget.BaseAdapter;
|
||||
@@ -162,10 +165,12 @@ public class DirectoryFragment extends Fragment {
|
||||
mListView = (ListView) view.findViewById(R.id.list);
|
||||
mListView.setOnItemClickListener(mItemListener);
|
||||
mListView.setMultiChoiceModeListener(mMultiListener);
|
||||
mListView.setRecyclerListener(mRecycleListener);
|
||||
|
||||
mGridView = (GridView) view.findViewById(R.id.grid);
|
||||
mGridView.setOnItemClickListener(mItemListener);
|
||||
mGridView.setMultiChoiceModeListener(mMultiListener);
|
||||
mGridView.setRecyclerListener(mRecycleListener);
|
||||
|
||||
return view;
|
||||
}
|
||||
@@ -192,13 +197,19 @@ public class DirectoryFragment extends Fragment {
|
||||
case TYPE_NORMAL:
|
||||
contentsUri = DocumentsContract.buildChildDocumentsUri(
|
||||
doc.authority, doc.documentId);
|
||||
if (state.action == ACTION_MANAGE) {
|
||||
contentsUri = DocumentsContract.setManageMode(contentsUri);
|
||||
}
|
||||
return new DirectoryLoader(
|
||||
context, root, doc, contentsUri, state.userSortOrder);
|
||||
context, mType, root, doc, contentsUri, state.userSortOrder);
|
||||
case TYPE_SEARCH:
|
||||
contentsUri = DocumentsContract.buildSearchDocumentsUri(
|
||||
doc.authority, doc.documentId, query);
|
||||
if (state.action == ACTION_MANAGE) {
|
||||
contentsUri = DocumentsContract.setManageMode(contentsUri);
|
||||
}
|
||||
return new DirectoryLoader(
|
||||
context, root, doc, contentsUri, state.userSortOrder);
|
||||
context, mType, root, doc, contentsUri, state.userSortOrder);
|
||||
case TYPE_RECENT_OPEN:
|
||||
final RootsCache roots = DocumentsApplication.getRootsCache(context);
|
||||
final List<RootInfo> matchingRoots = roots.getMatchingRoots(state);
|
||||
@@ -425,6 +436,20 @@ public class DirectoryFragment extends Fragment {
|
||||
}
|
||||
};
|
||||
|
||||
private RecyclerListener mRecycleListener = new RecyclerListener() {
|
||||
@Override
|
||||
public void onMovedToScrapHeap(View view) {
|
||||
final ImageView iconThumb = (ImageView) view.findViewById(R.id.icon_thumb);
|
||||
if (iconThumb != null) {
|
||||
final ThumbnailAsyncTask oldTask = (ThumbnailAsyncTask) iconThumb.getTag();
|
||||
if (oldTask != null) {
|
||||
oldTask.reallyCancel();
|
||||
iconThumb.setTag(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private void onShareDocuments(List<DocumentInfo> docs) {
|
||||
Intent intent;
|
||||
if (docs.size() == 1) {
|
||||
@@ -632,7 +657,9 @@ public class DirectoryFragment extends Fragment {
|
||||
final String docSummary = getCursorString(cursor, Document.COLUMN_SUMMARY);
|
||||
final long docSize = getCursorLong(cursor, Document.COLUMN_SIZE);
|
||||
|
||||
final ImageView icon = (ImageView) convertView.findViewById(android.R.id.icon);
|
||||
final View icon = convertView.findViewById(android.R.id.icon);
|
||||
final ImageView iconMime = (ImageView) convertView.findViewById(R.id.icon_mime);
|
||||
final ImageView iconThumb = (ImageView) convertView.findViewById(R.id.icon_thumb);
|
||||
final TextView title = (TextView) convertView.findViewById(android.R.id.title);
|
||||
final View line2 = convertView.findViewById(R.id.line2);
|
||||
final ImageView icon1 = (ImageView) convertView.findViewById(android.R.id.icon1);
|
||||
@@ -640,30 +667,49 @@ public class DirectoryFragment extends Fragment {
|
||||
final TextView date = (TextView) convertView.findViewById(R.id.date);
|
||||
final TextView size = (TextView) convertView.findViewById(R.id.size);
|
||||
|
||||
final ThumbnailAsyncTask oldTask = (ThumbnailAsyncTask) icon.getTag();
|
||||
final ThumbnailAsyncTask oldTask = (ThumbnailAsyncTask) iconThumb.getTag();
|
||||
if (oldTask != null) {
|
||||
oldTask.cancel(false);
|
||||
oldTask.reallyCancel();
|
||||
iconThumb.setTag(null);
|
||||
}
|
||||
|
||||
iconMime.animate().cancel();
|
||||
iconThumb.animate().cancel();
|
||||
|
||||
final boolean supportsThumbnail = (docFlags & Document.FLAG_SUPPORTS_THUMBNAIL) != 0;
|
||||
final boolean allowThumbnail = (state.derivedMode == MODE_GRID)
|
||||
|| MimePredicate.mimeMatches(LIST_THUMBNAIL_MIMES, docMimeType);
|
||||
|
||||
boolean cacheHit = false;
|
||||
if (supportsThumbnail && allowThumbnail) {
|
||||
final Uri uri = DocumentsContract.buildDocumentUri(docAuthority, docId);
|
||||
final Bitmap cachedResult = thumbs.get(uri);
|
||||
if (cachedResult != null) {
|
||||
icon.setImageBitmap(cachedResult);
|
||||
iconThumb.setImageBitmap(cachedResult);
|
||||
cacheHit = true;
|
||||
} else {
|
||||
final ThumbnailAsyncTask task = new ThumbnailAsyncTask(icon, mThumbSize);
|
||||
icon.setImageBitmap(null);
|
||||
icon.setTag(task);
|
||||
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, uri);
|
||||
iconThumb.setImageDrawable(null);
|
||||
final ThumbnailAsyncTask task = new ThumbnailAsyncTask(
|
||||
uri, iconMime, iconThumb, mThumbSize);
|
||||
iconThumb.setTag(task);
|
||||
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
}
|
||||
} else if (docIcon != 0) {
|
||||
icon.setImageDrawable(IconUtils.loadPackageIcon(context, docAuthority, docIcon));
|
||||
}
|
||||
|
||||
// Always throw MIME icon into place, even when a thumbnail is being
|
||||
// loaded in background.
|
||||
if (cacheHit) {
|
||||
iconMime.setAlpha(0f);
|
||||
iconThumb.setAlpha(1f);
|
||||
} else {
|
||||
icon.setImageDrawable(IconUtils.loadMimeIcon(context, docMimeType));
|
||||
iconMime.setAlpha(1f);
|
||||
iconThumb.setAlpha(0f);
|
||||
if (docIcon != 0) {
|
||||
iconMime.setImageDrawable(
|
||||
IconUtils.loadPackageIcon(context, docAuthority, docIcon));
|
||||
} else {
|
||||
iconMime.setImageDrawable(IconUtils.loadMimeIcon(context, docMimeType));
|
||||
}
|
||||
}
|
||||
|
||||
title.setText(docDisplayName);
|
||||
@@ -672,12 +718,19 @@ public class DirectoryFragment extends Fragment {
|
||||
|
||||
if (mType == TYPE_RECENT_OPEN) {
|
||||
final RootInfo root = roots.getRoot(docAuthority, docRootId);
|
||||
final Drawable iconDrawable = root.loadIcon(context);
|
||||
icon1.setVisibility(View.VISIBLE);
|
||||
icon1.setImageDrawable(root.loadIcon(context));
|
||||
summary.setText(root.getDirectoryString());
|
||||
summary.setVisibility(View.VISIBLE);
|
||||
summary.setTextAlignment(TextView.TEXT_ALIGNMENT_TEXT_END);
|
||||
hasLine2 = true;
|
||||
icon1.setImageDrawable(iconDrawable);
|
||||
|
||||
if (iconDrawable != null && roots.isIconUnique(root)) {
|
||||
// No summary needed if icon speaks for itself
|
||||
summary.setVisibility(View.INVISIBLE);
|
||||
} else {
|
||||
summary.setText(root.getDirectoryString());
|
||||
summary.setVisibility(View.VISIBLE);
|
||||
summary.setTextAlignment(TextView.TEXT_ALIGNMENT_TEXT_END);
|
||||
hasLine2 = true;
|
||||
}
|
||||
} else {
|
||||
icon1.setVisibility(View.GONE);
|
||||
if (docSummary != null) {
|
||||
@@ -762,32 +815,39 @@ public class DirectoryFragment extends Fragment {
|
||||
}
|
||||
|
||||
private static class ThumbnailAsyncTask extends AsyncTask<Uri, Void, Bitmap> {
|
||||
private final ImageView mTarget;
|
||||
private final Uri mUri;
|
||||
private final ImageView mIconMime;
|
||||
private final ImageView mIconThumb;
|
||||
private final Point mThumbSize;
|
||||
private final CancellationSignal mSignal;
|
||||
|
||||
public ThumbnailAsyncTask(ImageView target, Point thumbSize) {
|
||||
mTarget = target;
|
||||
public ThumbnailAsyncTask(
|
||||
Uri uri, ImageView iconMime, ImageView iconThumb, Point thumbSize) {
|
||||
mUri = uri;
|
||||
mIconMime = iconMime;
|
||||
mIconThumb = iconThumb;
|
||||
mThumbSize = thumbSize;
|
||||
mSignal = new CancellationSignal();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
mTarget.setTag(this);
|
||||
public void reallyCancel() {
|
||||
cancel(false);
|
||||
mSignal.cancel();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Bitmap doInBackground(Uri... params) {
|
||||
final Context context = mTarget.getContext();
|
||||
final Uri uri = params[0];
|
||||
final Context context = mIconThumb.getContext();
|
||||
|
||||
Bitmap result = null;
|
||||
try {
|
||||
// TODO: switch to using unstable provider
|
||||
result = DocumentsContract.getDocumentThumbnail(
|
||||
context.getContentResolver(), uri, mThumbSize, null);
|
||||
context.getContentResolver(), mUri, mThumbSize, mSignal);
|
||||
if (result != null) {
|
||||
final ThumbnailCache thumbs = DocumentsApplication.getThumbnailsCache(
|
||||
context, mThumbSize);
|
||||
thumbs.put(uri, result);
|
||||
thumbs.put(mUri, result);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "Failed to load thumbnail: " + e);
|
||||
@@ -797,9 +857,14 @@ public class DirectoryFragment extends Fragment {
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Bitmap result) {
|
||||
if (mTarget.getTag() == this) {
|
||||
mTarget.setImageBitmap(result);
|
||||
mTarget.setTag(null);
|
||||
if (mIconThumb.getTag() == this && result != null) {
|
||||
mIconThumb.setTag(null);
|
||||
mIconThumb.setImageBitmap(result);
|
||||
|
||||
mIconMime.setAlpha(1f);
|
||||
mIconMime.animate().alpha(0f).start();
|
||||
mIconThumb.setAlpha(0f);
|
||||
mIconThumb.animate().alpha(1f).start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,6 +62,7 @@ class DirectoryResult implements AutoCloseable {
|
||||
public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> {
|
||||
private final ForceLoadContentObserver mObserver = new ForceLoadContentObserver();
|
||||
|
||||
private final int mType;
|
||||
private final RootInfo mRoot;
|
||||
private final DocumentInfo mDoc;
|
||||
private final Uri mUri;
|
||||
@@ -70,9 +71,10 @@ public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> {
|
||||
private CancellationSignal mSignal;
|
||||
private DirectoryResult mResult;
|
||||
|
||||
public DirectoryLoader(
|
||||
Context context, RootInfo root, DocumentInfo doc, Uri uri, int userSortOrder) {
|
||||
public DirectoryLoader(Context context, int type, RootInfo root, DocumentInfo doc, Uri uri,
|
||||
int userSortOrder) {
|
||||
super(context);
|
||||
mType = type;
|
||||
mRoot = root;
|
||||
mDoc = doc;
|
||||
mUri = uri;
|
||||
@@ -128,6 +130,11 @@ public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> {
|
||||
}
|
||||
}
|
||||
|
||||
// Search always uses ranking from provider
|
||||
if (mType == DirectoryFragment.TYPE_SEARCH) {
|
||||
result.sortOrder = State.SORT_ORDER_UNKNOWN;
|
||||
}
|
||||
|
||||
Log.d(TAG, "userMode=" + userMode + ", userSortOrder=" + mUserSortOrder + " --> mode="
|
||||
+ result.mode + ", sortOrder=" + result.sortOrder);
|
||||
|
||||
@@ -137,11 +144,18 @@ public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> {
|
||||
mUri, null, null, null, getQuerySortOrder(result.sortOrder), mSignal);
|
||||
cursor.registerContentObserver(mObserver);
|
||||
|
||||
final Cursor withRoot = new RootCursorWrapper(
|
||||
mUri.getAuthority(), mRoot.rootId, cursor, -1);
|
||||
final Cursor sorted = new SortingCursorWrapper(withRoot, result.sortOrder);
|
||||
cursor = new RootCursorWrapper(mUri.getAuthority(), mRoot.rootId, cursor, -1);
|
||||
|
||||
result.cursor = sorted;
|
||||
if (mType == DirectoryFragment.TYPE_SEARCH) {
|
||||
// Filter directories out of search results, for now
|
||||
cursor = new FilteringCursorWrapper(cursor, null, new String[] {
|
||||
Document.MIME_TYPE_DIR });
|
||||
} else {
|
||||
// Normal directories should have sorting applied
|
||||
cursor = new SortingCursorWrapper(cursor, result.sortOrder);
|
||||
}
|
||||
|
||||
result.cursor = cursor;
|
||||
} catch (Exception e) {
|
||||
Log.d(TAG, "Failed to query", e);
|
||||
result.exception = e;
|
||||
|
||||
@@ -50,6 +50,7 @@ import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.MenuItem.OnActionExpandListener;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.BaseAdapter;
|
||||
@@ -87,6 +88,7 @@ public class DocumentsActivity extends Activity {
|
||||
private static final String EXTRA_STATE = "state";
|
||||
|
||||
private boolean mIgnoreNextNavigation;
|
||||
private boolean mIgnoreNextCollapse;
|
||||
|
||||
private RootsCache mRoots;
|
||||
private State mState;
|
||||
@@ -234,12 +236,14 @@ public class DocumentsActivity extends Activity {
|
||||
public void onDrawerOpened(View drawerView) {
|
||||
mDrawerToggle.onDrawerOpened(drawerView);
|
||||
updateActionBar();
|
||||
invalidateOptionsMenu();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDrawerClosed(View drawerView) {
|
||||
mDrawerToggle.onDrawerClosed(drawerView);
|
||||
updateActionBar();
|
||||
invalidateOptionsMenu();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -305,7 +309,6 @@ public class DocumentsActivity extends Activity {
|
||||
public boolean onQueryTextSubmit(String query) {
|
||||
mState.currentSearch = query;
|
||||
onCurrentDirectoryChanged();
|
||||
mSearchView.setIconified(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -315,12 +318,22 @@ public class DocumentsActivity extends Activity {
|
||||
}
|
||||
});
|
||||
|
||||
mSearchView.setOnCloseListener(new OnCloseListener() {
|
||||
searchMenu.setOnActionExpandListener(new OnActionExpandListener() {
|
||||
@Override
|
||||
public boolean onClose() {
|
||||
public boolean onMenuItemActionExpand(MenuItem item) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMenuItemActionCollapse(MenuItem item) {
|
||||
if (mIgnoreNextCollapse) {
|
||||
mIgnoreNextCollapse = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
mState.currentSearch = null;
|
||||
onCurrentDirectoryChanged();
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -342,6 +355,18 @@ public class DocumentsActivity extends Activity {
|
||||
final MenuItem list = menu.findItem(R.id.menu_list);
|
||||
final MenuItem settings = menu.findItem(R.id.menu_settings);
|
||||
|
||||
// Open drawer means we hide most actions
|
||||
if (mDrawerLayout.isDrawerOpen(mRootsContainer)) {
|
||||
createDir.setVisible(false);
|
||||
search.setVisible(false);
|
||||
sort.setVisible(false);
|
||||
grid.setVisible(false);
|
||||
list.setVisible(false);
|
||||
mIgnoreNextCollapse = true;
|
||||
search.collapseActionView();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (cwd != null) {
|
||||
sort.setVisible(true);
|
||||
grid.setVisible(mState.derivedMode != MODE_GRID);
|
||||
@@ -352,6 +377,17 @@ public class DocumentsActivity extends Activity {
|
||||
list.setVisible(false);
|
||||
}
|
||||
|
||||
if (mState.currentSearch != null) {
|
||||
// Search uses backend ranking; no sorting
|
||||
sort.setVisible(false);
|
||||
|
||||
search.expandActionView();
|
||||
mSearchView.setQuery(mState.currentSearch, false);
|
||||
} else {
|
||||
mIgnoreNextCollapse = true;
|
||||
search.collapseActionView();
|
||||
}
|
||||
|
||||
// Only sort by size when visible
|
||||
sortSize.setVisible(mState.showSize);
|
||||
|
||||
|
||||
@@ -56,7 +56,11 @@ public class DocumentsApplication extends Application {
|
||||
packageFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
|
||||
packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
|
||||
packageFilter.addDataScheme("package");
|
||||
registerReceiver(mPackageReceiver, packageFilter);
|
||||
registerReceiver(mCacheReceiver, packageFilter);
|
||||
|
||||
final IntentFilter localeFilter = new IntentFilter();
|
||||
localeFilter.addAction(Intent.ACTION_LOCALE_CHANGED);
|
||||
registerReceiver(mCacheReceiver, localeFilter);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -70,7 +74,7 @@ public class DocumentsApplication extends Application {
|
||||
}
|
||||
}
|
||||
|
||||
private BroadcastReceiver mPackageReceiver = new BroadcastReceiver() {
|
||||
private BroadcastReceiver mCacheReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
// TODO: narrow changed/removed to only packages that have backends
|
||||
|
||||
@@ -34,6 +34,10 @@ public class FilteringCursorWrapper extends AbstractCursor {
|
||||
private int mCount;
|
||||
|
||||
public FilteringCursorWrapper(Cursor cursor, String[] acceptMimes) {
|
||||
this(cursor, acceptMimes, null);
|
||||
}
|
||||
|
||||
public FilteringCursorWrapper(Cursor cursor, String[] acceptMimes, String[] rejectMimes) {
|
||||
mCursor = cursor;
|
||||
|
||||
final int count = cursor.getCount();
|
||||
@@ -43,6 +47,9 @@ public class FilteringCursorWrapper extends AbstractCursor {
|
||||
while (cursor.moveToNext()) {
|
||||
final String mimeType = cursor.getString(
|
||||
cursor.getColumnIndex(Document.COLUMN_MIME_TYPE));
|
||||
if (rejectMimes != null && MimePredicate.mimeMatches(rejectMimes, mimeType)) {
|
||||
continue;
|
||||
}
|
||||
if (MimePredicate.mimeMatches(acceptMimes, mimeType)) {
|
||||
mPosition[mCount++] = cursor.getPosition();
|
||||
}
|
||||
|
||||
@@ -183,12 +183,12 @@ public class RecentsCreateFragment extends Fragment {
|
||||
convertView = inflater.inflate(R.layout.item_doc_list, parent, false);
|
||||
}
|
||||
|
||||
final ImageView icon = (ImageView) convertView.findViewById(android.R.id.icon);
|
||||
final ImageView iconMime = (ImageView) convertView.findViewById(R.id.icon_mime);
|
||||
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));
|
||||
iconMime.setImageDrawable(stack.root.loadIcon(context));
|
||||
|
||||
final Drawable crumb = context.getResources()
|
||||
.getDrawable(R.drawable.ic_breadcrumb_arrow);
|
||||
|
||||
@@ -151,18 +151,18 @@ public class RecentsProvider extends ContentProvider {
|
||||
case URI_RECENT:
|
||||
final long cutoff = System.currentTimeMillis() - MAX_HISTORY_IN_MILLIS;
|
||||
return db.query(TABLE_RECENT, projection, RecentColumns.TIMESTAMP + ">" + cutoff,
|
||||
null, null, null, null);
|
||||
null, null, null, sortOrder);
|
||||
case URI_STATE:
|
||||
final String authority = uri.getPathSegments().get(1);
|
||||
final String rootId = uri.getPathSegments().get(2);
|
||||
final String documentId = uri.getPathSegments().get(3);
|
||||
return db.query(TABLE_STATE, projection, StateColumns.AUTHORITY + "=? AND "
|
||||
+ StateColumns.ROOT_ID + "=? AND " + StateColumns.DOCUMENT_ID + "=?",
|
||||
new String[] { authority, rootId, documentId }, null, null, null);
|
||||
new String[] { authority, rootId, documentId }, null, null, sortOrder);
|
||||
case URI_RESUME:
|
||||
final String packageName = uri.getPathSegments().get(1);
|
||||
return db.query(TABLE_RESUME, projection, ResumeColumns.PACKAGE_NAME + "=?",
|
||||
new String[] { packageName }, null, null, null);
|
||||
new String[] { packageName }, null, null, sortOrder);
|
||||
default:
|
||||
throw new UnsupportedOperationException("Unsupported Uri " + uri);
|
||||
}
|
||||
|
||||
@@ -109,6 +109,8 @@ public class RootsCache {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Log.d(TAG, "Update found " + mRoots.size() + " roots");
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@@ -133,6 +135,21 @@ public class RootsCache {
|
||||
return null;
|
||||
}
|
||||
|
||||
@GuardedBy("ActivityThread")
|
||||
public boolean isIconUnique(RootInfo root) {
|
||||
for (RootInfo test : mRoots) {
|
||||
if (Objects.equal(test.authority, root.authority)) {
|
||||
if (Objects.equal(test.rootId, root.rootId)) {
|
||||
continue;
|
||||
}
|
||||
if (test.icon == root.icon) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@GuardedBy("ActivityThread")
|
||||
public RootInfo getRecentsRoot() {
|
||||
return mRecentsRoot;
|
||||
|
||||
@@ -25,6 +25,7 @@ import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.os.Bundle;
|
||||
import android.provider.DocumentsContract.Root;
|
||||
import android.text.TextUtils;
|
||||
import android.text.format.Formatter;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@@ -168,7 +169,7 @@ public class RootsFragment extends Fragment {
|
||||
}
|
||||
|
||||
summary.setText(summaryText);
|
||||
summary.setVisibility(summaryText != null ? View.VISIBLE : View.GONE);
|
||||
summary.setVisibility(TextUtils.isEmpty(summaryText) ? View.GONE : View.VISIBLE);
|
||||
|
||||
return convertView;
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import android.graphics.drawable.Drawable;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.provider.DocumentsContract.Root;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.android.documentsui.IconUtils;
|
||||
import com.android.documentsui.R;
|
||||
@@ -203,6 +204,6 @@ public class RootInfo implements Durable, Parcelable {
|
||||
}
|
||||
|
||||
public String getDirectoryString() {
|
||||
return (summary != null) ? summary : title;
|
||||
return !TextUtils.isEmpty(summary) ? summary : title;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -267,16 +267,15 @@ public class ExternalStorageProvider extends DocumentsProvider {
|
||||
|
||||
final LinkedList<File> pending = new LinkedList<File>();
|
||||
pending.add(parent);
|
||||
while (!pending.isEmpty() && result.getCount() < 20) {
|
||||
while (!pending.isEmpty() && result.getCount() < 24) {
|
||||
final File file = pending.removeFirst();
|
||||
if (file.isDirectory()) {
|
||||
for (File child : file.listFiles()) {
|
||||
pending.add(child);
|
||||
}
|
||||
} else {
|
||||
if (file.getName().toLowerCase().contains(query)) {
|
||||
includeFile(result, null, file);
|
||||
}
|
||||
}
|
||||
if (file.getName().toLowerCase().contains(query)) {
|
||||
includeFile(result, null, file);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
|
||||
Reference in New Issue
Block a user