/*
* 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);
}
}
}