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

Commit 20409bd6 authored by Steven Terrell's avatar Steven Terrell Committed by Android (Google) Code Review
Browse files

Merge "Add State Changes to ListView" into main

parents 997901f7 731505f0
Loading
Loading
Loading
Loading
+28 −0
Original line number Diff line number Diff line
@@ -510,6 +510,14 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123769353)
    private OnScrollListener mOnScrollListener;

    /**
     * Optional callback to notify client when scroll state has changed. This is used internally
     * to track state changes for Jank metrics. A separate OnScrollListener is used in order to
     * avoid overwriting any existing OnScrollListener apps may have set.
     */
    @UnsupportedAppUsage
    private OnScrollListener mOnScrollStateChangeListener;

    /**
     * Keeps track of our accessory window
     */
@@ -653,6 +661,12 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
     */
    private int mLastScrollState = OnScrollListener.SCROLL_STATE_IDLE;

    /**
     * The last scroll state reported to {@link #mOnScrollStateChangeListener}, used for internal
     * tracking of scroll state changes.
     */
    private int mPreviousOnScrollListenerState = OnScrollListener.SCROLL_STATE_IDLE;

    /**
     * Indicates that reporting positions of child views to content capture is enabled via
     * DeviceConfig.
@@ -1609,6 +1623,15 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
        onScrollChanged(0, 0, 0, 0);
    }

    /**
     * Set the listener that will receive notifications only when scroll state changes.
     *
     * @hide
     */
    protected void setOnScrollStateChangeListener(OnScrollListener listener) {
        mOnScrollStateChangeListener = listener;
    }

    /**
     * A TYPE_VIEW_SCROLLED event should be sent whenever a scroll happens, even if the
     * mFirstPosition and the child count have not changed.
@@ -4918,6 +4941,11 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
            }
        }

        if (newState != mPreviousOnScrollListenerState && mOnScrollStateChangeListener != null) {
            mPreviousOnScrollListenerState = newState;
            mOnScrollStateChangeListener.onScrollStateChanged(this, newState);
        }

        // When scrolling, we want to report changes in the active children to Content Capture,
        // so set the flag to report on the next update only when scrolling has stopped or a fling
        // scroll is performed.
+58 −0
Original line number Diff line number Diff line
@@ -19,6 +19,8 @@ package android.widget;
import android.annotation.IdRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.jank.AppJankStats;
import android.app.jank.JankTracker;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.Intent;
@@ -213,6 +215,12 @@ public class ListView extends AbsListView {
    // Keeps focused children visible through resizes
    private FocusSelector mFocusSelector;

    // Associates scroll state changes to frame counts for jank metric reporting.
    private JankTracker mJankTracker;
    // Used to keep track of scroll state transitions for jank metrics. Value of -1 indicates no
    // previous state has been set.
    private int mPreviousScrollState = -1;

    public ListView(Context context) {
        this(context, null);
    }
@@ -268,6 +276,56 @@ public class ListView extends AbsListView {
        mFooterDividersEnabled = a.getBoolean(R.styleable.ListView_footerDividersEnabled, true);

        a.recycle();

        if (android.app.jank.Flags.instrumentListviewScrollStates()) {
            initializeScrollStateTracking(String.valueOf(this.getId()));
        }
    }

    private void initializeScrollStateTracking(String widgetId) {
        this.setOnScrollStateChangeListener(new OnScrollListener() {
            @Override
            public void onScrollStateChanged(AbsListView view, int scrollState) {
                if (mJankTracker == null) {
                    mJankTracker = view.getJankTracker();
                }
                // Certain apps are not supported based on their app category. In unsupported apps
                // JankTracker will always be null.
                if (mJankTracker != null && scrollState != mPreviousScrollState) {
                    if (mPreviousScrollState == -1) {
                        mJankTracker.addUiState(AppJankStats.WIDGET_CATEGORY_SCROLL,
                                widgetId,
                                getWidgetStateFromScrollState(scrollState));
                    } else {
                        mJankTracker.updateUiState(AppJankStats.WIDGET_CATEGORY_SCROLL, widgetId,
                                getWidgetStateFromScrollState(mPreviousScrollState),
                                getWidgetStateFromScrollState(scrollState));
                    }
                    mPreviousScrollState = scrollState;
                }
            }

            @Override
            public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
                    int totalItemCount) {
                // onScroll is not needed, for jank tracking we are only concerned with state
                // transitions, not item count and visibility.

            }
        });
    }

    private String getWidgetStateFromScrollState(int scrollState) {
        switch (scrollState) {
            case OnScrollListener.SCROLL_STATE_FLING -> {
                return AppJankStats.WIDGET_STATE_FLINGING;
            } case OnScrollListener.SCROLL_STATE_TOUCH_SCROLL -> {
                return AppJankStats.WIDGET_STATE_SCROLLING;
            }
            default -> {
                return AppJankStats.WIDGET_STATE_NONE;
            }
        }
    }

    /**
+7 −0
Original line number Diff line number Diff line
@@ -36,6 +36,13 @@
                <action android:name="android.intent.action.VIEW_PERMISSION_USAGE"/>
            </intent-filter>
        </activity>
        <activity android:name=".ListViewActivity"
            android:exported="true"
            android:label="ListViewActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
            </intent-filter>
        </activity>
        <uses-library android:name="android.test.runner"/>
    </application>

+31 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!--
  ~ Copyright (C) 2025 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.
  -->


<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/linear_layout_id"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <ListView
        android:id="@+id/list_view_id"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:contentDescription="TestList"
        />
</LinearLayout>
+8 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<TextView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@android:id/text1"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="16dp"
    android:textSize="16sp"/>
Loading