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

Commit e95aef8b authored by Willie Koomson's avatar Willie Koomson
Browse files

Limit number of previews held in AppWidgetService

Adds a configurable limit on the number of providers for which we hold
previews. This will be necessary until we land on-demand loading for
previews.

Bug: 361127558
Test: ApiCounterTest, manual local testing
Flag: EXEMPT bug fix

Change-Id: I146a9b76a978b30be1b0c4f6730bf36ad219cd1e
parent e2f22ecc
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -573,6 +573,12 @@ public final class SystemUiDeviceConfigFlags {
    public static final String GENERATED_PREVIEW_API_MAX_CALLS_PER_INTERVAL =
            "generated_preview_api_max_calls_per_interval";

    /*
     * (int) The max number of providers for which to keep generated previews.
     */
    public static final String GENERATED_PREVIEW_API_MAX_PROVIDERS =
            "generated_preview_api_max_providers";

    private SystemUiDeviceConfigFlags() {
    }
}
+38 −14
Original line number Diff line number Diff line
@@ -213,6 +213,8 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
            Duration.ofHours(1).toMillis();
    // Default max API calls per reset interval for generated preview API rate limiting.
    private static final int DEFAULT_GENERATED_PREVIEW_MAX_CALLS_PER_INTERVAL = 2;
    // Default max number of providers for which to keep previews.
    private static final int DEFAULT_GENERATED_PREVIEW_MAX_PROVIDERS = 50;
    // XML attribute for widget ids that are pending deletion.
    // See {@link Provider#pendingDeletedWidgetIds}.
    private static final String PENDING_DELETED_IDS_ATTR = "pending_deleted_ids";
@@ -358,10 +360,13 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
                SystemUiDeviceConfigFlags.GENERATED_PREVIEW_API_RESET_INTERVAL_MS,
                DEFAULT_GENERATED_PREVIEW_RESET_INTERVAL_MS);
        final int generatedPreviewMaxCallsPerInterval = DeviceConfig.getInt(NAMESPACE_SYSTEMUI,
                SystemUiDeviceConfigFlags.GENERATED_PREVIEW_API_RESET_INTERVAL_MS,
                SystemUiDeviceConfigFlags.GENERATED_PREVIEW_API_MAX_CALLS_PER_INTERVAL,
                DEFAULT_GENERATED_PREVIEW_MAX_CALLS_PER_INTERVAL);
        final int generatedPreviewsMaxProviders = DeviceConfig.getInt(NAMESPACE_SYSTEMUI,
                SystemUiDeviceConfigFlags.GENERATED_PREVIEW_API_MAX_PROVIDERS,
                DEFAULT_GENERATED_PREVIEW_MAX_PROVIDERS);
        mGeneratedPreviewsApiCounter = new ApiCounter(generatedPreviewResetInterval,
                generatedPreviewMaxCallsPerInterval);
                generatedPreviewMaxCallsPerInterval, generatedPreviewsMaxProviders);
        DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_SYSTEMUI,
                new HandlerExecutor(mCallbackHandler), this::handleSystemUiDeviceConfigChange);

@@ -4660,6 +4665,13 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
                        /* defaultValue= */ mGeneratedPreviewsApiCounter.getMaxCallsPerInterval());
                mGeneratedPreviewsApiCounter.setMaxCallsPerInterval(maxCallsPerInterval);
            }
            if (changed.contains(
                    SystemUiDeviceConfigFlags.GENERATED_PREVIEW_API_MAX_PROVIDERS)) {
                int maxProviders = properties.getInt(
                        SystemUiDeviceConfigFlags.GENERATED_PREVIEW_API_MAX_PROVIDERS,
                        /* defaultValue= */ mGeneratedPreviewsApiCounter.getMaxProviders());
                mGeneratedPreviewsApiCounter.setMaxProviders(maxProviders);
            }
        }
    }

@@ -5444,17 +5456,22 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
        private long mResetIntervalMs;
        // The max number of API calls per interval.
        private int mMaxCallsPerInterval;
        // The max number of providers to keep call records for. Any call to tryApiCall for new
        // providers will return false after this limit.
        private int mMaxProviders;

        // Returns the current time (monotonic). By default this is SystemClock.elapsedRealtime.
        private LongSupplier mMonotonicClock;

        ApiCounter(long resetIntervalMs, int maxCallsPerInterval) {
            this(resetIntervalMs, maxCallsPerInterval, SystemClock::elapsedRealtime);
        ApiCounter(long resetIntervalMs, int maxCallsPerInterval, int maxProviders) {
            this(resetIntervalMs, maxCallsPerInterval, maxProviders, SystemClock::elapsedRealtime);
        }

        ApiCounter(long resetIntervalMs, int maxCallsPerInterval,
        ApiCounter(long resetIntervalMs, int maxCallsPerInterval, int maxProviders,
                LongSupplier monotonicClock) {
            mResetIntervalMs = resetIntervalMs;
            mMaxCallsPerInterval = maxCallsPerInterval;
            mMaxProviders = maxProviders;
            mMonotonicClock = monotonicClock;
        }

@@ -5474,12 +5491,27 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
            return mMaxCallsPerInterval;
        }

        public void setMaxProviders(int maxProviders) {
            mMaxProviders = maxProviders;
        }

        public int getMaxProviders() {
            return mMaxProviders;
        }

        /**
         * Returns true if the API call for the provider should be allowed, false if it should be
         * rate-limited.
         */
        public boolean tryApiCall(@NonNull ProviderId provider) {
            final ApiCallRecord record = getOrCreateRecord(provider);
            if (!mCallCount.containsKey(provider)) {
                if (mCallCount.size() >= mMaxProviders) {
                    return false;
                }
                mCallCount.put(provider, new ApiCallRecord());
            }
            ApiCallRecord record = mCallCount.get(provider);

            final long now = mMonotonicClock.getAsLong();
            final long timeSinceLastResetMs = now - record.lastResetTimeMs;
            // If the last reset was beyond the reset interval, reset now.
@@ -5500,14 +5532,6 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
        public void remove(@NonNull ProviderId id) {
            mCallCount.remove(id);
        }

        @NonNull
        private ApiCallRecord getOrCreateRecord(@NonNull ProviderId provider) {
            if (!mCallCount.containsKey(provider)) {
                mCallCount.put(provider, new ApiCallRecord());
            }
            return mCallCount.get(provider);
        }
    }

    private class LoadedWidgetState {
+20 −1
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ class ApiCounterTest {
    private companion object {
        const val RESET_INTERVAL_MS = 10L
        const val MAX_CALLS_PER_INTERVAL = 2
        const val MAX_PROVIDERS = 10
    }

    private var currentTime = 0L
@@ -34,7 +35,9 @@ class ApiCounterTest {
            /* uid= */ 123,
            ComponentName("com.android.server.appwidget", "FakeProviderClass")
        )
    private val counter = ApiCounter(RESET_INTERVAL_MS, MAX_CALLS_PER_INTERVAL) { currentTime }
    private val counter = ApiCounter(RESET_INTERVAL_MS, MAX_CALLS_PER_INTERVAL, MAX_PROVIDERS) {
        currentTime
    }

    @Test
    fun tryApiCall() {
@@ -58,4 +61,20 @@ class ApiCounterTest {
        counter.remove(id)
        assertThat(counter.tryApiCall(id)).isTrue()
    }

    @Test
    fun maxProviders() {
        for (i in 0 until MAX_PROVIDERS) {
            for (j in 0 until MAX_CALLS_PER_INTERVAL) {
                assertThat(counter.tryApiCall(providerId(i))).isTrue()
            }
        }
        assertThat(counter.tryApiCall(providerId(MAX_PROVIDERS))).isFalse()
        // remove will allow another provider to be added
        counter.remove(providerId(0))
        assertThat(counter.tryApiCall(providerId(MAX_PROVIDERS))).isTrue()
    }

    private fun providerId(i: Int) =
        AppWidgetServiceImpl.ProviderId(/* uid= */ i, id.componentName)
}