Loading src/com/android/documentsui/DirectoryLoader.java +6 −6 Original line number Original line Diff line number Diff line Loading @@ -196,7 +196,7 @@ public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> { } else { } else { cursor = mModel.sortCursor(cursor, mFileTypeLookup); cursor = mModel.sortCursor(cursor, mFileTypeLookup); } } result.cursor = cursor; result.setCursor(cursor); } catch (Exception e) { } catch (Exception e) { Log.w(TAG, "Failed to query", e); Log.w(TAG, "Failed to query", e); result.exception = e; result.exception = e; Loading Loading @@ -305,8 +305,8 @@ public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> { // Ensure the loader is stopped // Ensure the loader is stopped onStopLoading(); onStopLoading(); if (mResult != null && mResult.cursor != null && mObserver != null) { if (mResult != null && mResult.getCursor() != null && mObserver != null) { mResult.cursor.unregisterContentObserver(mObserver); mResult.getCursor().unregisterContentObserver(mObserver); } } FileUtils.closeQuietly(mResult); FileUtils.closeQuietly(mResult); Loading @@ -314,10 +314,10 @@ public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> { } } private boolean checkIfCursorStale(DirectoryResult result) { private boolean checkIfCursorStale(DirectoryResult result) { if (result == null || result.cursor == null || result.cursor.isClosed()) { if (result == null || result.getCursor() == null || result.getCursor().isClosed()) { return true; return true; } } Cursor cursor = result.cursor; Cursor cursor = result.getCursor(); try { try { cursor.moveToPosition(-1); cursor.moveToPosition(-1); for (int pos = 0; pos < cursor.getCount(); ++pos) { for (int pos = 0; pos < cursor.getCount(); ++pos) { Loading src/com/android/documentsui/DirectoryResult.java +71 −3 Original line number Original line Diff line number Diff line Loading @@ -16,29 +16,97 @@ package com.android.documentsui; package com.android.documentsui; import static com.android.documentsui.base.DocumentInfo.getCursorString; import android.content.ContentProviderClient; import android.content.ContentProviderClient; import android.database.Cursor; import android.database.Cursor; import android.os.FileUtils; import android.os.FileUtils; import android.provider.DocumentsContract; import android.util.Log; import com.android.documentsui.archives.ArchivesProvider; import com.android.documentsui.archives.ArchivesProvider; import com.android.documentsui.base.DocumentInfo; import com.android.documentsui.base.DocumentInfo; import java.util.HashSet; import java.util.Set; public class DirectoryResult implements AutoCloseable { public class DirectoryResult implements AutoCloseable { public Cursor cursor; private static final String TAG = "DirectoryResult"; public Exception exception; public Exception exception; public DocumentInfo doc; public DocumentInfo doc; ContentProviderClient client; ContentProviderClient client; private Cursor mCursor; private Set<String> mFileNames; private String[] mModelIds; @Override @Override public void close() { public void close() { FileUtils.closeQuietly(cursor); FileUtils.closeQuietly(mCursor); if (client != null && doc.isInArchive()) { if (client != null && doc.isInArchive()) { ArchivesProvider.releaseArchive(client, doc.derivedUri); ArchivesProvider.releaseArchive(client, doc.derivedUri); } } FileUtils.closeQuietly(client); FileUtils.closeQuietly(client); cursor = null; client = null; client = null; doc = null; doc = null; setCursor(null); } public Cursor getCursor() { return mCursor; } public String[] getModelIds() { return mModelIds; } public Set<String> getFileNames() { return mFileNames; } /** Update the cursor and populate cursor-related fields. */ public void setCursor(Cursor cursor) { mCursor = cursor; if (mCursor == null) { mFileNames = null; mModelIds = null; } else { loadDataFromCursor(); } } /** Populate cursor-related field. Must not be called from UI thread. */ private void loadDataFromCursor() { ThreadHelper.assertNotOnMainThread(); int cursorCount = mCursor.getCount(); String[] modelIds = new String[cursorCount]; Set<String> fileNames = new HashSet<>(); try { mCursor.moveToPosition(-1); for (int pos = 0; pos < cursorCount; ++pos) { if (!mCursor.moveToNext()) { Log.e(TAG, "Fail to move cursor to next pos: " + pos); return; } // Generates a Model ID for a cursor entry that refers to a document. The Model // ID is a unique string that can be used to identify the document referred to by // the cursor. Prefix the ids with the authority to avoid collisions. modelIds[pos] = ModelId.build(mCursor); fileNames.add( getCursorString(mCursor, DocumentsContract.Document.COLUMN_DISPLAY_NAME)); } } catch (Exception e) { Log.e(TAG, "Exception when moving cursor. Stale cursor?", e); return; } // Model related data is only non-null when no error iterating through cursor. mModelIds = modelIds; mFileNames = fileNames; } } } } src/com/android/documentsui/Model.java +12 −31 Original line number Original line Diff line number Diff line Loading @@ -16,7 +16,6 @@ package com.android.documentsui; package com.android.documentsui; import static com.android.documentsui.base.DocumentInfo.getCursorString; import static com.android.documentsui.base.SharedMinimal.DEBUG; import static com.android.documentsui.base.SharedMinimal.DEBUG; import static com.android.documentsui.base.SharedMinimal.VERBOSE; import static com.android.documentsui.base.SharedMinimal.VERBOSE; Loading @@ -25,7 +24,6 @@ import android.database.Cursor; import android.net.Uri; import android.net.Uri; import android.os.Bundle; import android.os.Bundle; import android.provider.DocumentsContract; import android.provider.DocumentsContract; import android.provider.DocumentsContract.Document; import android.util.Log; import android.util.Log; import androidx.annotation.IntDef; import androidx.annotation.IntDef; Loading Loading @@ -125,11 +123,21 @@ public class Model { return; return; } } mCursor = result.cursor; mCursor = result.getCursor(); mCursorCount = mCursor.getCount(); mCursorCount = mCursor.getCount(); doc = result.doc; doc = result.doc; updateModelData(); if (result.getModelIds() != null && result.getFileNames() != null) { mIds = result.getModelIds(); mFileNames.clear(); mFileNames.addAll(result.getFileNames()); // Populate the positions. mPositions.clear(); for (int i = 0; i < mCursorCount; ++i) { mPositions.put(mIds[i], i); } } final Bundle extras = mCursor.getExtras(); final Bundle extras = mCursor.getExtras(); if (extras != null) { if (extras != null) { Loading @@ -146,33 +154,6 @@ public class Model { return mCursorCount; return mCursorCount; } } /** * Scan over the incoming cursor data, generate Model IDs for each row, and sort the IDs * according to the current sort order. */ private void updateModelData() { mIds = new String[mCursorCount]; mFileNames.clear(); mCursor.moveToPosition(-1); for (int pos = 0; pos < mCursorCount; ++pos) { if (!mCursor.moveToNext()) { Log.e(TAG, "Fail to move cursor to next pos: " + pos); return; } // Generates a Model ID for a cursor entry that refers to a document. The Model ID is a // unique string that can be used to identify the document referred to by the cursor. // Prefix the ids with the authority to avoid collisions. mIds[pos] = ModelId.build(mCursor); mFileNames.add(getCursorString(mCursor, Document.COLUMN_DISPLAY_NAME)); } // Populate the positions. mPositions.clear(); for (int i = 0; i < mCursorCount; ++i) { mPositions.put(mIds[i], i); } } public boolean hasFileWithName(String name) { public boolean hasFileWithName(String name) { return mFileNames.contains(name); return mFileNames.contains(name); } } Loading src/com/android/documentsui/MultiRootDocumentsLoader.java +3 −3 Original line number Original line Diff line number Diff line Loading @@ -237,7 +237,7 @@ public abstract class MultiRootDocumentsLoader extends AsyncTaskLoader<Directory extras.putBoolean(DocumentsContract.EXTRA_LOADING, !allDone); extras.putBoolean(DocumentsContract.EXTRA_LOADING, !allDone); sorted.setExtras(extras); sorted.setExtras(extras); result.cursor = sorted; result.setCursor(sorted); return result; return result; } } Loading Loading @@ -458,10 +458,10 @@ public abstract class MultiRootDocumentsLoader extends AsyncTaskLoader<Directory } } private boolean checkIfCursorStale(DirectoryResult result) { private boolean checkIfCursorStale(DirectoryResult result) { if (result == null || result.cursor == null || result.cursor.isClosed()) { if (result == null || result.getCursor() == null || result.getCursor().isClosed()) { return true; return true; } } Cursor cursor = result.cursor; Cursor cursor = result.getCursor(); try { try { cursor.moveToPosition(-1); cursor.moveToPosition(-1); for (int pos = 0; pos < cursor.getCount(); ++pos) { for (int pos = 0; pos < cursor.getCount(); ++pos) { Loading src/com/android/documentsui/ThreadHelper.java 0 → 100644 +58 −0 Original line number Original line Diff line number Diff line /* * Copyright (C) 2020 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; import android.os.Looper; /** Class for handler/thread utils. */ public final class ThreadHelper { private ThreadHelper() { } static final String MUST_NOT_ON_MAIN_THREAD_MSG = "This method should not be called on main thread."; static final String MUST_ON_MAIN_THREAD_MSG = "This method should only be called on main thread."; /** Verifies that current thread is not the UI thread. */ public static void assertNotOnMainThread() { if (Looper.getMainLooper().getThread() == Thread.currentThread()) { fatalAssert(MUST_NOT_ON_MAIN_THREAD_MSG); } } /** Verifies that current thread is the UI thread. */ public static void assertOnMainThread() { if (Looper.getMainLooper().getThread() != Thread.currentThread()) { fatalAssert(MUST_ON_MAIN_THREAD_MSG); } } /** * Exceptions thrown in background threads are silently swallowed on Android. Use the * uncaught exception handler of the UI thread to force the app to crash. */ public static void fatalAssert(final String message) { crashMainThread(new AssertionError(message)); } private static void crashMainThread(Throwable t) { Thread.UncaughtExceptionHandler uiThreadExceptionHandler = Looper.getMainLooper().getThread().getUncaughtExceptionHandler(); uiThreadExceptionHandler.uncaughtException(Thread.currentThread(), t); } } Loading
src/com/android/documentsui/DirectoryLoader.java +6 −6 Original line number Original line Diff line number Diff line Loading @@ -196,7 +196,7 @@ public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> { } else { } else { cursor = mModel.sortCursor(cursor, mFileTypeLookup); cursor = mModel.sortCursor(cursor, mFileTypeLookup); } } result.cursor = cursor; result.setCursor(cursor); } catch (Exception e) { } catch (Exception e) { Log.w(TAG, "Failed to query", e); Log.w(TAG, "Failed to query", e); result.exception = e; result.exception = e; Loading Loading @@ -305,8 +305,8 @@ public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> { // Ensure the loader is stopped // Ensure the loader is stopped onStopLoading(); onStopLoading(); if (mResult != null && mResult.cursor != null && mObserver != null) { if (mResult != null && mResult.getCursor() != null && mObserver != null) { mResult.cursor.unregisterContentObserver(mObserver); mResult.getCursor().unregisterContentObserver(mObserver); } } FileUtils.closeQuietly(mResult); FileUtils.closeQuietly(mResult); Loading @@ -314,10 +314,10 @@ public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> { } } private boolean checkIfCursorStale(DirectoryResult result) { private boolean checkIfCursorStale(DirectoryResult result) { if (result == null || result.cursor == null || result.cursor.isClosed()) { if (result == null || result.getCursor() == null || result.getCursor().isClosed()) { return true; return true; } } Cursor cursor = result.cursor; Cursor cursor = result.getCursor(); try { try { cursor.moveToPosition(-1); cursor.moveToPosition(-1); for (int pos = 0; pos < cursor.getCount(); ++pos) { for (int pos = 0; pos < cursor.getCount(); ++pos) { Loading
src/com/android/documentsui/DirectoryResult.java +71 −3 Original line number Original line Diff line number Diff line Loading @@ -16,29 +16,97 @@ package com.android.documentsui; package com.android.documentsui; import static com.android.documentsui.base.DocumentInfo.getCursorString; import android.content.ContentProviderClient; import android.content.ContentProviderClient; import android.database.Cursor; import android.database.Cursor; import android.os.FileUtils; import android.os.FileUtils; import android.provider.DocumentsContract; import android.util.Log; import com.android.documentsui.archives.ArchivesProvider; import com.android.documentsui.archives.ArchivesProvider; import com.android.documentsui.base.DocumentInfo; import com.android.documentsui.base.DocumentInfo; import java.util.HashSet; import java.util.Set; public class DirectoryResult implements AutoCloseable { public class DirectoryResult implements AutoCloseable { public Cursor cursor; private static final String TAG = "DirectoryResult"; public Exception exception; public Exception exception; public DocumentInfo doc; public DocumentInfo doc; ContentProviderClient client; ContentProviderClient client; private Cursor mCursor; private Set<String> mFileNames; private String[] mModelIds; @Override @Override public void close() { public void close() { FileUtils.closeQuietly(cursor); FileUtils.closeQuietly(mCursor); if (client != null && doc.isInArchive()) { if (client != null && doc.isInArchive()) { ArchivesProvider.releaseArchive(client, doc.derivedUri); ArchivesProvider.releaseArchive(client, doc.derivedUri); } } FileUtils.closeQuietly(client); FileUtils.closeQuietly(client); cursor = null; client = null; client = null; doc = null; doc = null; setCursor(null); } public Cursor getCursor() { return mCursor; } public String[] getModelIds() { return mModelIds; } public Set<String> getFileNames() { return mFileNames; } /** Update the cursor and populate cursor-related fields. */ public void setCursor(Cursor cursor) { mCursor = cursor; if (mCursor == null) { mFileNames = null; mModelIds = null; } else { loadDataFromCursor(); } } /** Populate cursor-related field. Must not be called from UI thread. */ private void loadDataFromCursor() { ThreadHelper.assertNotOnMainThread(); int cursorCount = mCursor.getCount(); String[] modelIds = new String[cursorCount]; Set<String> fileNames = new HashSet<>(); try { mCursor.moveToPosition(-1); for (int pos = 0; pos < cursorCount; ++pos) { if (!mCursor.moveToNext()) { Log.e(TAG, "Fail to move cursor to next pos: " + pos); return; } // Generates a Model ID for a cursor entry that refers to a document. The Model // ID is a unique string that can be used to identify the document referred to by // the cursor. Prefix the ids with the authority to avoid collisions. modelIds[pos] = ModelId.build(mCursor); fileNames.add( getCursorString(mCursor, DocumentsContract.Document.COLUMN_DISPLAY_NAME)); } } catch (Exception e) { Log.e(TAG, "Exception when moving cursor. Stale cursor?", e); return; } // Model related data is only non-null when no error iterating through cursor. mModelIds = modelIds; mFileNames = fileNames; } } } }
src/com/android/documentsui/Model.java +12 −31 Original line number Original line Diff line number Diff line Loading @@ -16,7 +16,6 @@ package com.android.documentsui; package com.android.documentsui; import static com.android.documentsui.base.DocumentInfo.getCursorString; import static com.android.documentsui.base.SharedMinimal.DEBUG; import static com.android.documentsui.base.SharedMinimal.DEBUG; import static com.android.documentsui.base.SharedMinimal.VERBOSE; import static com.android.documentsui.base.SharedMinimal.VERBOSE; Loading @@ -25,7 +24,6 @@ import android.database.Cursor; import android.net.Uri; import android.net.Uri; import android.os.Bundle; import android.os.Bundle; import android.provider.DocumentsContract; import android.provider.DocumentsContract; import android.provider.DocumentsContract.Document; import android.util.Log; import android.util.Log; import androidx.annotation.IntDef; import androidx.annotation.IntDef; Loading Loading @@ -125,11 +123,21 @@ public class Model { return; return; } } mCursor = result.cursor; mCursor = result.getCursor(); mCursorCount = mCursor.getCount(); mCursorCount = mCursor.getCount(); doc = result.doc; doc = result.doc; updateModelData(); if (result.getModelIds() != null && result.getFileNames() != null) { mIds = result.getModelIds(); mFileNames.clear(); mFileNames.addAll(result.getFileNames()); // Populate the positions. mPositions.clear(); for (int i = 0; i < mCursorCount; ++i) { mPositions.put(mIds[i], i); } } final Bundle extras = mCursor.getExtras(); final Bundle extras = mCursor.getExtras(); if (extras != null) { if (extras != null) { Loading @@ -146,33 +154,6 @@ public class Model { return mCursorCount; return mCursorCount; } } /** * Scan over the incoming cursor data, generate Model IDs for each row, and sort the IDs * according to the current sort order. */ private void updateModelData() { mIds = new String[mCursorCount]; mFileNames.clear(); mCursor.moveToPosition(-1); for (int pos = 0; pos < mCursorCount; ++pos) { if (!mCursor.moveToNext()) { Log.e(TAG, "Fail to move cursor to next pos: " + pos); return; } // Generates a Model ID for a cursor entry that refers to a document. The Model ID is a // unique string that can be used to identify the document referred to by the cursor. // Prefix the ids with the authority to avoid collisions. mIds[pos] = ModelId.build(mCursor); mFileNames.add(getCursorString(mCursor, Document.COLUMN_DISPLAY_NAME)); } // Populate the positions. mPositions.clear(); for (int i = 0; i < mCursorCount; ++i) { mPositions.put(mIds[i], i); } } public boolean hasFileWithName(String name) { public boolean hasFileWithName(String name) { return mFileNames.contains(name); return mFileNames.contains(name); } } Loading
src/com/android/documentsui/MultiRootDocumentsLoader.java +3 −3 Original line number Original line Diff line number Diff line Loading @@ -237,7 +237,7 @@ public abstract class MultiRootDocumentsLoader extends AsyncTaskLoader<Directory extras.putBoolean(DocumentsContract.EXTRA_LOADING, !allDone); extras.putBoolean(DocumentsContract.EXTRA_LOADING, !allDone); sorted.setExtras(extras); sorted.setExtras(extras); result.cursor = sorted; result.setCursor(sorted); return result; return result; } } Loading Loading @@ -458,10 +458,10 @@ public abstract class MultiRootDocumentsLoader extends AsyncTaskLoader<Directory } } private boolean checkIfCursorStale(DirectoryResult result) { private boolean checkIfCursorStale(DirectoryResult result) { if (result == null || result.cursor == null || result.cursor.isClosed()) { if (result == null || result.getCursor() == null || result.getCursor().isClosed()) { return true; return true; } } Cursor cursor = result.cursor; Cursor cursor = result.getCursor(); try { try { cursor.moveToPosition(-1); cursor.moveToPosition(-1); for (int pos = 0; pos < cursor.getCount(); ++pos) { for (int pos = 0; pos < cursor.getCount(); ++pos) { Loading
src/com/android/documentsui/ThreadHelper.java 0 → 100644 +58 −0 Original line number Original line Diff line number Diff line /* * Copyright (C) 2020 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; import android.os.Looper; /** Class for handler/thread utils. */ public final class ThreadHelper { private ThreadHelper() { } static final String MUST_NOT_ON_MAIN_THREAD_MSG = "This method should not be called on main thread."; static final String MUST_ON_MAIN_THREAD_MSG = "This method should only be called on main thread."; /** Verifies that current thread is not the UI thread. */ public static void assertNotOnMainThread() { if (Looper.getMainLooper().getThread() == Thread.currentThread()) { fatalAssert(MUST_NOT_ON_MAIN_THREAD_MSG); } } /** Verifies that current thread is the UI thread. */ public static void assertOnMainThread() { if (Looper.getMainLooper().getThread() != Thread.currentThread()) { fatalAssert(MUST_ON_MAIN_THREAD_MSG); } } /** * Exceptions thrown in background threads are silently swallowed on Android. Use the * uncaught exception handler of the UI thread to force the app to crash. */ public static void fatalAssert(final String message) { crashMainThread(new AssertionError(message)); } private static void crashMainThread(Throwable t) { Thread.UncaughtExceptionHandler uiThreadExceptionHandler = Looper.getMainLooper().getThread().getUncaughtExceptionHandler(); uiThreadExceptionHandler.uncaughtException(Thread.currentThread(), t); } }