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

Commit dd820766 authored by Willie Koomson's avatar Willie Koomson Committed by Android (Google) Code Review
Browse files

Merge "Log widget tap and scroll events" into main

parents 13fdabdf 4f871ab5
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -2483,7 +2483,6 @@ package android {
    field public static final int primary = 16908300; // 0x102000c
    field public static final int progress = 16908301; // 0x102000d
    field public static final int redo = 16908339; // 0x1020033
    field @FlaggedApi("android.appwidget.flags.engagement_metrics") public static final int remoteViewsMetricsId;
    field public static final int replaceText = 16908340; // 0x1020034
    field public static final int secondaryProgress = 16908303; // 0x102000f
    field public static final int selectAll = 16908319; // 0x102001f
@@ -61755,6 +61754,7 @@ package android.widget {
    method public void setTextViewText(@IdRes int, CharSequence);
    method public void setTextViewTextSize(@IdRes int, int, float);
    method public void setUri(@IdRes int, String, android.net.Uri);
    method @FlaggedApi("android.appwidget.flags.engagement_metrics") public void setUsageEventTag(@IdRes int, int);
    method public void setViewLayoutHeight(@IdRes int, float, int);
    method public void setViewLayoutHeightAttr(@IdRes int, @AttrRes int);
    method public void setViewLayoutHeightDimen(@IdRes int, @DimenRes int);
+115 −24
Original line number Diff line number Diff line
@@ -16,10 +16,15 @@

package android.appwidget;

import static android.appwidget.flags.Flags.FLAG_ENGAGEMENT_METRICS;
import static android.appwidget.flags.Flags.engagementMetrics;

import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Activity;
import android.app.ActivityOptions;
import android.app.PendingIntent;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
@@ -38,6 +43,7 @@ import android.os.Build;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.Parcelable;
import android.util.ArraySet;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Pair;
@@ -48,6 +54,7 @@ import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.AbsListView;
import android.widget.Adapter;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
@@ -57,8 +64,11 @@ import android.widget.RemoteViews.InteractionHandler;
import android.widget.RemoteViewsAdapter.RemoteAdapterConnectionCallback;
import android.widget.TextView;

import com.android.internal.annotations.VisibleForTesting;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Executor;

/**
@@ -99,7 +109,8 @@ public class AppWidgetHostView extends FrameLayout implements AppWidgetHost.AppW
    int mViewMode = VIEW_MODE_NOINIT;
    // If true, we should not try to re-apply the RemoteViews on the next inflation.
    boolean mColorMappingChanged = false;
    private InteractionHandler mInteractionHandler;
    @NonNull
    private InteractionLogger mInteractionLogger = new InteractionLogger();
    private boolean mOnLightBackground;
    private SizeF mCurrentSize = null;
    private RemoteViews.ColorResources mColorResources = null;
@@ -124,7 +135,7 @@ public class AppWidgetHostView extends FrameLayout implements AppWidgetHost.AppW
     */
    public AppWidgetHostView(Context context, InteractionHandler handler) {
        this(context, android.R.anim.fade_in, android.R.anim.fade_out);
        mInteractionHandler = getHandler(handler);
        setInteractionHandler(handler);
    }

    /**
@@ -145,13 +156,29 @@ public class AppWidgetHostView extends FrameLayout implements AppWidgetHost.AppW

    /**
     * Pass the given handler to RemoteViews when updating this widget. Unless this
     * is done immediatly after construction, a call to {@link #updateAppWidget(RemoteViews)}
     * is done immediately after construction, a call to {@link #updateAppWidget(RemoteViews)}
     * should be made.
     *
     * @hide
     */
    public void setInteractionHandler(InteractionHandler handler) {
        mInteractionHandler = getHandler(handler);
        if (handler instanceof InteractionLogger logger) {
            // Nested AppWidgetHostViews should reuse the parent logger instead of wrapping it.
            mInteractionLogger = logger;
        } else {
            mInteractionLogger = new InteractionLogger(handler);
        }
    }

    /**
     * Return the InteractionLogger used by this class.
     *
     * @hide
     */
    @VisibleForTesting
    @NonNull
    public InteractionLogger getInteractionLogger() {
        return mInteractionLogger;
    }

    /**
@@ -588,7 +615,7 @@ public class AppWidgetHostView extends FrameLayout implements AppWidgetHost.AppW

            if (!mColorMappingChanged && rvToApply.canRecycleView(mView)) {
                try {
                    rvToApply.reapply(mContext, mView, mInteractionHandler, mCurrentSize,
                    rvToApply.reapply(mContext, mView, mInteractionLogger, mCurrentSize,
                            mColorResources);
                    content = mView;
                    mLastInflatedRemoteViewsId = rvToApply.computeUniqueId(remoteViews);
@@ -602,7 +629,7 @@ public class AppWidgetHostView extends FrameLayout implements AppWidgetHost.AppW
            // Try normal RemoteView inflation
            if (content == null) {
                try {
                    content = rvToApply.apply(mContext, this, mInteractionHandler,
                    content = rvToApply.apply(mContext, this, mInteractionLogger,
                            mCurrentSize, mColorResources);
                    mLastInflatedRemoteViewsId = rvToApply.computeUniqueId(remoteViews);
                    if (LOGD) Log.d(TAG, "had to inflate new layout");
@@ -660,7 +687,7 @@ public class AppWidgetHostView extends FrameLayout implements AppWidgetHost.AppW
                        mView,
                        mAsyncExecutor,
                        new ViewApplyListener(remoteViews, layoutId, true),
                        mInteractionHandler,
                        mInteractionLogger,
                        mCurrentSize,
                        mColorResources);
            } catch (Exception e) {
@@ -672,7 +699,7 @@ public class AppWidgetHostView extends FrameLayout implements AppWidgetHost.AppW
                    this,
                    mAsyncExecutor,
                    new ViewApplyListener(remoteViews, layoutId, false),
                    mInteractionHandler,
                    mInteractionLogger,
                    mCurrentSize,
                    mColorResources);
        }
@@ -711,7 +738,7 @@ public class AppWidgetHostView extends FrameLayout implements AppWidgetHost.AppW
                        AppWidgetHostView.this,
                        mAsyncExecutor,
                        new ViewApplyListener(mViews, mLayoutId, false),
                        mInteractionHandler,
                        mInteractionLogger,
                        mCurrentSize);
            } else {
                applyContent(null, false, e);
@@ -916,21 +943,6 @@ public class AppWidgetHostView extends FrameLayout implements AppWidgetHost.AppW
        return null;
    }

    private InteractionHandler getHandler(InteractionHandler handler) {
        return (view, pendingIntent, response) -> {
            AppWidgetManager manager = AppWidgetManager.getInstance(mContext);
            if (manager != null) {
                manager.noteAppWidgetTapped(mAppWidgetId);
            }
            if (handler != null) {
                return handler.onInteraction(view, pendingIntent, response);
            } else {
                return RemoteViews.startPendingIntent(view, pendingIntent,
                        response.getLaunchOptions(view));
            }
        };
    }

    /**
     * Set the dynamically overloaded color resources.
     *
@@ -1016,4 +1028,83 @@ public class AppWidgetHostView extends FrameLayout implements AppWidgetHost.AppW
            post(this::handleViewError);
        }
    }

    /**
     * This class is used to track user interactions with this widget.
     * @hide
     */
    public class InteractionLogger implements RemoteViews.InteractionHandler {
        // Max number of clicked and scrolled IDs stored per impression.
        public static final int MAX_NUM_ITEMS = 10;
        // Clicked views
        @NonNull
        private final Set<Integer> mClickedIds = new ArraySet<>(MAX_NUM_ITEMS);
        // Scrolled views
        @NonNull
        private final Set<Integer> mScrolledIds = new ArraySet<>(MAX_NUM_ITEMS);
        @Nullable
        private RemoteViews.InteractionHandler mInteractionHandler = null;

        InteractionLogger() {
        }

        InteractionLogger(@Nullable InteractionHandler handler) {
            mInteractionHandler = handler;
        }

        @VisibleForTesting
        @NonNull
        public Set<Integer> getClickedIds() {
            return mClickedIds;
        }

        @VisibleForTesting
        @NonNull
        public Set<Integer> getScrolledIds() {
            return mScrolledIds;
        }

        @Override
        public boolean onInteraction(View view, PendingIntent pendingIntent,
                RemoteViews.RemoteResponse response) {
            if (engagementMetrics() && mClickedIds.size() < MAX_NUM_ITEMS) {
                mClickedIds.add(getMetricsId(view));
            }
            AppWidgetManager manager = AppWidgetManager.getInstance(mContext);
            if (manager != null) {
                manager.noteAppWidgetTapped(mAppWidgetId);
            }

            if (mInteractionHandler != null) {
                return mInteractionHandler.onInteraction(view, pendingIntent, response);
            } else {
                return RemoteViews.startPendingIntent(view, pendingIntent,
                        response.getLaunchOptions(view));
            }
        }

        @Override
        public void onScroll(@NonNull AbsListView view) {
            if (!engagementMetrics()) return;

            if (mScrolledIds.size() < MAX_NUM_ITEMS) {
                mScrolledIds.add(getMetricsId(view));
            }

            if (mInteractionHandler != null) {
                mInteractionHandler.onScroll(view);
            }
        }

        @FlaggedApi(FLAG_ENGAGEMENT_METRICS)
        private int getMetricsId(@NonNull View view) {
            int viewId = view.getId();
            Object metricsTag = view.getTag(com.android.internal.R.id.remoteViewsMetricsId);
            if (metricsTag instanceof Integer tag) {
                viewId = tag;
            }
            return viewId;
        }
    }
}
+10 −8
Original line number Diff line number Diff line
@@ -515,12 +515,13 @@ public class AppWidgetManager {

    /**
     * This bundle extra describes which views have been clicked during a single impression of the
     * widget. It is an integer array of view IDs of the clicked views.
     * widget. It is an integer array of view IDs of the clicked views. The array may contain up to
     * 10 distinct IDs per event.
     *
     * Widget providers may set a different ID for event purposes by setting the
     * {@link android.R.id.remoteViewsMetricsId} int tag on the view.
     * Widget providers may set a different ID for event logging by setting the usage event tag on
     * the view with {@link RemoteViews#setUsageEventTag}.
     *
     * @see android.views.RemoteViews.setIntTag
     * @see android.widget.RemoteViews#setUsageEventTag
     */
    @FlaggedApi(Flags.FLAG_ENGAGEMENT_METRICS)
    public static final String EXTRA_EVENT_CLICKED_VIEWS =
@@ -528,12 +529,13 @@ public class AppWidgetManager {

    /**
     * This bundle extra describes which views have been scrolled during a single impression of the
     * widget. It is an integer array of view IDs of the scrolled views.
     * widget. It is an integer array of view IDs of the scrolled views. The array may contain up to
     * 10 distinct IDs per event.
     *
     * Widget providers may set a different ID for event purposes by setting the
     * {@link android.R.id.remoteViewsMetricsId} int tag on the view.
     * Widget providers may set a different ID for event logging by setting the usage event tag on
     * the view with {@link RemoteViews#setUsageEventTag}.
     *
     * @see android.views.RemoteViews.setIntTag
     * @see android.widget.RemoteViews#setUsageEventTag
     */
    @FlaggedApi(Flags.FLAG_ENGAGEMENT_METRICS)
    public static final String EXTRA_EVENT_SCROLLED_VIEWS =
+57 −1
Original line number Diff line number Diff line
@@ -547,6 +547,25 @@ public class RemoteViews implements Parcelable, Filter {
        addAction(new SetIntTagAction(viewId, key, tag));
    }

    /**
     * Set a view tag associating a View with an ID to be used for widget interaction usage events
     * ({@link android.app.usage.UsageEvents.Event}). When this RemoteViews is applied to a bound
     * widget, any clicks or scrolls on the tagged view will be reported to
     * {@link android.app.usage.UsageStatsManager} using this tag.
     *
     * @param viewId ID of the View whose tag will be set
     * @param tag The integer tag to use for the event
     *
     * @see android.appwidget.AppWidgetManager#EVENT_TYPE_WIDGET_INTERACTION
     * @see android.appwidget.AppWidgetManager#EXTRA_EVENT_CLICKED_VIEWS
     * @see android.appwidget.AppWidgetManager#EXTRA_EVENT_SCROLLED_VIEWS
     * @see android.app.usage.UsageStatsManager#queryEventsForSelf
     */
    @FlaggedApi(Flags.FLAG_ENGAGEMENT_METRICS)
    public void setUsageEventTag(@IdRes int viewId, int tag) {
        addAction(new SetIntTagAction(viewId, com.android.internal.R.id.remoteViewsMetricsId, tag));
    }

    /**
     * Set that it is disallowed to reapply another remoteview with the same layout as this view.
     * This should be done if an action is destroying the view tree of the base layout.
@@ -666,6 +685,14 @@ public class RemoteViews implements Parcelable, Filter {
                View view,
                PendingIntent pendingIntent,
                RemoteResponse response);

        /**
         * Invoked when an AbsListView is scrolled.
         * @param view view that was scrolled
         *
         * @hide
         */
        default void onScroll(@NonNull AbsListView view) {}
    }

    /**
@@ -1313,6 +1340,21 @@ public class RemoteViews implements Parcelable, Filter {
                // a type error.
                throw new ActionException(throwable);
            }
            if (adapterView instanceof AbsListView listView) {
                listView.setOnScrollListener(new AbsListView.OnScrollListener() {
                    @Override
                    public void onScrollStateChanged(AbsListView view, int scrollState) {
                        if (scrollState != SCROLL_STATE_IDLE) {
                            params.handler.onScroll(view);
                        }
                    }

                    @Override
                    public void onScroll(AbsListView view, int firstVisibleItem,
                            int visibleItemCount, int totalItemCount) {
                    }
                });
            }
        }

        @Override
@@ -1804,6 +1846,19 @@ public class RemoteViews implements Parcelable, Filter {
                AbsListView v = (AbsListView) target;
                v.setRemoteViewsAdapter(mIntent, mIsAsync);
                v.setRemoteViewsInteractionHandler(params.handler);
                v.setOnScrollListener(new AbsListView.OnScrollListener() {
                    @Override
                    public void onScrollStateChanged(AbsListView view, int scrollState) {
                        if (scrollState != SCROLL_STATE_IDLE) {
                            params.handler.onScroll(view);
                        }
                    }

                    @Override
                    public void onScroll(AbsListView view, int firstVisibleItem,
                            int visibleItemCount, int totalItemCount) {
                    }
                });
            } else if (target instanceof AdapterViewAnimator) {
                AdapterViewAnimator v = (AdapterViewAnimator) target;
                v.setRemoteViewsAdapter(mIntent, mIsAsync);
@@ -1894,7 +1949,8 @@ public class RemoteViews implements Parcelable, Filter {
                target.setTagInternal(com.android.internal.R.id.fillInIntent, null);
                return;
            }
            target.setOnClickListener(v -> mResponse.handleViewInteraction(v, params.handler));
            target.setOnClickListener(v ->
                    mResponse.handleViewInteraction(v, params.handler));
        }

        @Override
+1 −1
Original line number Diff line number Diff line
@@ -128,7 +128,7 @@

  <staging-public-group type="id" first-id="0x01b20000">
    <!-- @FlaggedApi(android.appwidget.flags.Flags.FLAG_ENGAGEMENT_METRICS) -->
    <public name="remoteViewsMetricsId"/>
    <public name="removed_remoteViewsMetricsId"/>
    <!-- @FlaggedApi("android.view.accessibility.a11y_selection_api")  -->
    <public name="accessibilityActionSetExtendedSelection"/>
  </staging-public-group>
Loading