Loading core/java/android/appwidget/AppWidgetEvent.aidl 0 → 100644 +19 −0 Original line number Diff line number Diff line /* * 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. */ package android.appwidget; parcelable AppWidgetEvent; core/java/android/appwidget/AppWidgetEvent.java 0 → 100644 +285 −0 Original line number Diff line number Diff line /* * 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. */ package android.appwidget; import static android.appwidget.flags.Flags.FLAG_ENGAGEMENT_METRICS; import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.usage.UsageStatsManager; import android.graphics.Rect; import android.os.Parcel; import android.os.Parcelable; import android.os.PersistableBundle; import android.text.TextUtils; import android.util.ArraySet; import java.util.Arrays; /** * An immutable class that describes the event data for an app widget interaction event. * * @hide */ @FlaggedApi(FLAG_ENGAGEMENT_METRICS) public class AppWidgetEvent implements Parcelable { /** * Max number of clicked and scrolled IDs stored per event. */ public static final int MAX_NUM_ITEMS = 10; private final int mAppWidgetId; private final long mDurationMs; @Nullable private final Rect mPosition; @Nullable private final int[] mClickedIds; @Nullable private final int[] mScrolledIds; /** * The app widget ID of the widget that generated this event. */ public int getAppWidgetId() { return mAppWidgetId; } /** * This contains a long that represents the duration of time in milliseconds during which the * widget was visible. */ public long getDurationMs() { return mDurationMs; } /** * This rect with describes the global coordinates of the widget at the end of the interaction * event. */ @Nullable public Rect getPosition() { return mPosition; } /** * This describes which views have been clicked during a single impression of the widget. */ @Nullable public int[] getClickedIds() { return mClickedIds; } /** * This describes which views have been scrolled during a single impression of the widget. */ @Nullable public int[] getScrolledIds() { return mScrolledIds; } private AppWidgetEvent(int appWidgetId, long durationMs, @Nullable Rect position, @Nullable int[] clickedIds, @Nullable int[] scrolledIds) { mAppWidgetId = appWidgetId; mDurationMs = durationMs; mPosition = position; mClickedIds = clickedIds; mScrolledIds = scrolledIds; } /** * Unflatten the AppWidgetEvent from a parcel. */ private AppWidgetEvent(Parcel in) { mAppWidgetId = in.readInt(); mDurationMs = in.readLong(); mPosition = in.readTypedObject(Rect.CREATOR); mClickedIds = in.createIntArray(); mScrolledIds = in.createIntArray(); } @Override public void writeToParcel(Parcel out, int flags) { out.writeInt(mAppWidgetId); out.writeLong(mDurationMs); out.writeTypedObject(mPosition, flags); out.writeIntArray(mClickedIds); out.writeIntArray(mScrolledIds); } /** * Parcelable.Creator that instantiates AppWidgetEvent objects */ public static final @android.annotation.NonNull Parcelable.Creator<AppWidgetEvent> CREATOR = new Parcelable.Creator<>() { public AppWidgetEvent createFromParcel(Parcel parcel) { return new AppWidgetEvent(parcel); } public AppWidgetEvent[] newArray(int size) { return new AppWidgetEvent[size]; } }; @Override public int describeContents() { return 0; } /** * Create a PersistableBundle that represents this event. */ @NonNull public PersistableBundle toBundle() { PersistableBundle extras = new PersistableBundle(); extras.putString(UsageStatsManager.EXTRA_EVENT_ACTION, AppWidgetManager.EVENT_TYPE_WIDGET_INTERACTION); extras.putString(UsageStatsManager.EXTRA_EVENT_CATEGORY, AppWidgetManager.EVENT_CATEGORY_APPWIDGET); extras.putInt(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId); extras.putLong(AppWidgetManager.EXTRA_EVENT_DURATION_MS, mDurationMs); if (mPosition != null) { extras.putIntArray(AppWidgetManager.EXTRA_EVENT_POSITION_RECT, new int[]{mPosition.left, mPosition.top, mPosition.right, mPosition.bottom}); } if (mClickedIds != null && mClickedIds.length > 0) { extras.putIntArray(AppWidgetManager.EXTRA_EVENT_CLICKED_VIEWS, mClickedIds); } if (mScrolledIds != null && mScrolledIds.length > 0) { extras.putIntArray(AppWidgetManager.EXTRA_EVENT_SCROLLED_VIEWS, mScrolledIds); } return extras; } @Override public String toString() { return TextUtils.formatSimple("AppWidgetEvent(appWidgetId=%d, durationMs=%d, position=%s," + " clickedIds=%s, scrolledIds=%s)", mAppWidgetId, mDurationMs, mPosition, Arrays.toString(mClickedIds), Arrays.toString(mScrolledIds)); } /** * Builder class to construct AppWidgetEvent objects. * * @hide */ public static class Builder { @NonNull private final ArraySet<Integer> mClickedIds = new ArraySet<>(MAX_NUM_ITEMS); @NonNull private final ArraySet<Integer> mScrolledIds = new ArraySet<>(MAX_NUM_ITEMS); private int mAppWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID; private long mDurationMs = 0; @Nullable private Rect mPosition = null; public Builder() { } public Builder setAppWidgetId(int appWidgetId) { mAppWidgetId = appWidgetId; return this; } public Builder addDurationMs(long durationMs) { mDurationMs += durationMs; return this; } public Builder setPosition(@Nullable Rect position) { mPosition = position; return this; } public Builder addClickedId(int id) { if (mClickedIds.size() < MAX_NUM_ITEMS) { mClickedIds.add(id); } return this; } public Builder addScrolledId(int id) { if (mScrolledIds.size() < MAX_NUM_ITEMS) { mScrolledIds.add(id); } return this; } /** * Merge the given event's data into this event's data. */ public void merge(@Nullable AppWidgetEvent event) { if (event == null) { return; } if (mAppWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) { setAppWidgetId(event.getAppWidgetId()); } else if (mAppWidgetId != event.getAppWidgetId()) { throw new IllegalArgumentException("Trying to merge events with different app " + "widget IDs: " + mAppWidgetId + " != " + event.getAppWidgetId()); } addDurationMs(event.getDurationMs()); setPosition(event.getPosition()); addAllUntilMax(mClickedIds, event.getClickedIds()); addAllUntilMax(mScrolledIds, event.getScrolledIds()); } /** * Returns true if the app widget ID has not been set, or if no data has been added to this * event yet. */ public boolean isEmpty() { return mAppWidgetId <= 0 || mDurationMs == 0L; } /** * Resets the event data fields. */ public void clear() { mDurationMs = 0; mPosition = null; mClickedIds.clear(); mScrolledIds.clear(); } public AppWidgetEvent build() { return new AppWidgetEvent(mAppWidgetId, mDurationMs, mPosition, toIntArray(mClickedIds), toIntArray(mScrolledIds)); } private static void addAllUntilMax(@NonNull ArraySet<Integer> set, @Nullable int[] toAdd) { if (toAdd == null) { return; } for (int i = 0; i < toAdd.length && set.size() < MAX_NUM_ITEMS; i++) { set.add(toAdd[i]); } } @Nullable private static int[] toIntArray(@NonNull ArraySet<Integer> set) { if (set.isEmpty()) return null; int[] array = new int[set.size()]; for (int i = 0; i < array.length; i++) { array[i] = set.valueAt(i); } return array; } } } core/java/android/appwidget/AppWidgetHost.java +14 −9 Original line number Diff line number Diff line Loading @@ -16,6 +16,10 @@ 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; Loading @@ -31,7 +35,6 @@ import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.PersistableBundle; import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; Loading @@ -47,6 +50,7 @@ import com.android.internal.appwidget.IAppWidgetService; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** Loading Loading @@ -564,8 +568,9 @@ public class AppWidgetHost { * * @hide */ @FlaggedApi(FLAG_ENGAGEMENT_METRICS) @Nullable default PersistableBundle collectWidgetEvent() { default AppWidgetEvent collectWidgetEvent() { return null; } } Loading Loading @@ -663,14 +668,14 @@ public class AppWidgetHost { * @hide */ public void reportAllWidgetEvents() { if (sService == null) { if (sService == null || !engagementMetrics()) { return; } List<PersistableBundle> eventList = new ArrayList<>(); List<AppWidgetEvent> eventList = new ArrayList<>(); synchronized (mListeners) { for (int i = 0; i < mListeners.size(); i++) { PersistableBundle event = mListeners.valueAt(i).collectWidgetEvent(); AppWidgetEvent event = mListeners.valueAt(i).collectWidgetEvent(); if (event != null) { eventList.add(event); } Loading @@ -679,7 +684,7 @@ public class AppWidgetHost { if (eventList.isEmpty()) { return; } PersistableBundle[] events = new PersistableBundle[eventList.size()]; AppWidgetEvent[] events = new AppWidgetEvent[eventList.size()]; for (int i = 0; i < events.length; i++) { events[i] = eventList.get(i); } Loading @@ -697,18 +702,18 @@ public class AppWidgetHost { * @hide */ public void reportEventForWidget(int appWidgetId) { if (sService == null) { if (sService == null || !engagementMetrics()) { return; } AppWidgetHostListener listener = getListener(appWidgetId); if (listener == null) { return; } PersistableBundle event = listener.collectWidgetEvent(); AppWidgetEvent event = listener.collectWidgetEvent(); if (event == null) { return; } PersistableBundle[] events = {event}; AppWidgetEvent[] events = {event}; try { sService.reportWidgetEvents(mContextOpPackageName, events); Loading core/java/android/appwidget/AppWidgetHostView.java +27 −68 Original line number Diff line number Diff line Loading @@ -43,9 +43,7 @@ import android.os.Build; import android.os.Bundle; import android.os.CancellationSignal; import android.os.Parcelable; import android.os.PersistableBundle; import android.os.SystemClock; import android.util.ArraySet; import android.util.AttributeSet; import android.util.Log; import android.util.Pair; Loading @@ -71,7 +69,6 @@ import com.android.internal.annotations.VisibleForTesting; import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.concurrent.Executor; /** Loading Loading @@ -1048,8 +1045,9 @@ public class AppWidgetHostView extends FrameLayout implements AppWidgetHost.AppW * * @hide */ @FlaggedApi(FLAG_ENGAGEMENT_METRICS) @Override public PersistableBundle collectWidgetEvent() { public AppWidgetEvent collectWidgetEvent() { return mInteractionLogger.collectWidgetEvent(); } Loading @@ -1058,23 +1056,13 @@ public class AppWidgetHostView extends FrameLayout implements AppWidgetHost.AppW * @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; // Determines the minimum time between calls to updateVisibility(). private static final long UPDATE_VISIBILITY_DELAY_MS = 1000L; // Clicked views @NonNull private final ArraySet<Integer> mClickedIds = new ArraySet<>(MAX_NUM_ITEMS); // Scrolled views @NonNull private final ArraySet<Integer> mScrolledIds = new ArraySet<>(MAX_NUM_ITEMS); private final AppWidgetEvent.Builder mEvent = new AppWidgetEvent.Builder(); @Nullable private RemoteViews.InteractionHandler mInteractionHandler = null; // Last position this widget was laid out in @Nullable private Rect mPosition = null; // Total duration for the impression private long mDurationMs = 0L; // Holds event data since last report. // Last time the widget became visible in SystemClock.uptimeMillis() private long mVisibilityChangeMs = 0L; private boolean mIsVisible = false; Loading @@ -1087,34 +1075,19 @@ public class AppWidgetHostView extends FrameLayout implements AppWidgetHost.AppW mInteractionHandler = handler; } /** * Return the current AppWidgetEvent without clearing the tracked data. */ @VisibleForTesting @NonNull public Set<Integer> getClickedIds() { return mClickedIds; } @VisibleForTesting @NonNull public Set<Integer> getScrolledIds() { return mScrolledIds; } @VisibleForTesting public long getDurationMs() { return mDurationMs; } @VisibleForTesting @Nullable public Rect getPosition() { return mPosition; public AppWidgetEvent getEvent() { return mEvent.build(); } @Override public boolean onInteraction(View view, PendingIntent pendingIntent, RemoteViews.RemoteResponse response) { if (engagementMetrics() && mClickedIds.size() < MAX_NUM_ITEMS) { mClickedIds.add(getMetricsId(view)); if (engagementMetrics()) { mEvent.addClickedId(getMetricsId(view)); } AppWidgetManager manager = AppWidgetManager.getInstance(mContext); if (manager != null) { Loading @@ -1133,10 +1106,7 @@ public class AppWidgetHostView extends FrameLayout implements AppWidgetHost.AppW public void onScroll(@NonNull AbsListView view) { if (!engagementMetrics()) return; if (mScrolledIds.size() < MAX_NUM_ITEMS) { mScrolledIds.add(getMetricsId(view)); } mEvent.addScrolledId(getMetricsId(view)); if (mInteractionHandler != null) { mInteractionHandler.onScroll(view); } Loading @@ -1157,9 +1127,10 @@ public class AppWidgetHostView extends FrameLayout implements AppWidgetHost.AppW */ private void onPositionChanged() { if (!engagementMetrics()) return; mPosition = new Rect(); if (getGlobalVisibleRect(mPosition)) { applyScrollOffset(); Rect position = new Rect(); if (getGlobalVisibleRect(position)) { applyScrollOffset(position); mEvent.setPosition(position); } } Loading @@ -1167,8 +1138,8 @@ public class AppWidgetHostView extends FrameLayout implements AppWidgetHost.AppW * Finds the first parent with a scrollX or scrollY offset and applies it to the current * position Rect. This corresponds to the current "page" of this widget on its workspace. */ private void applyScrollOffset() { if (mPosition == null) return; private void applyScrollOffset(@Nullable Rect position) { if (position == null) return; int dx = 0; int dy = 0; for (ViewParent parent = getParent(); parent != null; parent = parent.getParent()) { Loading @@ -1179,7 +1150,7 @@ public class AppWidgetHostView extends FrameLayout implements AppWidgetHost.AppW break; } } mPosition.offset(dx, dy); position.offset(dx, dy); } private void onDraw() { Loading Loading @@ -1230,7 +1201,7 @@ public class AppWidgetHostView extends FrameLayout implements AppWidgetHost.AppW mVisibilityChangeMs = SystemClock.uptimeMillis(); } else if (wasVisible && !isVisible) { // View is no longer visible, add duration. mDurationMs += SystemClock.uptimeMillis() - mVisibilityChangeMs; mEvent.addDurationMs(SystemClock.uptimeMillis() - mVisibilityChangeMs); } mIsVisible = isVisible; Loading @@ -1243,33 +1214,21 @@ public class AppWidgetHostView extends FrameLayout implements AppWidgetHost.AppW } @Nullable private PersistableBundle collectWidgetEvent() { private AppWidgetEvent collectWidgetEvent() { if (!engagementMetrics()) return null; if (mIsVisible) { // If the widget is currently visible, add the current duration to the event data. updateVisibility(false); } if (mAppWidgetId <= 0 || mDurationMs == 0L) { mEvent.setAppWidgetId(mAppWidgetId); if (mEvent.isEmpty()) { return null; } PersistableBundle event = AppWidgetManager.createWidgetInteractionEvent(mAppWidgetId, mDurationMs, mPosition, toIntArray(mClickedIds), toIntArray(mScrolledIds)); mClickedIds.clear(); mScrolledIds.clear(); mDurationMs = 0; mPosition = null; AppWidgetEvent event = mEvent.build(); mEvent.clear(); return event; } private static int[] toIntArray(ArraySet<Integer> set) { if (set.isEmpty()) return null; int[] array = new int[set.size()]; for (int i = 0; i < array.length; i++) { array[i] = set.valueAt(i); } return array; } } } core/java/android/appwidget/AppWidgetManager.java +0 −35 Original line number Diff line number Diff line Loading @@ -43,7 +43,6 @@ import android.content.ServiceConnection; import android.content.pm.PackageManager; import android.content.pm.ParceledListSlice; import android.content.pm.ShortcutInfo; import android.graphics.Rect; import android.os.Binder; import android.os.Build; import android.os.Bundle; Loading @@ -52,7 +51,6 @@ import android.os.HandlerExecutor; import android.os.HandlerThread; import android.os.IBinder; import android.os.Looper; import android.os.PersistableBundle; import android.os.Process; import android.os.RemoteException; import android.os.UserHandle; Loading Loading @@ -1593,39 +1591,6 @@ public class AppWidgetManager { } } /** * Create a {@link PersistableBundle} that represents a single widget interaction event. * * @param appWidgetId App Widget ID of the widget. * @param durationMs Duration of the impression in milliseconds * @param position Current position of the widget. * @param clickedIds IDs of views clicked during this event. * @param scrolledIds IDs of views scrolled during this event. * * @hide */ @FlaggedApi(Flags.FLAG_ENGAGEMENT_METRICS) @NonNull public static PersistableBundle createWidgetInteractionEvent(int appWidgetId, long durationMs, @Nullable Rect position, @Nullable int[] clickedIds, @Nullable int[] scrolledIds) { PersistableBundle extras = new PersistableBundle(); extras.putString(UsageStatsManager.EXTRA_EVENT_ACTION, EVENT_TYPE_WIDGET_INTERACTION); extras.putString(UsageStatsManager.EXTRA_EVENT_CATEGORY, EVENT_CATEGORY_APPWIDGET); extras.putInt(EXTRA_APPWIDGET_ID, appWidgetId); extras.putLong(EXTRA_EVENT_DURATION_MS, durationMs); if (position != null) { extras.putIntArray(EXTRA_EVENT_POSITION_RECT, new int[]{position.left, position.top, position.right, position.bottom}); } if (clickedIds != null && clickedIds.length > 0) { extras.putIntArray(EXTRA_EVENT_CLICKED_VIEWS, clickedIds); } if (scrolledIds != null && scrolledIds.length > 0) { extras.putIntArray(EXTRA_EVENT_SCROLLED_VIEWS, scrolledIds); } return extras; } @UiThread private static @NonNull Executor createUpdateExecutorIfNull() { if (sUpdateExecutor == null) { Loading Loading
core/java/android/appwidget/AppWidgetEvent.aidl 0 → 100644 +19 −0 Original line number Diff line number Diff line /* * 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. */ package android.appwidget; parcelable AppWidgetEvent;
core/java/android/appwidget/AppWidgetEvent.java 0 → 100644 +285 −0 Original line number Diff line number Diff line /* * 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. */ package android.appwidget; import static android.appwidget.flags.Flags.FLAG_ENGAGEMENT_METRICS; import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.usage.UsageStatsManager; import android.graphics.Rect; import android.os.Parcel; import android.os.Parcelable; import android.os.PersistableBundle; import android.text.TextUtils; import android.util.ArraySet; import java.util.Arrays; /** * An immutable class that describes the event data for an app widget interaction event. * * @hide */ @FlaggedApi(FLAG_ENGAGEMENT_METRICS) public class AppWidgetEvent implements Parcelable { /** * Max number of clicked and scrolled IDs stored per event. */ public static final int MAX_NUM_ITEMS = 10; private final int mAppWidgetId; private final long mDurationMs; @Nullable private final Rect mPosition; @Nullable private final int[] mClickedIds; @Nullable private final int[] mScrolledIds; /** * The app widget ID of the widget that generated this event. */ public int getAppWidgetId() { return mAppWidgetId; } /** * This contains a long that represents the duration of time in milliseconds during which the * widget was visible. */ public long getDurationMs() { return mDurationMs; } /** * This rect with describes the global coordinates of the widget at the end of the interaction * event. */ @Nullable public Rect getPosition() { return mPosition; } /** * This describes which views have been clicked during a single impression of the widget. */ @Nullable public int[] getClickedIds() { return mClickedIds; } /** * This describes which views have been scrolled during a single impression of the widget. */ @Nullable public int[] getScrolledIds() { return mScrolledIds; } private AppWidgetEvent(int appWidgetId, long durationMs, @Nullable Rect position, @Nullable int[] clickedIds, @Nullable int[] scrolledIds) { mAppWidgetId = appWidgetId; mDurationMs = durationMs; mPosition = position; mClickedIds = clickedIds; mScrolledIds = scrolledIds; } /** * Unflatten the AppWidgetEvent from a parcel. */ private AppWidgetEvent(Parcel in) { mAppWidgetId = in.readInt(); mDurationMs = in.readLong(); mPosition = in.readTypedObject(Rect.CREATOR); mClickedIds = in.createIntArray(); mScrolledIds = in.createIntArray(); } @Override public void writeToParcel(Parcel out, int flags) { out.writeInt(mAppWidgetId); out.writeLong(mDurationMs); out.writeTypedObject(mPosition, flags); out.writeIntArray(mClickedIds); out.writeIntArray(mScrolledIds); } /** * Parcelable.Creator that instantiates AppWidgetEvent objects */ public static final @android.annotation.NonNull Parcelable.Creator<AppWidgetEvent> CREATOR = new Parcelable.Creator<>() { public AppWidgetEvent createFromParcel(Parcel parcel) { return new AppWidgetEvent(parcel); } public AppWidgetEvent[] newArray(int size) { return new AppWidgetEvent[size]; } }; @Override public int describeContents() { return 0; } /** * Create a PersistableBundle that represents this event. */ @NonNull public PersistableBundle toBundle() { PersistableBundle extras = new PersistableBundle(); extras.putString(UsageStatsManager.EXTRA_EVENT_ACTION, AppWidgetManager.EVENT_TYPE_WIDGET_INTERACTION); extras.putString(UsageStatsManager.EXTRA_EVENT_CATEGORY, AppWidgetManager.EVENT_CATEGORY_APPWIDGET); extras.putInt(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId); extras.putLong(AppWidgetManager.EXTRA_EVENT_DURATION_MS, mDurationMs); if (mPosition != null) { extras.putIntArray(AppWidgetManager.EXTRA_EVENT_POSITION_RECT, new int[]{mPosition.left, mPosition.top, mPosition.right, mPosition.bottom}); } if (mClickedIds != null && mClickedIds.length > 0) { extras.putIntArray(AppWidgetManager.EXTRA_EVENT_CLICKED_VIEWS, mClickedIds); } if (mScrolledIds != null && mScrolledIds.length > 0) { extras.putIntArray(AppWidgetManager.EXTRA_EVENT_SCROLLED_VIEWS, mScrolledIds); } return extras; } @Override public String toString() { return TextUtils.formatSimple("AppWidgetEvent(appWidgetId=%d, durationMs=%d, position=%s," + " clickedIds=%s, scrolledIds=%s)", mAppWidgetId, mDurationMs, mPosition, Arrays.toString(mClickedIds), Arrays.toString(mScrolledIds)); } /** * Builder class to construct AppWidgetEvent objects. * * @hide */ public static class Builder { @NonNull private final ArraySet<Integer> mClickedIds = new ArraySet<>(MAX_NUM_ITEMS); @NonNull private final ArraySet<Integer> mScrolledIds = new ArraySet<>(MAX_NUM_ITEMS); private int mAppWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID; private long mDurationMs = 0; @Nullable private Rect mPosition = null; public Builder() { } public Builder setAppWidgetId(int appWidgetId) { mAppWidgetId = appWidgetId; return this; } public Builder addDurationMs(long durationMs) { mDurationMs += durationMs; return this; } public Builder setPosition(@Nullable Rect position) { mPosition = position; return this; } public Builder addClickedId(int id) { if (mClickedIds.size() < MAX_NUM_ITEMS) { mClickedIds.add(id); } return this; } public Builder addScrolledId(int id) { if (mScrolledIds.size() < MAX_NUM_ITEMS) { mScrolledIds.add(id); } return this; } /** * Merge the given event's data into this event's data. */ public void merge(@Nullable AppWidgetEvent event) { if (event == null) { return; } if (mAppWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) { setAppWidgetId(event.getAppWidgetId()); } else if (mAppWidgetId != event.getAppWidgetId()) { throw new IllegalArgumentException("Trying to merge events with different app " + "widget IDs: " + mAppWidgetId + " != " + event.getAppWidgetId()); } addDurationMs(event.getDurationMs()); setPosition(event.getPosition()); addAllUntilMax(mClickedIds, event.getClickedIds()); addAllUntilMax(mScrolledIds, event.getScrolledIds()); } /** * Returns true if the app widget ID has not been set, or if no data has been added to this * event yet. */ public boolean isEmpty() { return mAppWidgetId <= 0 || mDurationMs == 0L; } /** * Resets the event data fields. */ public void clear() { mDurationMs = 0; mPosition = null; mClickedIds.clear(); mScrolledIds.clear(); } public AppWidgetEvent build() { return new AppWidgetEvent(mAppWidgetId, mDurationMs, mPosition, toIntArray(mClickedIds), toIntArray(mScrolledIds)); } private static void addAllUntilMax(@NonNull ArraySet<Integer> set, @Nullable int[] toAdd) { if (toAdd == null) { return; } for (int i = 0; i < toAdd.length && set.size() < MAX_NUM_ITEMS; i++) { set.add(toAdd[i]); } } @Nullable private static int[] toIntArray(@NonNull ArraySet<Integer> set) { if (set.isEmpty()) return null; int[] array = new int[set.size()]; for (int i = 0; i < array.length; i++) { array[i] = set.valueAt(i); } return array; } } }
core/java/android/appwidget/AppWidgetHost.java +14 −9 Original line number Diff line number Diff line Loading @@ -16,6 +16,10 @@ 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; Loading @@ -31,7 +35,6 @@ import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.PersistableBundle; import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; Loading @@ -47,6 +50,7 @@ import com.android.internal.appwidget.IAppWidgetService; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** Loading Loading @@ -564,8 +568,9 @@ public class AppWidgetHost { * * @hide */ @FlaggedApi(FLAG_ENGAGEMENT_METRICS) @Nullable default PersistableBundle collectWidgetEvent() { default AppWidgetEvent collectWidgetEvent() { return null; } } Loading Loading @@ -663,14 +668,14 @@ public class AppWidgetHost { * @hide */ public void reportAllWidgetEvents() { if (sService == null) { if (sService == null || !engagementMetrics()) { return; } List<PersistableBundle> eventList = new ArrayList<>(); List<AppWidgetEvent> eventList = new ArrayList<>(); synchronized (mListeners) { for (int i = 0; i < mListeners.size(); i++) { PersistableBundle event = mListeners.valueAt(i).collectWidgetEvent(); AppWidgetEvent event = mListeners.valueAt(i).collectWidgetEvent(); if (event != null) { eventList.add(event); } Loading @@ -679,7 +684,7 @@ public class AppWidgetHost { if (eventList.isEmpty()) { return; } PersistableBundle[] events = new PersistableBundle[eventList.size()]; AppWidgetEvent[] events = new AppWidgetEvent[eventList.size()]; for (int i = 0; i < events.length; i++) { events[i] = eventList.get(i); } Loading @@ -697,18 +702,18 @@ public class AppWidgetHost { * @hide */ public void reportEventForWidget(int appWidgetId) { if (sService == null) { if (sService == null || !engagementMetrics()) { return; } AppWidgetHostListener listener = getListener(appWidgetId); if (listener == null) { return; } PersistableBundle event = listener.collectWidgetEvent(); AppWidgetEvent event = listener.collectWidgetEvent(); if (event == null) { return; } PersistableBundle[] events = {event}; AppWidgetEvent[] events = {event}; try { sService.reportWidgetEvents(mContextOpPackageName, events); Loading
core/java/android/appwidget/AppWidgetHostView.java +27 −68 Original line number Diff line number Diff line Loading @@ -43,9 +43,7 @@ import android.os.Build; import android.os.Bundle; import android.os.CancellationSignal; import android.os.Parcelable; import android.os.PersistableBundle; import android.os.SystemClock; import android.util.ArraySet; import android.util.AttributeSet; import android.util.Log; import android.util.Pair; Loading @@ -71,7 +69,6 @@ import com.android.internal.annotations.VisibleForTesting; import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.concurrent.Executor; /** Loading Loading @@ -1048,8 +1045,9 @@ public class AppWidgetHostView extends FrameLayout implements AppWidgetHost.AppW * * @hide */ @FlaggedApi(FLAG_ENGAGEMENT_METRICS) @Override public PersistableBundle collectWidgetEvent() { public AppWidgetEvent collectWidgetEvent() { return mInteractionLogger.collectWidgetEvent(); } Loading @@ -1058,23 +1056,13 @@ public class AppWidgetHostView extends FrameLayout implements AppWidgetHost.AppW * @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; // Determines the minimum time between calls to updateVisibility(). private static final long UPDATE_VISIBILITY_DELAY_MS = 1000L; // Clicked views @NonNull private final ArraySet<Integer> mClickedIds = new ArraySet<>(MAX_NUM_ITEMS); // Scrolled views @NonNull private final ArraySet<Integer> mScrolledIds = new ArraySet<>(MAX_NUM_ITEMS); private final AppWidgetEvent.Builder mEvent = new AppWidgetEvent.Builder(); @Nullable private RemoteViews.InteractionHandler mInteractionHandler = null; // Last position this widget was laid out in @Nullable private Rect mPosition = null; // Total duration for the impression private long mDurationMs = 0L; // Holds event data since last report. // Last time the widget became visible in SystemClock.uptimeMillis() private long mVisibilityChangeMs = 0L; private boolean mIsVisible = false; Loading @@ -1087,34 +1075,19 @@ public class AppWidgetHostView extends FrameLayout implements AppWidgetHost.AppW mInteractionHandler = handler; } /** * Return the current AppWidgetEvent without clearing the tracked data. */ @VisibleForTesting @NonNull public Set<Integer> getClickedIds() { return mClickedIds; } @VisibleForTesting @NonNull public Set<Integer> getScrolledIds() { return mScrolledIds; } @VisibleForTesting public long getDurationMs() { return mDurationMs; } @VisibleForTesting @Nullable public Rect getPosition() { return mPosition; public AppWidgetEvent getEvent() { return mEvent.build(); } @Override public boolean onInteraction(View view, PendingIntent pendingIntent, RemoteViews.RemoteResponse response) { if (engagementMetrics() && mClickedIds.size() < MAX_NUM_ITEMS) { mClickedIds.add(getMetricsId(view)); if (engagementMetrics()) { mEvent.addClickedId(getMetricsId(view)); } AppWidgetManager manager = AppWidgetManager.getInstance(mContext); if (manager != null) { Loading @@ -1133,10 +1106,7 @@ public class AppWidgetHostView extends FrameLayout implements AppWidgetHost.AppW public void onScroll(@NonNull AbsListView view) { if (!engagementMetrics()) return; if (mScrolledIds.size() < MAX_NUM_ITEMS) { mScrolledIds.add(getMetricsId(view)); } mEvent.addScrolledId(getMetricsId(view)); if (mInteractionHandler != null) { mInteractionHandler.onScroll(view); } Loading @@ -1157,9 +1127,10 @@ public class AppWidgetHostView extends FrameLayout implements AppWidgetHost.AppW */ private void onPositionChanged() { if (!engagementMetrics()) return; mPosition = new Rect(); if (getGlobalVisibleRect(mPosition)) { applyScrollOffset(); Rect position = new Rect(); if (getGlobalVisibleRect(position)) { applyScrollOffset(position); mEvent.setPosition(position); } } Loading @@ -1167,8 +1138,8 @@ public class AppWidgetHostView extends FrameLayout implements AppWidgetHost.AppW * Finds the first parent with a scrollX or scrollY offset and applies it to the current * position Rect. This corresponds to the current "page" of this widget on its workspace. */ private void applyScrollOffset() { if (mPosition == null) return; private void applyScrollOffset(@Nullable Rect position) { if (position == null) return; int dx = 0; int dy = 0; for (ViewParent parent = getParent(); parent != null; parent = parent.getParent()) { Loading @@ -1179,7 +1150,7 @@ public class AppWidgetHostView extends FrameLayout implements AppWidgetHost.AppW break; } } mPosition.offset(dx, dy); position.offset(dx, dy); } private void onDraw() { Loading Loading @@ -1230,7 +1201,7 @@ public class AppWidgetHostView extends FrameLayout implements AppWidgetHost.AppW mVisibilityChangeMs = SystemClock.uptimeMillis(); } else if (wasVisible && !isVisible) { // View is no longer visible, add duration. mDurationMs += SystemClock.uptimeMillis() - mVisibilityChangeMs; mEvent.addDurationMs(SystemClock.uptimeMillis() - mVisibilityChangeMs); } mIsVisible = isVisible; Loading @@ -1243,33 +1214,21 @@ public class AppWidgetHostView extends FrameLayout implements AppWidgetHost.AppW } @Nullable private PersistableBundle collectWidgetEvent() { private AppWidgetEvent collectWidgetEvent() { if (!engagementMetrics()) return null; if (mIsVisible) { // If the widget is currently visible, add the current duration to the event data. updateVisibility(false); } if (mAppWidgetId <= 0 || mDurationMs == 0L) { mEvent.setAppWidgetId(mAppWidgetId); if (mEvent.isEmpty()) { return null; } PersistableBundle event = AppWidgetManager.createWidgetInteractionEvent(mAppWidgetId, mDurationMs, mPosition, toIntArray(mClickedIds), toIntArray(mScrolledIds)); mClickedIds.clear(); mScrolledIds.clear(); mDurationMs = 0; mPosition = null; AppWidgetEvent event = mEvent.build(); mEvent.clear(); return event; } private static int[] toIntArray(ArraySet<Integer> set) { if (set.isEmpty()) return null; int[] array = new int[set.size()]; for (int i = 0; i < array.length; i++) { array[i] = set.valueAt(i); } return array; } } }
core/java/android/appwidget/AppWidgetManager.java +0 −35 Original line number Diff line number Diff line Loading @@ -43,7 +43,6 @@ import android.content.ServiceConnection; import android.content.pm.PackageManager; import android.content.pm.ParceledListSlice; import android.content.pm.ShortcutInfo; import android.graphics.Rect; import android.os.Binder; import android.os.Build; import android.os.Bundle; Loading @@ -52,7 +51,6 @@ import android.os.HandlerExecutor; import android.os.HandlerThread; import android.os.IBinder; import android.os.Looper; import android.os.PersistableBundle; import android.os.Process; import android.os.RemoteException; import android.os.UserHandle; Loading Loading @@ -1593,39 +1591,6 @@ public class AppWidgetManager { } } /** * Create a {@link PersistableBundle} that represents a single widget interaction event. * * @param appWidgetId App Widget ID of the widget. * @param durationMs Duration of the impression in milliseconds * @param position Current position of the widget. * @param clickedIds IDs of views clicked during this event. * @param scrolledIds IDs of views scrolled during this event. * * @hide */ @FlaggedApi(Flags.FLAG_ENGAGEMENT_METRICS) @NonNull public static PersistableBundle createWidgetInteractionEvent(int appWidgetId, long durationMs, @Nullable Rect position, @Nullable int[] clickedIds, @Nullable int[] scrolledIds) { PersistableBundle extras = new PersistableBundle(); extras.putString(UsageStatsManager.EXTRA_EVENT_ACTION, EVENT_TYPE_WIDGET_INTERACTION); extras.putString(UsageStatsManager.EXTRA_EVENT_CATEGORY, EVENT_CATEGORY_APPWIDGET); extras.putInt(EXTRA_APPWIDGET_ID, appWidgetId); extras.putLong(EXTRA_EVENT_DURATION_MS, durationMs); if (position != null) { extras.putIntArray(EXTRA_EVENT_POSITION_RECT, new int[]{position.left, position.top, position.right, position.bottom}); } if (clickedIds != null && clickedIds.length > 0) { extras.putIntArray(EXTRA_EVENT_CLICKED_VIEWS, clickedIds); } if (scrolledIds != null && scrolledIds.length > 0) { extras.putIntArray(EXTRA_EVENT_SCROLLED_VIEWS, scrolledIds); } return extras; } @UiThread private static @NonNull Executor createUpdateExecutorIfNull() { if (sUpdateExecutor == null) { Loading