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

Commit 401900c9 authored by Joanne Chung's avatar Joanne Chung
Browse files

Optimization: find views in single traversal.

The app will pass a list of views for Textview translation. Currently,
we traverse the view tree for each requested view. That means we will
traverse view tree many times. If the requested size of view list is
large, it shoule be a performance issue. We refine to find views in
sinslge traversal in this change.

Use a sample app with many views and layouts and compare the time of
two implementations. The average time of original solution is 300000
~ 600000 ns. The new approach is 150000 ~ 300000 ns.

Bug:178989965
Test: Write a sample app with many views and complicate structure,
see bug for test sample screenshot. Compare the execute time of two
implementation.

Change-Id: I5a051740ad40e77ed4a54294368031eb82ab87ab
parent b8737dac
Loading
Loading
Loading
Loading
+45 −11
Original line number Diff line number Diff line
@@ -32,6 +32,9 @@ import android.util.ArrayMap;
import android.util.Log;
import android.util.Pair;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewRootImpl;
import android.view.WindowManagerGlobal;
import android.view.autofill.AutofillId;
import android.view.translation.UiTranslationManager.UiTranslationState;

@@ -194,24 +197,19 @@ public class UiTranslationController {
    private void onUiTranslationStarted(Translator translator, List<AutofillId> views) {
        synchronized (mLock) {
            // Find Views collect the translation data
            // TODO(b/178084101): try to optimize, e.g. to this in a single traversal
            final int viewCounts = views.size();
            final ArrayList<TranslationRequest> requests = new ArrayList<>();
            for (int i = 0; i < viewCounts; i++) {
                final AutofillId viewAutofillId = views.get(i);
                final View view = mActivity.findViewByAutofillIdTraversal(viewAutofillId);
                if (view == null) {
                    Log.w(TAG, "Can not find the View for autofill id= " + viewAutofillId);
                    continue;
                }
                mViews.put(viewAutofillId, new WeakReference<>(view));
            final ArrayList<View> foundViews = new ArrayList<>();
            findViewsTraversalByAutofillIds(views, foundViews);
            for (int i = 0; i < foundViews.size(); i++) {
                final View view = foundViews.get(i);
                final int currentCount = i;
                mActivity.runOnUiThread(() -> {
                    final TranslationRequest translationRequest = view.onCreateTranslationRequest();
                    if (translationRequest != null
                            && translationRequest.getTranslationText().length() > 0) {
                        requests.add(translationRequest);
                    }
                    if (requests.size() == viewCounts) {
                    if (currentCount == (foundViews.size() - 1)) {
                        Log.v(TAG, "onUiTranslationStarted: send " + requests.size() + " request.");
                        mWorkerHandler.sendMessage(PooledLambda.obtainMessage(
                                UiTranslationController::sendTranslationRequest,
@@ -222,6 +220,42 @@ public class UiTranslationController {
        }
    }

    private void findViewsTraversalByAutofillIds(List<AutofillId> sourceViewIds,
            ArrayList<View> foundViews) {
        final ArrayList<ViewRootImpl> roots =
                WindowManagerGlobal.getInstance().getRootViews(mActivity.getActivityToken());
        for (int rootNum = 0; rootNum < roots.size(); rootNum++) {
            final View rootView = roots.get(rootNum).getView();
            if (rootView instanceof ViewGroup) {
                findViewsTraversalByAutofillIds((ViewGroup) rootView, sourceViewIds, foundViews);
            } else {
                addViewIfNeeded(sourceViewIds, rootView, foundViews);
            }
        }
    }

    private void findViewsTraversalByAutofillIds(ViewGroup viewGroup,
            List<AutofillId> sourceViewIds, ArrayList<View> foundViews) {
        final int childCount = viewGroup.getChildCount();
        for (int i = 0; i < childCount; ++i) {
            final View child = viewGroup.getChildAt(i);
            if (child instanceof ViewGroup) {
                findViewsTraversalByAutofillIds((ViewGroup) child, sourceViewIds, foundViews);
            } else {
                addViewIfNeeded(sourceViewIds, child, foundViews);
            }
        }
    }

    private void addViewIfNeeded(List<AutofillId> sourceViewIds, View view,
            ArrayList<View> foundViews) {
        final AutofillId autofillId = view.getAutofillId();
        if (sourceViewIds.contains(autofillId)) {
            mViews.put(autofillId, new WeakReference<>(view));
            foundViews.add(view);
        }
    }

    private void runForEachView(Consumer<View> action) {
        synchronized (mLock) {
            final ArrayMap<AutofillId, WeakReference<View>> views = new ArrayMap<>(mViews);