Loading res/values/strings.xml +3 −0 Original line number Diff line number Diff line Loading @@ -117,6 +117,9 @@ <!-- Table header for last modified time. [CHAR_LIMIT=18] --> <string name="sort_dimension_date">Modified</string> <!--Table header for number of children--> <string name="directory_children">Number of Children</string> <!-- content description to describe ascending sorting used with upward arrow in table header. --> <string name="sort_direction_ascending">Ascending</string> <!-- content description to describe descending sorting used with downward arrow in table header. --> Loading src/com/android/documentsui/base/DocumentInfo.java +2 −0 Original line number Diff line number Diff line Loading @@ -60,6 +60,7 @@ public class DocumentInfo implements Durable, Parcelable { public String summary; public long size; public int icon; public int numberOfChildren; /** Derived fields that aren't persisted */ public Uri derivedUri; Loading @@ -80,6 +81,7 @@ public class DocumentInfo implements Durable, Parcelable { size = -1; icon = 0; derivedUri = null; numberOfChildren = -1; } @Override Loading src/com/android/documentsui/inspector/DetailsView.java +4 −0 Original line number Diff line number Diff line Loading @@ -81,5 +81,9 @@ public class DetailsView extends TableView implements Consumer<DocumentInfo> { setRow(R.string.sort_dimension_file_type, info.mimeType); setRow(R.string.sort_dimension_size, formatSize(info.size)); setRow(R.string.sort_dimension_date, String.valueOf(info.lastModified)); if(info.numberOfChildren != -1) { setRow(R.string.directory_children, String.valueOf(info.numberOfChildren)); } } } No newline at end of file src/com/android/documentsui/inspector/DirectoryLoader.java 0 → 100644 +112 −0 Original line number Diff line number Diff line /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.documentsui.inspector; import static com.android.internal.util.Preconditions.checkArgument; import android.content.ContentResolver; import android.database.Cursor; import android.net.Uri; import android.os.AsyncTask; import android.provider.DocumentsContract; import android.provider.DocumentsContract.Document; import com.android.documentsui.base.DocumentInfo; import java.util.LinkedList; import java.util.Queue; import java.util.function.Consumer; public class DirectoryLoader extends AsyncTask<DocumentInfo, Integer, DocumentInfo> { private static int MAXIMUM_FILE_COUNT = 5000; private final ContentResolver mResolver; private final Consumer<DocumentInfo> mCallback; public DirectoryLoader(ContentResolver resolver, Consumer<DocumentInfo> callback) { mResolver = resolver; mCallback = callback; } /** * Finds the size and number of children. */ @Override protected DocumentInfo doInBackground(DocumentInfo... documentInfos) { checkArgument(documentInfos.length == 1); if (documentInfos[0].isDirectory()) { DocumentInfo directory = documentInfos[0]; directory.numberOfChildren = getChildrenCount(directory); directory.size = getDirectorySize(directory); return directory; } else { return null; } } @Override protected void onPostExecute(DocumentInfo result) { mCallback.accept(result); } private int getChildrenCount(DocumentInfo directory) { return getCursor(directory).getCount(); } private long getDirectorySize(DocumentInfo directory) { Long size = 0L; Queue<DocumentInfo> directories = new LinkedList<>(); directories.add(directory); int count = 0; while(directories.size() > 0) { //makes a cursor from first directory in queue. Cursor cursor = getCursor(directories.remove()); while (cursor.moveToNext()) { //hard stop if we have processed a large amount of files. if(count >= MAXIMUM_FILE_COUNT) { return size; } //iterate through the directory. DocumentInfo info = DocumentInfo.fromCursor(cursor, directory.authority); if (info.isDirectory()) { directories.add(info); } else { size += info.size; } count++; } //done checking this directory, close the cursor. cursor.close(); } return size; } private Cursor getCursor(DocumentInfo directory) { checkArgument(directory.isDirectory()); Uri children = DocumentsContract.buildChildDocumentsUri( directory.authority, directory.documentId); return mResolver .query(children, null, null, null, Document.COLUMN_SIZE + " DESC", null); } } src/com/android/documentsui/inspector/InspectorController.java +33 −5 Original line number Diff line number Diff line Loading @@ -25,12 +25,14 @@ import android.provider.DocumentsContract; import android.support.annotation.Nullable; import android.support.annotation.VisibleForTesting; import android.view.View; import com.android.documentsui.DocumentsApplication; import com.android.documentsui.ProviderExecutor; import com.android.documentsui.R; import com.android.documentsui.base.DocumentInfo; import com.android.documentsui.base.Lookup; import com.android.documentsui.roots.ProvidersAccess; import com.android.documentsui.ui.Snackbars; import java.util.concurrent.Executor; import java.util.function.Consumer; /** * A controller that coordinates retrieving document information and sending it to the view. Loading @@ -45,6 +47,7 @@ public final class InspectorController { private final Context mContext; private final ProvidersAccess mProviders; private final Runnable mShowSnackbar; private final Lookup<String, Executor> mExecutors; /** * InspectorControllerTest relies on this controller. Loading @@ -52,7 +55,8 @@ public final class InspectorController { @VisibleForTesting public InspectorController(Context context, Loader loader, ProvidersAccess providers, boolean showDebug, Consumer<DocumentInfo> header, Consumer<DocumentInfo> details, Consumer<DocumentInfo> debugView, Runnable showSnackbar) { Consumer<DocumentInfo> debugView, Lookup<String, Executor> executors, Runnable showSnackbar) { checkArgument(context != null); checkArgument(loader != null); Loading @@ -61,6 +65,7 @@ public final class InspectorController { checkArgument(details != null); checkArgument(debugView != null); checkArgument(showSnackbar != null); checkArgument(executors != null); mContext = context; mLoader = loader; Loading @@ -69,6 +74,7 @@ public final class InspectorController { mHeader = header; mDetails = details; mDebugView = debugView; mExecutors = executors; mShowSnackbar = showSnackbar; } Loading @@ -81,6 +87,7 @@ public final class InspectorController { (HeaderView) layout.findViewById(R.id.inspector_header_view), (DetailsView) layout.findViewById(R.id.inspector_details_view), (DebugView) layout.findViewById(R.id.inspector_debug_view), ProviderExecutor::forAuthority, () -> { // using a runnable to support unit testing this feature. Snackbars.showInspectorError(activity); Loading @@ -100,24 +107,45 @@ public final class InspectorController { } /** * Updates the view. * Updates the view with documentInfo. */ @Nullable public void updateView(@Nullable DocumentInfo docInfo) { if (docInfo == null) { mShowSnackbar.run(); } else { } else { mHeader.accept(docInfo); mDetails.accept(docInfo); if (docInfo.isDirectory()) { new DirectoryLoader(mContext.getContentResolver(), this::displayDirectory) .executeOnExecutor(mExecutors.lookup(docInfo.authority), docInfo); } if (mShowDebug) { mDebugView.accept(docInfo); } } } /** * Displays a directory's information to the view. * * @param dirInfo - null if uri was not to a directory. */ @Nullable public void displayDirectory(@Nullable DocumentInfo directory) { if (directory == null) { mShowSnackbar.run(); } else { //update directory information. mDetails.accept(directory); } } /** * Shows the selected document in it's content provider. * Loading Loading
res/values/strings.xml +3 −0 Original line number Diff line number Diff line Loading @@ -117,6 +117,9 @@ <!-- Table header for last modified time. [CHAR_LIMIT=18] --> <string name="sort_dimension_date">Modified</string> <!--Table header for number of children--> <string name="directory_children">Number of Children</string> <!-- content description to describe ascending sorting used with upward arrow in table header. --> <string name="sort_direction_ascending">Ascending</string> <!-- content description to describe descending sorting used with downward arrow in table header. --> Loading
src/com/android/documentsui/base/DocumentInfo.java +2 −0 Original line number Diff line number Diff line Loading @@ -60,6 +60,7 @@ public class DocumentInfo implements Durable, Parcelable { public String summary; public long size; public int icon; public int numberOfChildren; /** Derived fields that aren't persisted */ public Uri derivedUri; Loading @@ -80,6 +81,7 @@ public class DocumentInfo implements Durable, Parcelable { size = -1; icon = 0; derivedUri = null; numberOfChildren = -1; } @Override Loading
src/com/android/documentsui/inspector/DetailsView.java +4 −0 Original line number Diff line number Diff line Loading @@ -81,5 +81,9 @@ public class DetailsView extends TableView implements Consumer<DocumentInfo> { setRow(R.string.sort_dimension_file_type, info.mimeType); setRow(R.string.sort_dimension_size, formatSize(info.size)); setRow(R.string.sort_dimension_date, String.valueOf(info.lastModified)); if(info.numberOfChildren != -1) { setRow(R.string.directory_children, String.valueOf(info.numberOfChildren)); } } } No newline at end of file
src/com/android/documentsui/inspector/DirectoryLoader.java 0 → 100644 +112 −0 Original line number Diff line number Diff line /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.documentsui.inspector; import static com.android.internal.util.Preconditions.checkArgument; import android.content.ContentResolver; import android.database.Cursor; import android.net.Uri; import android.os.AsyncTask; import android.provider.DocumentsContract; import android.provider.DocumentsContract.Document; import com.android.documentsui.base.DocumentInfo; import java.util.LinkedList; import java.util.Queue; import java.util.function.Consumer; public class DirectoryLoader extends AsyncTask<DocumentInfo, Integer, DocumentInfo> { private static int MAXIMUM_FILE_COUNT = 5000; private final ContentResolver mResolver; private final Consumer<DocumentInfo> mCallback; public DirectoryLoader(ContentResolver resolver, Consumer<DocumentInfo> callback) { mResolver = resolver; mCallback = callback; } /** * Finds the size and number of children. */ @Override protected DocumentInfo doInBackground(DocumentInfo... documentInfos) { checkArgument(documentInfos.length == 1); if (documentInfos[0].isDirectory()) { DocumentInfo directory = documentInfos[0]; directory.numberOfChildren = getChildrenCount(directory); directory.size = getDirectorySize(directory); return directory; } else { return null; } } @Override protected void onPostExecute(DocumentInfo result) { mCallback.accept(result); } private int getChildrenCount(DocumentInfo directory) { return getCursor(directory).getCount(); } private long getDirectorySize(DocumentInfo directory) { Long size = 0L; Queue<DocumentInfo> directories = new LinkedList<>(); directories.add(directory); int count = 0; while(directories.size() > 0) { //makes a cursor from first directory in queue. Cursor cursor = getCursor(directories.remove()); while (cursor.moveToNext()) { //hard stop if we have processed a large amount of files. if(count >= MAXIMUM_FILE_COUNT) { return size; } //iterate through the directory. DocumentInfo info = DocumentInfo.fromCursor(cursor, directory.authority); if (info.isDirectory()) { directories.add(info); } else { size += info.size; } count++; } //done checking this directory, close the cursor. cursor.close(); } return size; } private Cursor getCursor(DocumentInfo directory) { checkArgument(directory.isDirectory()); Uri children = DocumentsContract.buildChildDocumentsUri( directory.authority, directory.documentId); return mResolver .query(children, null, null, null, Document.COLUMN_SIZE + " DESC", null); } }
src/com/android/documentsui/inspector/InspectorController.java +33 −5 Original line number Diff line number Diff line Loading @@ -25,12 +25,14 @@ import android.provider.DocumentsContract; import android.support.annotation.Nullable; import android.support.annotation.VisibleForTesting; import android.view.View; import com.android.documentsui.DocumentsApplication; import com.android.documentsui.ProviderExecutor; import com.android.documentsui.R; import com.android.documentsui.base.DocumentInfo; import com.android.documentsui.base.Lookup; import com.android.documentsui.roots.ProvidersAccess; import com.android.documentsui.ui.Snackbars; import java.util.concurrent.Executor; import java.util.function.Consumer; /** * A controller that coordinates retrieving document information and sending it to the view. Loading @@ -45,6 +47,7 @@ public final class InspectorController { private final Context mContext; private final ProvidersAccess mProviders; private final Runnable mShowSnackbar; private final Lookup<String, Executor> mExecutors; /** * InspectorControllerTest relies on this controller. Loading @@ -52,7 +55,8 @@ public final class InspectorController { @VisibleForTesting public InspectorController(Context context, Loader loader, ProvidersAccess providers, boolean showDebug, Consumer<DocumentInfo> header, Consumer<DocumentInfo> details, Consumer<DocumentInfo> debugView, Runnable showSnackbar) { Consumer<DocumentInfo> debugView, Lookup<String, Executor> executors, Runnable showSnackbar) { checkArgument(context != null); checkArgument(loader != null); Loading @@ -61,6 +65,7 @@ public final class InspectorController { checkArgument(details != null); checkArgument(debugView != null); checkArgument(showSnackbar != null); checkArgument(executors != null); mContext = context; mLoader = loader; Loading @@ -69,6 +74,7 @@ public final class InspectorController { mHeader = header; mDetails = details; mDebugView = debugView; mExecutors = executors; mShowSnackbar = showSnackbar; } Loading @@ -81,6 +87,7 @@ public final class InspectorController { (HeaderView) layout.findViewById(R.id.inspector_header_view), (DetailsView) layout.findViewById(R.id.inspector_details_view), (DebugView) layout.findViewById(R.id.inspector_debug_view), ProviderExecutor::forAuthority, () -> { // using a runnable to support unit testing this feature. Snackbars.showInspectorError(activity); Loading @@ -100,24 +107,45 @@ public final class InspectorController { } /** * Updates the view. * Updates the view with documentInfo. */ @Nullable public void updateView(@Nullable DocumentInfo docInfo) { if (docInfo == null) { mShowSnackbar.run(); } else { } else { mHeader.accept(docInfo); mDetails.accept(docInfo); if (docInfo.isDirectory()) { new DirectoryLoader(mContext.getContentResolver(), this::displayDirectory) .executeOnExecutor(mExecutors.lookup(docInfo.authority), docInfo); } if (mShowDebug) { mDebugView.accept(docInfo); } } } /** * Displays a directory's information to the view. * * @param dirInfo - null if uri was not to a directory. */ @Nullable public void displayDirectory(@Nullable DocumentInfo directory) { if (directory == null) { mShowSnackbar.run(); } else { //update directory information. mDetails.accept(directory); } } /** * Shows the selected document in it's content provider. * Loading