NekoX/TMessagesProj/src/main/java/org/telegram/ui/Components/RLottieDrawable.java

1285 lines
44 KiB
Java

/*
* This is the source code of Telegram for Android v. 5.x.x.
* It is licensed under GNU GPL v. 2 or later.
* You should have received a copy of the license in this archive (see LICENSE).
*
* Copyright Nikolai Kudashov, 2013-2018.
*/
package org.telegram.ui.Components;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.BitmapDrawable;
import android.os.Handler;
import android.os.Looper;
import android.os.SystemClock;
import android.text.TextUtils;
import android.view.HapticFeedbackConstants;
import android.view.View;
import org.telegram.messenger.AndroidUtilities;
import org.telegram.messenger.ApplicationLoader;
import org.telegram.messenger.DispatchQueue;
import org.telegram.messenger.DispatchQueuePool;
import org.telegram.messenger.DispatchQueuePoolBackground;
import org.telegram.messenger.FileLog;
import org.telegram.messenger.ImageReceiver;
import org.telegram.messenger.R;
import org.telegram.messenger.Utilities;
import org.telegram.messenger.utils.BitmapsCache;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.concurrent.CountDownLatch;
public class RLottieDrawable extends BitmapDrawable implements Animatable, BitmapsCache.Cacheable {
public boolean skipFrameUpdate;
public static native long create(String src, String json, int w, int h, int[] params, boolean precache, int[] colorReplacement, boolean limitFps, int fitzModifier);
protected static native long createWithJson(String json, String name, int[] params, int[] colorReplacement);
public static native void destroy(long ptr);
private static native void setLayerColor(long ptr, String layer, int color);
private static native void replaceColors(long ptr, int[] colorReplacement);
public static native int getFrame(long ptr, int frame, Bitmap bitmap, int w, int h, int stride, boolean clear);
protected final int width;
protected final int height;
protected final int[] metaData = new int[3];
protected int timeBetweenFrames;
protected int customEndFrame = -1;
protected boolean playInDirectionOfCustomEndFrame;
private int[] newReplaceColors;
private int[] pendingReplaceColors;
private HashMap<String, Integer> newColorUpdates = new HashMap<>();
private volatile HashMap<String, Integer> pendingColorUpdates = new HashMap<>();
private HashMap<Integer, Integer> vibrationPattern;
private boolean resetVibrationAfterRestart = false;
private boolean allowVibration = true;
private WeakReference<Runnable> frameReadyCallback;
protected WeakReference<Runnable> onFinishCallback;
private int finishFrame;
private View currentParentView;
private ArrayList<ImageReceiver> parentViews = new ArrayList<>();
protected int isDice;
protected int diceSwitchFramesCount = -1;
protected int autoRepeat = 1;
protected int autoRepeatPlayCount;
protected long autoRepeatTimeout;
private long lastFrameTime;
protected volatile boolean nextFrameIsLast;
protected Runnable cacheGenerateTask;
protected Runnable loadFrameTask;
protected volatile Bitmap renderingBitmap;
protected volatile Bitmap nextRenderingBitmap;
protected volatile Bitmap backgroundBitmap;
protected boolean waitingForNextTask;
protected CountDownLatch frameWaitSync;
protected boolean destroyWhenDone;
private boolean decodeSingleFrame;
private boolean singleFrameDecoded;
private boolean forceFrameRedraw;
private boolean applyingLayerColors;
protected int currentFrame;
private boolean shouldLimitFps;
private float scaleX = 1.0f;
private float scaleY = 1.0f;
private boolean applyTransformation;
private boolean needScale;
private final RectF dstRect = new RectF();
private RectF dstRectBackground;
private Paint backgroundPaint;
protected static final Handler uiHandler = new Handler(Looper.getMainLooper());
protected volatile boolean isRunning;
protected volatile boolean isRecycled;
protected volatile long nativePtr;
protected volatile long secondNativePtr;
protected boolean loadingInBackground;
protected boolean secondLoadingInBackground;
protected boolean destroyAfterLoading;
protected int secondFramesCount;
protected volatile boolean setLastFrame;
private boolean fallbackCache;
private boolean invalidateOnProgressSet;
private boolean isInvalid;
private boolean doNotRemoveInvalidOnFrameReady;
private static ThreadLocal<byte[]> readBufferLocal = new ThreadLocal<>();
private static ThreadLocal<byte[]> bufferLocal = new ThreadLocal<>();
private static final DispatchQueuePool loadFrameRunnableQueue = new DispatchQueuePool(4);
public static DispatchQueue lottieCacheGenerateQueue;
File file;
boolean precache;
private Runnable onAnimationEndListener;
private Runnable onFrameReadyRunnable;
private View masterParent;
NativePtrArgs args;
protected Runnable uiRunnableNoFrame = new Runnable() {
@Override
public void run() {
loadFrameTask = null;
decodeFrameFinishedInternal();
if (onFrameReadyRunnable != null) {
onFrameReadyRunnable.run();
}
}
};
protected Runnable uiRunnable = new Runnable() {
@Override
public void run() {
singleFrameDecoded = true;
invalidateInternal();
decodeFrameFinishedInternal();
if (onFrameReadyRunnable != null) {
onFrameReadyRunnable.run();
}
}
};
long startTime;
boolean generatingCache;
private Runnable uiRunnableGenerateCache = new Runnable() {
@Override
public void run() {
if (!isRecycled && !destroyWhenDone && canLoadFrames() && cacheGenerateTask == null) {
startTime = System.currentTimeMillis();
generatingCache = true;
lottieCacheGenerateQueue.postRunnable(cacheGenerateTask = () -> {
BitmapsCache bitmapsCacheFinal = bitmapsCache;
if (bitmapsCacheFinal != null) {
bitmapsCacheFinal.createCache();
}
uiHandler.post(uiRunnableCacheFinished);
});
}
}
};
private Runnable uiRunnableCacheFinished = new Runnable() {
@Override
public void run() {
cacheGenerateTask = null;
generatingCache = false;
decodeFrameFinishedInternal();
}
};
BitmapsCache bitmapsCache;
int generateCacheFramePointer;
public static void createCacheGenQueue() {
lottieCacheGenerateQueue = new DispatchQueue("cache generator queue");
}
protected void checkRunningTasks() {
if (cacheGenerateTask != null) {
lottieCacheGenerateQueue.cancelRunnable(cacheGenerateTask);
cacheGenerateTask = null;
}
if (!hasParentView() && nextRenderingBitmap != null && loadFrameTask != null) {
loadFrameTask = null;
nextRenderingBitmap = null;
}
}
protected void decodeFrameFinishedInternal() {
if (destroyWhenDone) {
checkRunningTasks();
if (loadFrameTask == null && cacheGenerateTask == null && nativePtr != 0) {
destroy(nativePtr);
nativePtr = 0;
if (secondNativePtr != 0) {
destroy(secondNativePtr);
secondNativePtr = 0;
}
}
}
if ((nativePtr == 0 || fallbackCache) && secondNativePtr == 0 && bitmapsCache == null) {
recycleResources();
return;
}
waitingForNextTask = true;
if (!hasParentView()) {
stop();
}
scheduleNextGetFrame();
}
protected void recycleResources() {
ArrayList<Bitmap> bitmapToRecycle = new ArrayList<>();
bitmapToRecycle.add(renderingBitmap);
bitmapToRecycle.add(nextRenderingBitmap);
renderingBitmap = null;
backgroundBitmap = null;
AndroidUtilities.recycleBitmaps(bitmapToRecycle);
if (onAnimationEndListener != null) {
onAnimationEndListener = null;
}
invalidateInternal();
}
public void setOnFinishCallback(Runnable callback, int frame) {
if (callback != null) {
onFinishCallback = new WeakReference<>(callback);
finishFrame = frame;
} else if (onFinishCallback != null) {
onFinishCallback = null;
}
}
private boolean genCacheSend;
protected Runnable loadFrameRunnable = new Runnable() {
private long lastUpdate = 0;
@Override
public void run() {
if (isRecycled) {
return;
}
if (!canLoadFrames() || isDice == 2 && secondNativePtr == 0) {
if (frameWaitSync != null) {
frameWaitSync.countDown();
}
uiHandler.post(uiRunnableNoFrame);
return;
}
if (backgroundBitmap == null) {
try {
backgroundBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
} catch (Throwable e) {
FileLog.e(e);
}
}
if (backgroundBitmap != null) {
try {
if (!pendingColorUpdates.isEmpty()) {
for (HashMap.Entry<String, Integer> entry : pendingColorUpdates.entrySet()) {
setLayerColor(nativePtr, entry.getKey(), entry.getValue());
}
pendingColorUpdates.clear();
}
} catch (Exception ignore) {
}
if (pendingReplaceColors != null && nativePtr != 0) {
replaceColors(nativePtr, pendingReplaceColors);
pendingReplaceColors = null;
}
try {
int result = 0;
int framesPerUpdates = shouldLimitFps ? 2 : 1;
if (precache && bitmapsCache != null && (!fallbackCache || !generatingCache)) {
try {
result = bitmapsCache.getFrame(currentFrame / framesPerUpdates, backgroundBitmap);
} catch (Exception e) {
FileLog.e(e);
}
} else {
if (fallbackCache) {
final long now = SystemClock.elapsedRealtime();
if (lastUpdate > 0) {
framesPerUpdates = Math.max(1, Math.min(4, Math.round((now - lastUpdate) / 16f)));
if (currentFrame + framesPerUpdates > (customEndFrame >= 0 ? customEndFrame : metaData[0])) {
framesPerUpdates = (customEndFrame >= 0 ? customEndFrame : metaData[0]) - currentFrame;
}
}
lastUpdate = now;
}
long ptrToUse;
if (isDice == 1) {
ptrToUse = nativePtr;
} else if (isDice == 2) {
ptrToUse = secondNativePtr;
if (setLastFrame) {
currentFrame = secondFramesCount - 1;
}
} else {
ptrToUse = nativePtr;
}
result = getFrame(ptrToUse, currentFrame, backgroundBitmap, width, height, backgroundBitmap.getRowBytes(), true);
}
if (bitmapsCache != null && bitmapsCache.needGenCache()) {
if (!genCacheSend) {
genCacheSend = true;
uiHandler.post(uiRunnableGenerateCache);
}
if (!fallbackCache) {
result = -1;
}
}
if (result == -1) {
uiHandler.post(uiRunnableNoFrame);
if (frameWaitSync != null) {
frameWaitSync.countDown();
}
return;
}
nextRenderingBitmap = backgroundBitmap;
if (isDice == 1) {
if (currentFrame + framesPerUpdates < (diceSwitchFramesCount == -1 ? metaData[0] : diceSwitchFramesCount)) {
currentFrame += framesPerUpdates;
} else {
currentFrame = 0;
nextFrameIsLast = false;
if (secondNativePtr != 0) {
isDice = 2;
}
if (resetVibrationAfterRestart) {
vibrationPattern = null;
resetVibrationAfterRestart = false;
}
}
} else if (isDice == 2) {
if (currentFrame + framesPerUpdates < secondFramesCount) {
currentFrame += framesPerUpdates;
} else {
nextFrameIsLast = true;
autoRepeatPlayCount++;
}
} else {
if (customEndFrame >= 0 && playInDirectionOfCustomEndFrame) {
if (currentFrame > customEndFrame) {
if (currentFrame - framesPerUpdates >= customEndFrame) {
currentFrame -= framesPerUpdates;
nextFrameIsLast = false;
} else {
nextFrameIsLast = true;
checkDispatchOnAnimationEnd();
}
} else {
if (currentFrame + framesPerUpdates < customEndFrame) {
currentFrame += framesPerUpdates;
nextFrameIsLast = false;
} else {
nextFrameIsLast = true;
checkDispatchOnAnimationEnd();
}
}
} else {
if (currentFrame + framesPerUpdates < (customEndFrame >= 0 ? customEndFrame : metaData[0])) {
if (autoRepeat == 3) {
nextFrameIsLast = true;
autoRepeatPlayCount++;
} else {
currentFrame += framesPerUpdates;
nextFrameIsLast = false;
}
} else if (autoRepeat == 1) {
currentFrame = 0;
nextFrameIsLast = false;
if (resetVibrationAfterRestart) {
vibrationPattern = null;
resetVibrationAfterRestart = false;
}
} else if (autoRepeat == 2) {
currentFrame = 0;
nextFrameIsLast = true;
autoRepeatPlayCount++;
if (resetVibrationAfterRestart) {
vibrationPattern = null;
resetVibrationAfterRestart = false;
}
} else {
nextFrameIsLast = true;
checkDispatchOnAnimationEnd();
}
}
}
} catch (Exception e) {
FileLog.e(e);
}
}
uiHandler.post(uiRunnable);
if (frameWaitSync != null) {
frameWaitSync.countDown();
}
}
};
public RLottieDrawable(File file, int w, int h, BitmapsCache.CacheOptions cacheOptions, boolean limitFps) {
this(file, w, h, cacheOptions, limitFps, null, 0);
}
public RLottieDrawable(File file, int w, int h, BitmapsCache.CacheOptions cacheOptions, boolean limitFps, int[] colorReplacement, int fitzModifier) {
width = w;
height = h;
shouldLimitFps = limitFps;
this.precache = cacheOptions != null;
this.fallbackCache = cacheOptions != null && cacheOptions.fallback;
getPaint().setFlags(Paint.FILTER_BITMAP_FLAG);
this.file = file;
if (precache && lottieCacheGenerateQueue == null) {
createCacheGenQueue();
}
if (precache) {
bitmapsCache = new BitmapsCache(file, this, cacheOptions, w, h);
args = new NativePtrArgs();
args.file = file.getAbsoluteFile();
args.json = null;
args.colorReplacement = colorReplacement;
args.fitzModifier = fitzModifier;
nativePtr = create(file.getAbsolutePath(), null, w, h, metaData, precache, colorReplacement, shouldLimitFps, fitzModifier);
if (fallbackCache) {
if (nativePtr == 0) {
file.delete();
}
} else {
destroy(nativePtr);
nativePtr = 0;
}
} else {
nativePtr = create(file.getAbsolutePath(), null, w, h, metaData, precache, colorReplacement, shouldLimitFps, fitzModifier);
if (nativePtr == 0) {
file.delete();
}
}
if (shouldLimitFps && metaData[1] < 60) {
shouldLimitFps = false;
}
timeBetweenFrames = Math.max(shouldLimitFps ? 33 : 16, (int) (1000.0f / metaData[1]));
}
public RLottieDrawable(File file, String json, int w, int h, BitmapsCache.CacheOptions options, boolean limitFps, int[] colorReplacement, int fitzModifier) {
width = w;
height = h;
shouldLimitFps = limitFps;
this.precache = options != null;
getPaint().setFlags(Paint.FILTER_BITMAP_FLAG);
if (precache && lottieCacheGenerateQueue == null) {
createCacheGenQueue();
}
if (precache) {
bitmapsCache = new BitmapsCache(file, this, options, w, h);
args = new NativePtrArgs();
args.file = file.getAbsoluteFile();
args.json = json;
args.colorReplacement = colorReplacement;
args.fitzModifier = fitzModifier;
nativePtr = create(file.getAbsolutePath(), json, w, h, metaData, precache, colorReplacement, shouldLimitFps, fitzModifier);
if (fallbackCache) {
if (nativePtr == 0) {
file.delete();
}
} else {
if (nativePtr != 0) {
destroy(nativePtr);
}
nativePtr = 0;
}
} else {
nativePtr = create(file.getAbsolutePath(), json, w, h, metaData, precache, colorReplacement, shouldLimitFps, fitzModifier);
if (nativePtr == 0) {
file.delete();
}
}
if (shouldLimitFps && metaData[1] < 60) {
shouldLimitFps = false;
}
timeBetweenFrames = Math.max(shouldLimitFps ? 33 : 16, (int) (1000.0f / metaData[1]));
}
public RLottieDrawable(int rawRes, String name, int w, int h) {
this(rawRes, name, w, h, true, null);
}
public RLottieDrawable(String diceEmoji, int w, int h) {
width = w;
height = h;
isDice = 1;
String jsonString;
if ("\uD83C\uDFB2".equals(diceEmoji)) {
jsonString = readRes(null, R.raw.diceloop);
diceSwitchFramesCount = 60;
} else if ("\uD83C\uDFAF".equals(diceEmoji)) {
jsonString = readRes(null, R.raw.dartloop);
} else {
jsonString = null;
}
getPaint().setFlags(Paint.FILTER_BITMAP_FLAG);
if (TextUtils.isEmpty(jsonString)) {
timeBetweenFrames = 16;
return;
}
nativePtr = createWithJson(jsonString, "dice", metaData, null);
timeBetweenFrames = Math.max(16, (int) (1000.0f / metaData[1]));
}
private void checkDispatchOnAnimationEnd() {
if (onAnimationEndListener != null) {
onAnimationEndListener.run();
onAnimationEndListener = null;
}
}
public void setOnAnimationEndListener(Runnable onAnimationEndListener) {
this.onAnimationEndListener = onAnimationEndListener;
}
public boolean isDice() {
return isDice != 0;
}
public boolean setBaseDice(File path) {
if (nativePtr != 0 || loadingInBackground) {
return true;
}
String jsonString = readRes(path, 0);
if (TextUtils.isEmpty(jsonString)) {
return false;
}
loadingInBackground = true;
Utilities.globalQueue.postRunnable(() -> {
nativePtr = createWithJson(jsonString, "dice", metaData, null);
AndroidUtilities.runOnUIThread(() -> {
loadingInBackground = false;
if (!secondLoadingInBackground && destroyAfterLoading) {
recycle();
return;
}
timeBetweenFrames = Math.max(16, (int) (1000.0f / metaData[1]));
scheduleNextGetFrame();
invalidateInternal();
});
});
return true;
}
public boolean hasBaseDice() {
return nativePtr != 0 || loadingInBackground;
}
public boolean setDiceNumber(File path, boolean instant) {
if (secondNativePtr != 0 || secondLoadingInBackground) {
return true;
}
String jsonString = readRes(path, 0);
if (TextUtils.isEmpty(jsonString)) {
return false;
}
if (instant && nextRenderingBitmap == null && renderingBitmap == null && loadFrameTask == null) {
isDice = 2;
setLastFrame = true;
}
secondLoadingInBackground = true;
Utilities.globalQueue.postRunnable(() -> {
if (destroyAfterLoading) {
AndroidUtilities.runOnUIThread(() -> {
secondLoadingInBackground = false;
if (!loadingInBackground && destroyAfterLoading) {
recycle();
}
});
return;
}
int[] metaData2 = new int[3];
secondNativePtr = createWithJson(jsonString, "dice", metaData2, null);
AndroidUtilities.runOnUIThread(() -> {
secondLoadingInBackground = false;
if (!secondLoadingInBackground && destroyAfterLoading) {
recycle();
return;
}
secondFramesCount = metaData2[0];
timeBetweenFrames = Math.max(16, (int) (1000.0f / metaData2[1]));
scheduleNextGetFrame();
invalidateInternal();
});
});
return true;
}
public RLottieDrawable(int rawRes, String name, int w, int h, boolean startDecode, int[] colorReplacement) {
width = w;
height = h;
autoRepeat = 0;
String jsonString = readRes(null, rawRes);
if (TextUtils.isEmpty(jsonString)) {
return;
}
getPaint().setFlags(Paint.FILTER_BITMAP_FLAG);
nativePtr = createWithJson(jsonString, name, metaData, colorReplacement);
timeBetweenFrames = Math.max(16, (int) (1000.0f / metaData[1]));
if (startDecode) {
setAllowDecodeSingleFrame(true);
}
}
public static String readRes(File path, int rawRes) {
int totalRead = 0;
byte[] readBuffer = readBufferLocal.get();
if (readBuffer == null) {
readBuffer = new byte[64 * 1024];
readBufferLocal.set(readBuffer);
}
InputStream inputStream = null;
try {
if (path != null) {
inputStream = new FileInputStream(path);
} else {
inputStream = ApplicationLoader.applicationContext.getResources().openRawResource(rawRes);
}
int readLen;
byte[] buffer = bufferLocal.get();
if (buffer == null) {
buffer = new byte[4096];
bufferLocal.set(buffer);
}
while ((readLen = inputStream.read(buffer, 0, buffer.length)) >= 0) {
if (readBuffer.length < totalRead + readLen) {
byte[] newBuffer = new byte[readBuffer.length * 2];
System.arraycopy(readBuffer, 0, newBuffer, 0, totalRead);
readBuffer = newBuffer;
readBufferLocal.set(readBuffer);
}
if (readLen > 0) {
System.arraycopy(buffer, 0, readBuffer, totalRead, readLen);
totalRead += readLen;
}
}
} catch (Throwable e) {
return null;
} finally {
try {
if (inputStream != null) {
inputStream.close();
}
} catch (Throwable ignore) {
}
}
return new String(readBuffer, 0, totalRead);
}
public int getCurrentFrame() {
return currentFrame;
}
public int getCustomEndFrame() {
return customEndFrame;
}
public long getDuration() {
return (long) (metaData[0] / (float) metaData[1] * 1000);
}
public void setPlayInDirectionOfCustomEndFrame(boolean value) {
playInDirectionOfCustomEndFrame = value;
}
public boolean setCustomEndFrame(int frame) {
if (customEndFrame == frame || frame > metaData[0]) {
return false;
}
customEndFrame = frame;
return true;
}
public int getFramesCount() {
return metaData[0];
}
public void addParentView(ImageReceiver parent) {
if (parent == null) {
return;
}
parentViews.add(parent);
}
public void removeParentView(ImageReceiver parent) {
if (parent == null) {
return;
}
parentViews.remove(parent);
}
protected boolean hasParentView() {
return !parentViews.isEmpty() || masterParent != null || getCallback() != null;
}
protected void invalidateInternal() {
for (int i = 0; i < parentViews.size(); i++) {
parentViews.get(i).invalidate();
}
if (masterParent != null) {
masterParent.invalidate();
}
if (getCallback() != null) {
invalidateSelf();
}
}
public void setAllowDecodeSingleFrame(boolean value) {
decodeSingleFrame = value;
if (decodeSingleFrame) {
scheduleNextGetFrame();
}
}
public void recycle() {
isRunning = false;
isRecycled = true;
checkRunningTasks();
if (loadingInBackground || secondLoadingInBackground) {
destroyAfterLoading = true;
} else if (loadFrameTask == null && cacheGenerateTask == null && !generatingCache) {
if (nativePtr != 0) {
destroy(nativePtr);
nativePtr = 0;
}
if (secondNativePtr != 0) {
destroy(secondNativePtr);
secondNativePtr = 0;
}
if (bitmapsCache != null) {
bitmapsCache.recycle();
bitmapsCache = null;
}
recycleResources();
} else {
destroyWhenDone = true;
}
}
public void setAutoRepeat(int value) {
if (autoRepeat == 2 && value == 3 && currentFrame != 0) {
return;
}
autoRepeat = value;
}
public void setAutoRepeatTimeout(long timeout) {
autoRepeatTimeout = timeout;
}
@Override
protected void finalize() throws Throwable {
try {
recycle();
} finally {
super.finalize();
}
}
@Override
public int getOpacity() {
return PixelFormat.TRANSPARENT;
}
@Override
public void start() {
if (isRunning || autoRepeat >= 2 && autoRepeatPlayCount != 0 || customEndFrame == currentFrame) {
return;
}
isRunning = true;
if (invalidateOnProgressSet) {
isInvalid = true;
if (loadFrameTask != null) {
doNotRemoveInvalidOnFrameReady = true;
}
}
scheduleNextGetFrame();
invalidateInternal();
}
public boolean restart() {
if (autoRepeat < 2 || autoRepeatPlayCount == 0) {
return false;
}
autoRepeatPlayCount = 0;
autoRepeat = 2;
start();
return true;
}
public void setVibrationPattern(HashMap<Integer, Integer> pattern) {
vibrationPattern = pattern;
}
public boolean hasVibrationPattern() {
return vibrationPattern != null;
}
public void beginApplyLayerColors() {
applyingLayerColors = true;
}
public void commitApplyLayerColors() {
if (!applyingLayerColors) {
return;
}
applyingLayerColors = false;
if (!isRunning && decodeSingleFrame) {
if (currentFrame <= 2) {
currentFrame = 0;
}
nextFrameIsLast = false;
singleFrameDecoded = false;
if (!scheduleNextGetFrame()) {
forceFrameRedraw = true;
}
}
invalidateInternal();
}
public void replaceColors(int[] colors) {
newReplaceColors = colors;
requestRedrawColors();
}
public void setLayerColor(String layerName, int color) {
newColorUpdates.put(layerName, color);
requestRedrawColors();
}
private void requestRedrawColors() {
if (!applyingLayerColors && !isRunning && decodeSingleFrame) {
if (currentFrame <= 2) {
currentFrame = 0;
}
nextFrameIsLast = false;
singleFrameDecoded = false;
if (!scheduleNextGetFrame()) {
forceFrameRedraw = true;
}
}
invalidateInternal();
}
protected boolean scheduleNextGetFrame() {
return scheduleNextGetFrame(false);
}
protected boolean scheduleNextGetFrame(boolean allowGroupedUpdateLocal) {
if (loadFrameTask != null || nextRenderingBitmap != null || !canLoadFrames() || loadingInBackground || destroyWhenDone || !isRunning && (!decodeSingleFrame || decodeSingleFrame && singleFrameDecoded)) {
return false;
}
if (generatingCache) {
return false;
}
if (!newColorUpdates.isEmpty()) {
pendingColorUpdates.putAll(newColorUpdates);
newColorUpdates.clear();
}
if (newReplaceColors != null) {
pendingReplaceColors = newReplaceColors;
newReplaceColors = null;
}
loadFrameTask = loadFrameRunnable;
if (allowGroupedUpdateLocal && shouldLimitFps) {
DispatchQueuePoolBackground.execute(loadFrameTask);
} else {
loadFrameRunnableQueue.execute(loadFrameTask);
}
return true;
}
public boolean isHeavyDrawable() {
return isDice == 0;
}
@Override
public void stop() {
isRunning = false;
}
public void setCurrentFrame(int frame) {
setCurrentFrame(frame, true);
}
public void setCurrentFrame(int frame, boolean async) {
setCurrentFrame(frame, async, false);
}
public void setCurrentFrame(int frame, boolean async, boolean resetFrame) {
if (frame < 0 || frame > metaData[0] || (currentFrame == frame && !resetFrame)) {
return;
}
currentFrame = frame;
nextFrameIsLast = false;
singleFrameDecoded = false;
if (invalidateOnProgressSet) {
isInvalid = true;
if (loadFrameTask != null) {
doNotRemoveInvalidOnFrameReady = true;
}
}
if ((!async || resetFrame) && waitingForNextTask && nextRenderingBitmap != null) {
backgroundBitmap = nextRenderingBitmap;
nextRenderingBitmap = null;
loadFrameTask = null;
waitingForNextTask = false;
}
if (!async) {
if (loadFrameTask == null) {
frameWaitSync = new CountDownLatch(1);
}
}
if (resetFrame && !isRunning) {
isRunning = true;
}
if (scheduleNextGetFrame(false)) {
if (!async) {
try {
frameWaitSync.await();
} catch (Exception e) {
FileLog.e(e);
}
frameWaitSync = null;
}
} else {
forceFrameRedraw = true;
}
invalidateSelf();
}
public boolean isCacheFallbacked() {
return fallbackCache;
}
public void setProgressMs(long ms) {
int frameNum = (int) ((Math.max(0, ms) / timeBetweenFrames) % metaData[0]);
setCurrentFrame(frameNum, true, true);
}
public void setProgress(float progress) {
setProgress(progress, true);
}
public void setProgress(float progress, boolean async) {
if (progress < 0.0f) {
progress = 0.0f;
} else if (progress > 1.0f) {
progress = 1.0f;
}
setCurrentFrame((int) (metaData[0] * progress), async);
}
public void setCurrentParentView(View view) {
currentParentView = view;
}
@Override
public boolean isRunning() {
return isRunning;
}
@Override
public int getIntrinsicHeight() {
return height;
}
@Override
public int getIntrinsicWidth() {
return width;
}
@Override
protected void onBoundsChange(Rect bounds) {
super.onBoundsChange(bounds);
applyTransformation = true;
}
private void setCurrentFrame(long now, long timeDiff, long timeCheck, boolean force) {
backgroundBitmap = renderingBitmap;
renderingBitmap = nextRenderingBitmap;
nextRenderingBitmap = null;
if (isDice == 2) {
if (onFinishCallback != null && currentFrame - 1 >= finishFrame) {
Runnable runnable = onFinishCallback.get();
if (runnable != null) {
runnable.run();
}
onFinishCallback = null;
}
}
if (nextFrameIsLast) {
stop();
}
loadFrameTask = null;
if (doNotRemoveInvalidOnFrameReady) {
doNotRemoveInvalidOnFrameReady = false;
} else if (isInvalid) {
isInvalid = false;
}
singleFrameDecoded = true;
waitingForNextTask = false;
if (AndroidUtilities.screenRefreshRate <= 60) {
lastFrameTime = now;
} else {
lastFrameTime = now - Math.min(16, timeDiff - timeCheck);
}
if (force && forceFrameRedraw) {
singleFrameDecoded = false;
forceFrameRedraw = false;
}
if (isDice == 0) {
if (onFinishCallback != null && currentFrame >= finishFrame) {
Runnable runnable = onFinishCallback.get();
if (runnable != null) {
runnable.run();
}
}
}
scheduleNextGetFrame(true);
}
@Override
public void draw(Canvas canvas) {
drawInternal(canvas, false, 0);
}
public void drawInBackground(Canvas canvas, float x, float y, float w, float h, int alpha) {
if (dstRectBackground == null) {
dstRectBackground = new RectF();
backgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
backgroundPaint.setFilterBitmap(true);
}
backgroundPaint.setAlpha(alpha);
dstRectBackground.set(x, y, x + w, y + h);
drawInternal(canvas, true, 0);
}
public void drawInternal(Canvas canvas, boolean drawInBackground, long time) {
if (!canLoadFrames() || destroyWhenDone) {
return;
}
if (!drawInBackground) {
updateCurrentFrame(time, false);
}
RectF rect = drawInBackground ? dstRectBackground : dstRect;
Paint paint = drawInBackground ? backgroundPaint : getPaint();
if (paint.getAlpha() == 0) {
return;
}
if (!isInvalid && renderingBitmap != null) {
float scaleX, scaleY;
boolean needScale;
if (!drawInBackground) {
rect.set(getBounds());
if (applyTransformation) {
this.scaleX = rect.width() / width;
this.scaleY = rect.height() / height;
applyTransformation = false;
this.needScale = !(Math.abs(rect.width() - width) < AndroidUtilities.dp(1) && Math.abs(rect.height() - height) < AndroidUtilities.dp(1));
}
scaleX = this.scaleX;
scaleY = this.scaleY;
needScale = this.needScale;
} else {
scaleX = rect.width() / width;
scaleY = rect.height() / height;
needScale = !(Math.abs(rect.width() - width) < AndroidUtilities.dp(1) && Math.abs(rect.height() - height) < AndroidUtilities.dp(1));
}
if (!needScale) {
canvas.drawBitmap(renderingBitmap, rect.left, rect.top, paint);
} else {
canvas.save();
canvas.translate(rect.left, rect.top);
canvas.scale(scaleX, scaleY);
canvas.drawBitmap(renderingBitmap, 0, 0, paint);
canvas.restore();
}
if (isRunning && !drawInBackground) {
invalidateInternal();
}
}
}
public void updateCurrentFrame(long time, boolean updateInBackground) {
long now = time == 0 ? System.currentTimeMillis() : time;
long timeDiff = now - lastFrameTime;
int timeCheck;
if (AndroidUtilities.screenRefreshRate <= 60 || (updateInBackground && AndroidUtilities.screenRefreshRate <= 80)) {
timeCheck = timeBetweenFrames - 6;
} else {
timeCheck = timeBetweenFrames;
}
if (isRunning) {
if (renderingBitmap == null && nextRenderingBitmap == null) {
scheduleNextGetFrame(true);
} else if (nextRenderingBitmap != null && (renderingBitmap == null || (timeDiff >= timeCheck && !skipFrameUpdate))) {
if (vibrationPattern != null && currentParentView != null && allowVibration) {
Integer force = vibrationPattern.get(currentFrame - 1);
if (force != null) {
currentParentView.performHapticFeedback(force == 1 ? HapticFeedbackConstants.LONG_PRESS : HapticFeedbackConstants.KEYBOARD_TAP, HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING);
}
}
setCurrentFrame(now, timeDiff, timeCheck, false);
}
} else if ((forceFrameRedraw || decodeSingleFrame && timeDiff >= timeCheck) && nextRenderingBitmap != null) {
setCurrentFrame(now, timeDiff, timeCheck, true);
}
}
public void setAllowVibration(boolean allow) {
allowVibration = allow;
}
public void resetVibrationAfterRestart(boolean value) {
resetVibrationAfterRestart = value;
}
@Override
public int getMinimumHeight() {
return height;
}
@Override
public int getMinimumWidth() {
return width;
}
public Bitmap getRenderingBitmap() {
return renderingBitmap;
}
public Bitmap getNextRenderingBitmap() {
return nextRenderingBitmap;
}
public Bitmap getBackgroundBitmap() {
return backgroundBitmap;
}
public Bitmap getAnimatedBitmap() {
if (renderingBitmap != null) {
return renderingBitmap;
} else if (nextRenderingBitmap != null) {
return nextRenderingBitmap;
}
return null;
}
public boolean hasBitmap() {
return !isRecycled && (renderingBitmap != null || nextRenderingBitmap != null) && !isInvalid;
}
public void setInvalidateOnProgressSet(boolean value) {
invalidateOnProgressSet = value;
}
public boolean isGeneratingCache() {
return cacheGenerateTask != null;
}
public void setOnFrameReadyRunnable(Runnable onFrameReadyRunnable) {
this.onFrameReadyRunnable = onFrameReadyRunnable;
}
public boolean isLastFrame() {
return currentFrame == getFramesCount() - 1;
}
long generateCacheNativePtr;
@Override
public void prepareForGenerateCache() {
generateCacheNativePtr = create(args.file.toString(), args.json, width, height, new int[3], false, args.colorReplacement, false, args.fitzModifier);
if (generateCacheNativePtr == 0) {
file.delete();
}
}
@Override
public int getNextFrame(Bitmap bitmap) {
if (generateCacheNativePtr == 0) {
return -1;
}
int framesPerUpdates = shouldLimitFps ? 2 : 1;
int result = getFrame(generateCacheNativePtr, generateCacheFramePointer, bitmap, width, height, bitmap.getRowBytes(), true);
if (result == -5) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
return getNextFrame(bitmap);
}
generateCacheFramePointer += framesPerUpdates;
if (generateCacheFramePointer > metaData[0]) {
return 0;
}
return 1;
}
@Override
public void releaseForGenerateCache() {
if (generateCacheNativePtr != 0) {
destroy(generateCacheNativePtr);
generateCacheNativePtr = 0;
}
}
@Override
public Bitmap getFirstFrame(Bitmap bitmap) {
long nativePtr = create(args.file.toString(), args.json, width, height, new int[3], false, args.colorReplacement, false, args.fitzModifier);
if (nativePtr == 0) {
return bitmap;
}
getFrame(nativePtr, 0, bitmap, width, height, bitmap.getRowBytes(), true);
destroy(nativePtr);
return bitmap;
}
void setMasterParent(View parent) {
masterParent = parent;
}
public boolean canLoadFrames() {
if (precache) {
return bitmapsCache != null || fallbackCache;
} else {
return nativePtr != 0;
}
}
private class NativePtrArgs {
public int[] colorReplacement;
public int fitzModifier;
File file;
String json;
}
}