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

Commit eda268a4 authored by Zemiao Zhu's avatar Zemiao Zhu
Browse files

Unblock UI thread from updating model.

The model data is loaded from DirectoryResult.cursor, so it makes more sense
to populate all relevant data directly when we generate DirectoryResult
in the background.

Bug: 169449744
Test: atest DocumentsUIGoogleTests
Change-Id: I26557f9fe93ab6cb01599c51f43c0f393e17a2a3
parent a9219f8a
Loading
Loading
Loading
Loading
+6 −6
Original line number Original line Diff line number Diff line
@@ -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;
@@ -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);
@@ -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) {
+71 −3
Original line number Original line Diff line number Diff line
@@ -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;
    }
    }
}
}
+12 −31
Original line number Original line Diff line number Diff line
@@ -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;


@@ -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;
@@ -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) {
@@ -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);
    }
    }
+3 −3
Original line number Original line Diff line number Diff line
@@ -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;
    }
    }
@@ -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) {
+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