/* * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. */ package com.facebook.fresco.animation.backend; import javax.annotation.Nullable; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import android.graphics.Canvas; import android.graphics.drawable.Drawable; import com.facebook.common.internal.VisibleForTesting; import com.facebook.common.time.MonotonicClock; /** * Animation backend delegate for animation backends that implement {@link InactivityListener}. * After a certain inactivity period (default = {@link #INACTIVITY_THRESHOLD_MS}, * {@link InactivityListener#onInactive()} will be called. * * This can for example be used to drop caches if needed. * * New instances can be created with * {@link #createForBackend(AnimationBackend, MonotonicClock, ScheduledExecutorService)}. */ public class AnimationBackendDelegateWithInactivityCheck<T extends AnimationBackend> extends AnimationBackendDelegate<T> { public interface InactivityListener { /** * Called when the animation backend has not been used to draw frames within * the given threshold. */ void onInactive(); } public static <T extends AnimationBackend & AnimationBackendDelegateWithInactivityCheck.InactivityListener> AnimationBackendDelegate<T> createForBackend( T backend, MonotonicClock monotonicClock, ScheduledExecutorService scheduledExecutorServiceForUiThread) { return createForBackend(backend, backend, monotonicClock, scheduledExecutorServiceForUiThread); } public static <T extends AnimationBackend> AnimationBackendDelegate<T> createForBackend( T backend, InactivityListener inactivityListener, MonotonicClock monotonicClock, ScheduledExecutorService scheduledExecutorServiceForUiThread) { return new AnimationBackendDelegateWithInactivityCheck<>( backend, inactivityListener, monotonicClock, scheduledExecutorServiceForUiThread); } @VisibleForTesting static final long INACTIVITY_THRESHOLD_MS = 2000; @VisibleForTesting static final long INACTIVITY_CHECK_POLLING_TIME_MS = 1000; private final MonotonicClock mMonotonicClock; private final ScheduledExecutorService mScheduledExecutorServiceForUiThread; private boolean mInactivityCheckScheduled = false; private long mLastDrawnTimeMs; private long mInactivityThresholdMs = INACTIVITY_THRESHOLD_MS; private long mInactivityCheckPollingTimeMs = INACTIVITY_CHECK_POLLING_TIME_MS; @Nullable private InactivityListener mInactivityListener; /** * Watchdog runnable that calls {@link InactivityListener#onInactive()} if necessary * or schedules a new watchdog task otherwise. */ private final Runnable mIsInactiveCheck = new Runnable() { @Override public void run() { synchronized (AnimationBackendDelegateWithInactivityCheck.this) { mInactivityCheckScheduled = false; if (isInactive()) { if (mInactivityListener != null) { mInactivityListener.onInactive(); } } else { maybeScheduleInactivityCheck(); } } } }; private AnimationBackendDelegateWithInactivityCheck( @Nullable T animationBackend, @Nullable InactivityListener inactivityListener, MonotonicClock monotonicClock, ScheduledExecutorService scheduledExecutorServiceForUiThread) { super(animationBackend); mInactivityListener = inactivityListener; mMonotonicClock = monotonicClock; mScheduledExecutorServiceForUiThread = scheduledExecutorServiceForUiThread; } @Override public boolean drawFrame(Drawable parent, Canvas canvas, int frameNumber) { mLastDrawnTimeMs = mMonotonicClock.now(); boolean result = super.drawFrame(parent, canvas, frameNumber); maybeScheduleInactivityCheck(); return result; } public void setInactivityListener(@Nullable InactivityListener inactivityListener) { mInactivityListener = inactivityListener; } public long getInactivityCheckPollingTimeMs() { return mInactivityCheckPollingTimeMs; } public void setInactivityCheckPollingTimeMs(long inactivityCheckPollingTimeMs) { mInactivityCheckPollingTimeMs = inactivityCheckPollingTimeMs; } public long getInactivityThresholdMs() { return mInactivityThresholdMs; } public void setInactivityThresholdMs(long inactivityThresholdMs) { mInactivityThresholdMs = inactivityThresholdMs; } private boolean isInactive() { return mMonotonicClock.now() - mLastDrawnTimeMs > mInactivityThresholdMs; } private synchronized void maybeScheduleInactivityCheck() { if (!mInactivityCheckScheduled) { mInactivityCheckScheduled = true; mScheduledExecutorServiceForUiThread.schedule( mIsInactiveCheck, mInactivityCheckPollingTimeMs, TimeUnit.MILLISECONDS); } } }