<|start_filename|>src/main/java/com/tenor/android/core/weakref/WeakRefOnClickListener.java<|end_filename|>
package com.tenor.android.core.weakref;

import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.View;

import com.tenor.android.core.util.AbstractWeakReferenceUtils;

import java.lang.ref.WeakReference;

public abstract class WeakRefOnClickListener<CTX> implements IWeakRefObject<CTX>, View.OnClickListener {

    private final WeakReference<CTX> mWeakRef;

    public WeakRefOnClickListener(@Nullable final CTX ctx) {
        mWeakRef = new WeakReference<>(ctx);
    }

    public WeakRefOnClickListener(@NonNull final WeakReference<CTX> weakRef) {
        mWeakRef = weakRef;
    }

    @NonNull
    @Override
    public WeakReference<CTX> getWeakRef() {
        return mWeakRef;
    }

    @Override
    public boolean hasRef() {
        return AbstractWeakReferenceUtils.isAlive(mWeakRef);
    }

    @Override
    public void onClick(View v) {
        if (hasRef()) {
            //noinspection ConstantConditions
            onClick(getWeakRef().get(), v);
        }
    }

    public abstract void onClick(@NonNull CTX ctx, @NonNull View v);
}


<|start_filename|>src/main/java/com/tenor/android/core/model/impl/Result.java<|end_filename|>
package com.tenor.android.core.model.impl;

import android.support.annotation.FloatRange;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;

import com.google.gson.annotations.SerializedName;
import com.tenor.android.core.constant.StringConstant;
import com.tenor.android.core.model.IGif;
import com.tenor.android.core.util.AbstractListUtils;
import com.tenor.android.core.validator.ColorHex;
import com.tenor.android.core.validator.FloatString;

import java.util.Collections;
import java.util.List;

/**
 * The model of {@link Result}
 */
public class Result implements IGif {
    private static final long serialVersionUID = -4037633614634142811L;

    private String url;

    @SerializedName("media")
    private List<MediaCollection> medias;

    private double created;
    private int shares;
    private String itemurl;
    private Media composite;

    @SerializedName("hasaudio")
    private boolean hasAudio;

    private String title;
    private String id;
    private List<String> tags;

    @SerializedName("bg_color")
    private String placeholderColor;

    @SerializedName("aspect_ratio")
    private String aspectRatio;

    @SerializedName("feature_info")
    private FeaturedInfo featuredInfo;

    @SerializedName("badge_info")
    private BadgeInfo badgeInfo;

    @SerializedName("source_id")
    private String sourceId;

    /**
     * @return true if asset has sound
     */
    public boolean isHasAudio() {
        return hasAudio;
    }

    @Nullable
    public Media getComposite() {
        return composite;
    }

    /**
     * @return created at timestamp
     */
    public double getCreated() {
        return created;
    }

    /**
     * @return unique result id
     */
    @NonNull
    public String getId() {
        return StringConstant.getOrEmpty(id);
    }

    @Override
    @NonNull
    public String getName() {
        return StringConstant.getOrEmpty(title);
    }

    /**
     * @return collection of {@link Media} formats of this {@link Result}
     */
    @NonNull
    public List<MediaCollection> getMedias() {
        return !AbstractListUtils.isEmpty(medias) ? medias : Collections.<MediaCollection>emptyList();
    }

    /**
     * @return collection of related {@link Tag}s of this {@link Result}
     */
    @NonNull
    public List<String> getTags() {
        return !AbstractListUtils.isEmpty(tags) ? tags : Collections.<String>emptyList();
    }

    /**
     * @return share count of this {@link Result}
     */
    public int getShares() {
        return shares;
    }

    /**
     * @return name of this {@link Result}
     */
    public String getTitle() {
        return title;
    }

    /**
     * @return web url of this {@link Result}
     */
    public String getUrl() {
        return url;
    }

    /**
     * @return alternative web url of this {@link Result}
     */
    public String getItemUrl() {
        return itemurl;
    }

    /**
     * @return placeholder color hex of this {@link Result}
     */
    @NonNull
    public String getPlaceholderColorHex() {
        return ColorHex.parse(placeholderColor, "#000000");
    }

    /**
     * @return aspect ratio of this {@link Result} or the default 1080p aspect ratio, 1.7778f
     */
    @FloatRange(from = 0.01f, to = 5.01f)
    public float getAspectRatio() {
        float ratio = FloatString.parse(aspectRatio, 1.7778f);
        return ratio >= 0.01f && ratio <= 5.01f ? ratio : 1.7778f;
    }

    @Nullable
    public FeaturedInfo getFeaturedInfo() {
        return featuredInfo;
    }

    public boolean hasFeaturedInfo() {
        return featuredInfo != null;
    }

    @Nullable
    public BadgeInfo getBadgeInfo() {
        return badgeInfo;
    }

    public boolean hasBadgeInfo() {
        return badgeInfo != null;
    }

    @NonNull
    public String getSourceId() {
        return StringConstant.getOrEmpty(sourceId);
    }

    public boolean hasSourceId() {
        return !TextUtils.isEmpty(sourceId);
    }
}


<|start_filename|>src/main/java/com/tenor/android/core/util/AbstractSessionUtils.java<|end_filename|>
package com.tenor.android.core.util;

import android.content.Context;
import android.content.SharedPreferences;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;

import com.tenor.android.core.constant.StringConstant;

/**
 * The session utility class
 */
public abstract class AbstractSessionUtils {
    private static final String DEVICE_PREF = "device_preferences";

    /**
     * Use {@link #KEY_ANON_ID} instead
     *
     * @deprecated
     */
    private static final String KEY_KEYBOARD_ID = "KEY_KEYBOARD_ID";
    private static final String KEY_ANON_ID = "KEY_ANON_ID";
    private static final String KEY_ANDROID_ADVERTISE_ID = "KEY_ANDROID_ADVERTISE_ID";

    protected static SharedPreferences getPreferences(@NonNull final Context context) {
        return context.getSharedPreferences(DEVICE_PREF, Context.MODE_PRIVATE);
    }

    protected static void remove(@NonNull final Context context, String... keys) {
        SharedPreferences.Editor edit = getPreferences(context).edit();
        for (String key : keys) {
            edit.remove(key);
        }
        edit.apply();
    }

    public static synchronized void setAnonId(@NonNull final Context context, @Nullable final String keyboardId) {
        if (TextUtils.isEmpty(keyboardId)) {
            return;
        }
        getPreferences(context).edit().putString(KEY_ANON_ID, keyboardId).apply();
    }

    public static synchronized String getAnonId(@NonNull final Context context) {

        final String id = getPreferences(context).getString(KEY_ANON_ID, StringConstant.EMPTY);
        if (!TextUtils.isEmpty(id)) {
            return id;
        }
        return migrateKeyboardId(context);
    }

    private static synchronized String migrateKeyboardId(@NonNull final Context context) {
        if (!getPreferences(context).contains(KEY_KEYBOARD_ID)) {
            return StringConstant.EMPTY;
        }

        // migrate id
        final String keyboardId = getPreferences(context).getString(KEY_KEYBOARD_ID, StringConstant.EMPTY);
        // migrate keyboard id to anon id
        setAnonId(context, keyboardId);
        // remove KEY_KEYBOARD_ID
        getPreferences(context).edit().remove(KEY_KEYBOARD_ID).apply();
        return keyboardId;
    }

    public static boolean hasAnonId(@NonNull final Context context) {
        return !TextUtils.isEmpty(getAnonId(context));
    }

    public static void setAndroidAdvertiseId(@NonNull final Context context,
                                             @NonNull final String aaid) {
        // empty string is used if aaid is not available
        final String id = StringConstant.getOrEmpty(aaid);
        getPreferences(context).edit().putString(KEY_ANDROID_ADVERTISE_ID, id).apply();
    }

    public static String getAndroidAdvertiseId(@NonNull final Context context) {
        return getPreferences(context).getString(KEY_ANDROID_ADVERTISE_ID, StringConstant.EMPTY);
    }
}

<|start_filename|>src/main/java/com/tenor/android/core/constant/MediaCollectionFormat.java<|end_filename|>
package com.tenor.android.core.constant;

import android.support.annotation.StringDef;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
 * The supported media formats
 */
public class MediaCollectionFormat {

    @Retention(RetentionPolicy.CLASS)
    @StringDef({GIF, GIF_TINY, GIF_MEDIUM, GIF_NANO,
            MP4, MP4_TINY, MP4_NANO, MP4_LOOPED,
            WEBM, WEBM_TINY, WEBM_NANO
    })
    public @interface Value {
    }
    
    /**
     * Standard GIF format
     */
    public static final String GIF = "GIF";
    /**
     * Compressed gif.  Good for quick loading and sending
     */
    public static final String GIF_TINY = "GIF_TINY";
    public static final String GIF_MEDIUM = "GIF_MEDIUM";
    public static final String GIF_NANO = "GIF_NANO";

    /**
     * Standard mp4 format
     */
    public static final String MP4 = "MP4";
    /**
     * Compressed mp4 format.  Good for quick loading and sending
     */
    public static final String MP4_TINY = "MP4_TINY";
    public static final String MP4_NANO = "MP4_NANO";
    /**
     * Looped mp4 format that contains 3 loops built in
     */
    public static final String MP4_LOOPED = "MP4_LOOPED";

    /**
     * Web player url
     */
    public static final String WEBM = "WEBM";
    public static final String WEBM_TINY = "WEBM_TINY";
    public static final String WEBM_NANO = "WEBM_NANO";
}


<|start_filename|>src/main/java/com/tenor/android/core/util/AbstractGsonUtils.java<|end_filename|>
package com.tenor.android.core.util;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

/**
 * The Gson utility class
 */
public abstract class AbstractGsonUtils {

    private static Gson sGson;

    public static Gson getInstance() {
        if (sGson == null) {
            sGson = new GsonBuilder().create();
        }
        return sGson;
    }
}


<|start_filename|>src/main/java/com/tenor/android/core/presenter/IBasePresenter.java<|end_filename|>
package com.tenor.android.core.presenter;

import android.support.annotation.Nullable;

import com.tenor.android.core.view.IBaseView;

/**
 * The base presenter interface that all presenters should implement
 */
public interface IBasePresenter<T extends IBaseView> {
    @Nullable
    T getView();
}

<|start_filename|>src/main/java/com/tenor/android/core/constant/ItemBadgePosition.java<|end_filename|>
package com.tenor.android.core.constant;

import android.support.annotation.IntDef;
import android.support.annotation.NonNull;

import com.tenor.android.core.model.impl.ItemBadge;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

public class ItemBadgePosition {

    @Retention(RetentionPolicy.CLASS)
    @IntDef({NONE,
            INTERIOR_TOP_LEFT, INTERIOR_TOP_RIGHT,
            INTERIOR_BOTTOM_RIGHT, INTERIOR_BOTTOM_LEFT,
            EXTERIOR_TOP_LEFT, EXTERIOR_TOP_RIGHT,
            EXTERIOR_BOTTOM_RIGHT, EXTERIOR_BOTTOM_LEFT})
    public @interface Value {
    }

    public static final int NONE = 0;
    public static final int INTERIOR_TOP_LEFT = 1;
    public static final int INTERIOR_TOP_RIGHT = 2;
    public static final int INTERIOR_BOTTOM_RIGHT = 3;
    public static final int INTERIOR_BOTTOM_LEFT = 4;
    public static final int EXTERIOR_TOP_LEFT = 5;
    public static final int EXTERIOR_TOP_RIGHT = 6;
    public static final int EXTERIOR_BOTTOM_RIGHT = 7;
    public static final int EXTERIOR_BOTTOM_LEFT = 8;

    @Value
    public static int parse(int position) {
        switch (position) {
            case INTERIOR_TOP_LEFT:
                return INTERIOR_TOP_LEFT;
            case INTERIOR_TOP_RIGHT:
                return INTERIOR_TOP_RIGHT;
            case INTERIOR_BOTTOM_RIGHT:
                return INTERIOR_BOTTOM_RIGHT;
            case INTERIOR_BOTTOM_LEFT:
                return INTERIOR_BOTTOM_LEFT;
            case EXTERIOR_TOP_LEFT:
                return EXTERIOR_TOP_LEFT;
            case EXTERIOR_TOP_RIGHT:
                return EXTERIOR_TOP_RIGHT;
            case EXTERIOR_BOTTOM_RIGHT:
                return EXTERIOR_BOTTOM_RIGHT;
            case EXTERIOR_BOTTOM_LEFT:
                return EXTERIOR_BOTTOM_LEFT;
            case NONE:
            default:
                return NONE;
        }
    }

    public static boolean isExterior(@NonNull ItemBadge badge) {
        return isExterior(badge.getPosition());
    }

    public static boolean isExterior(@Value int position) {
        switch (position) {
            case EXTERIOR_TOP_LEFT:
            case EXTERIOR_TOP_RIGHT:
            case EXTERIOR_BOTTOM_RIGHT:
            case EXTERIOR_BOTTOM_LEFT:
                return true;
            default:
                return false;
        }
    }

    public static boolean isInterior(@NonNull ItemBadge badge) {
        return isInterior(badge.getPosition());
    }

    public static boolean isInterior(@Value int position) {
        switch (position) {
            case INTERIOR_TOP_LEFT:
            case INTERIOR_TOP_RIGHT:
            case INTERIOR_BOTTOM_RIGHT:
            case INTERIOR_BOTTOM_LEFT:
                return true;
            default:
                return false;
        }
    }

    public static boolean isLeft(@NonNull ItemBadge badge) {
        return isLeft(badge.getPosition());
    }

    public static boolean isLeft(@Value int position) {
        switch (position) {
            case INTERIOR_TOP_LEFT:
            case INTERIOR_BOTTOM_LEFT:
            case EXTERIOR_TOP_LEFT:
            case EXTERIOR_BOTTOM_LEFT:
                return true;
            default:
                return false;
        }
    }

    public static boolean isRight(@NonNull ItemBadge badge) {
        return isRight(badge.getPosition());
    }

    public static boolean isRight(@Value int position) {
        switch (position) {
            case INTERIOR_TOP_RIGHT:
            case INTERIOR_BOTTOM_RIGHT:
            case EXTERIOR_TOP_RIGHT:
            case EXTERIOR_BOTTOM_RIGHT:
                return true;
            default:
                return false;
        }
    }
}


<|start_filename|>src/main/java/com/tenor/android/core/network/IAnonIdListener.java<|end_filename|>
package com.tenor.android.core.network;


import android.support.annotation.NonNull;
import android.support.annotation.Nullable;

/**
 * Callback for asynchronous anon id API request is done
 */
public interface IAnonIdListener {

    /**
     * @param anonId the keyboard id
     */
    void onReceiveAnonIdSucceeded(@NonNull String anonId);

    /**
     * @param throwable the {@link Throwable}
     */
    void onReceiveAnonIdFailed(@Nullable Throwable throwable);
}


<|start_filename|>src/main/java/com/tenor/android/core/measurable/MeasurableOnScrollListener.java<|end_filename|>
package com.tenor.android.core.measurable;

import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.widget.OrientationHelper;
import android.support.v7.widget.RecyclerView;

import com.tenor.android.core.checker.ScriptDirectionChecker;
import com.tenor.android.core.util.AbstractLayoutManagerUtils;
import com.tenor.android.core.util.AbstractLogUtils;

import java.util.List;

public class MeasurableOnScrollListener extends RecyclerView.OnScrollListener {

    private int mDraggingStart = RecyclerView.NO_POSITION;
    private int mDraggingEnd = RecyclerView.NO_POSITION;
    private boolean mDragging;

    private static final int TYPE_UNMEASURABLE = -1;
    private static final int TYPE_UNKNOWN = 0;
    private static final int TYPE_MEASURABLE = 1;
    private int mMeasurable = TYPE_UNKNOWN;
    private int mScriptDirectionState = ScriptDirectionChecker.UNSPECIFIED;

    private boolean validateMeasurable(@Nullable RecyclerView recyclerView) {
        if (mMeasurable != TYPE_UNKNOWN) {
            return mMeasurable == TYPE_MEASURABLE;
        }

        if (recyclerView instanceof IMeasurableRecyclerView) {
            mMeasurable = TYPE_MEASURABLE;
        } else {
            mMeasurable = TYPE_UNMEASURABLE;
        }
        return mMeasurable == TYPE_MEASURABLE;
    }

    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        if (mDragging) {
            // Only for dragging case to improve the accuracy
            onDragged(recyclerView);
        } else {
            super.onScrolled(recyclerView, dx, dy);
        }
    }

    private void initScriptDirection(RecyclerView recyclerView) {
        if (mScriptDirectionState == ScriptDirectionChecker.UNSPECIFIED) {
            mScriptDirectionState = ScriptDirectionChecker.checkSelfScriptDirection(recyclerView.getContext());
        }
    }

    @Override
    public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
        initScriptDirection(recyclerView);

        switch (newState) {
            case RecyclerView.SCROLL_STATE_DRAGGING:
                // distinct drag to improve accuracy, init mDraggingStart and mDraggingEnd here
                mDragging = true;
                final int[] range = AbstractLayoutManagerUtils.getVisibleRange(recyclerView);
                mDraggingStart = range[0];
                mDraggingEnd = range[1];
                break;
            case RecyclerView.SCROLL_STATE_SETTLING:
                mDragging = false;
                break;
            case RecyclerView.SCROLL_STATE_IDLE:
                mDragging = false;

                // perform final update on the range we have ever visited
                updateVisibleRange(recyclerView);
                AbstractLogUtils.e(this, "==>  visible range: [" + mDraggingStart + ", " + mDraggingEnd + "]");
                MeasurableRecyclerViewHelper.notifyMeasurableViewHolderDataRangeChanged(recyclerView, mDraggingStart, mDraggingEnd);

                // reset mDraggingStart and mDraggingEnd here
                mDraggingStart = RecyclerView.NO_POSITION;
                mDraggingEnd = RecyclerView.NO_POSITION;

                final int state = ScriptDirectionChecker.checkSelfScriptDirection(recyclerView.getContext());
                if (mScriptDirectionState != ScriptDirectionChecker.UNSPECIFIED
                        && state != ScriptDirectionChecker.UNSPECIFIED
                        && mScriptDirectionState != state) {
                    /*
                     * [ANDROID-1778]
                     *
                     * immediately flush the statistics if user change between a ltr and rtl language
                     * on the fly; do the check in here for better performance
                     */
                    final List<IMeasurableViewHolder> holders =
                            MeasurableRecyclerViewHelper.getViewHolders(recyclerView, IMeasurableViewHolder.class);
                    for (IMeasurableViewHolder holder : holders) {
                        holder.flush();
                    }

                    if (AbstractLayoutManagerUtils.getOrientation(recyclerView.getLayoutManager())
                            == OrientationHelper.HORIZONTAL) {
                        AbstractLayoutManagerUtils.setReverseLayout(recyclerView.getLayoutManager(),
                                state == ScriptDirectionChecker.RIGHT_TO_LEFT);
                    }
                    mScriptDirectionState = state;
                }
                break;
            default:
                break;
        }
    }

    private void onDragged(@NonNull RecyclerView recyclerView) {
        updateVisibleRange(recyclerView);

        /*
         * Get the view holders that are currently visiting or just visited.  Using the range from
         * `AbstractLayoutManagerUtils.getVisibleRange(recyclerView);` should result in better
         * performance, yet it can potentially reduce the measuring accuracy
         */
        final List<IMeasurableViewHolder> holders = MeasurableRecyclerViewHelper.getViewHolders(
                recyclerView, IMeasurableViewHolder.class, mDraggingStart, mDraggingEnd);
        for (IMeasurableViewHolder holder : holders) {
            holder.measure(recyclerView);
        }
    }

    private void updateVisibleRange(@NonNull RecyclerView recyclerView) {
        /*
         * [ANDROID-1830]
         *
         * Get the estimated completely visible item range of a multi-span layout manager,
         * this just a rough range, each start/end item on each span may not be completely
         * visible
         */
        final int[] range = AbstractLayoutManagerUtils.getVisibleRange(recyclerView);
        if (range[0] > RecyclerView.NO_POSITION && range[0] < mDraggingStart) {
            mDraggingStart = range[0];
        }
        if (range[1] > RecyclerView.NO_POSITION && range[1] > mDraggingEnd) {
            mDraggingEnd = range[1];
        }
    }
}



<|start_filename|>src/main/java/com/tenor/android/core/weakref/WeakRefHandlerThread.java<|end_filename|>
package com.tenor.android.core.weakref;

import android.app.Activity;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;

import com.tenor.android.core.util.AbstractWeakReferenceUtils;

import java.lang.ref.WeakReference;

/**
 * {@link HandlerThread} with a {@link WeakReference} on its calling context
 * <p/>
 * This is intended to avoid unintentional leakage on {@link Activity} and {@link android.app.Fragment}
 */
public abstract class WeakRefHandlerThread<CTX, H extends Handler> extends HandlerThread
        implements IWeakRefObject<CTX> {

    private H mHandler;
    private final WeakReference<CTX> mWeakRef;
    private boolean mHandlerPrepared;

    public WeakRefHandlerThread(@NonNull final CTX activity, @NonNull final String id) {
        super(id);
        mWeakRef = new WeakReference<>(activity);
    }

    /**
     * Get {@link Handler}, wrap this method with {@link #isAlive()}
     * <p>
     * This method returns the {@link Handler} associated with this {@link Thread}.
     * If this thread not been started or for any reason is {@link #isAlive()} returns false,
     * this method will return null. If this thread has been started,
     * this method will block until the handler has been initialized.
     *
     * @return The {@link Handler}.
     */
    @Nullable
    public H getHandler() {
        if (!isAlive()) {
            return null;
        }

        // If the thread has been started, wait until the handler has been created.
        synchronized (this) {
            while (isAlive() && mHandler == null) {
                try {
                    wait();
                } catch (InterruptedException ignored) {
                }
            }
        }
        return mHandler;
    }

    public boolean isHandlerPrepared() {
        return mHandlerPrepared;
    }

    @Override
    protected void onLooperPrepared() {
        super.onLooperPrepared();
        mHandler = initHandler(getLooper(), getWeakRef());
        mHandlerPrepared = true;
        onHandlerPrepared();
    }

    protected void onHandlerPrepared() {
    }

    /**
     * Initialize the {@link Handler} that will associate with this {@link HandlerThread}
     *
     * @param looper  the looper of the current prepared {@link WeakRefHandlerThread}
     * @param weakRef the {@link WeakReference} of its calling context
     */
    public abstract H initHandler(@NonNull Looper looper, @NonNull WeakReference<CTX> weakRef);

    @NonNull
    @Override
    public WeakReference<CTX> getWeakRef() {
        return mWeakRef;
    }

    @Override
    public boolean hasRef() {
        return AbstractWeakReferenceUtils.isAlive(mWeakRef);
    }

    /**
     * Cancel all existing tasks
     */
    public void cancelAll() {
        getHandler().removeCallbacksAndMessages(null);
    }
}


<|start_filename|>src/main/java/com/tenor/android/core/weakref/WeakRefRunnable.java<|end_filename|>
package com.tenor.android.core.weakref;

import android.app.Activity;
import android.support.annotation.CallSuper;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;

import java.lang.ref.WeakReference;

/**
 * {@link Runnable} with a {@link WeakReference} on its calling context
 * <p/>
 * This is intended to avoid unintentional leakage on {@link Activity} and {@link android.app.Fragment}
 */
public abstract class WeakRefRunnable<T> extends WeakRefObject<T> implements Runnable {

    private boolean mConsumed;

    /**
     * @param t the type of caller
     */
    public WeakRefRunnable(@Nullable final T t) {
        this(new WeakReference<>(t));
    }

    public WeakRefRunnable(@NonNull final WeakReference<T> weakRef) {
        super(weakRef);
    }

    @Override
    public void run() {
        if (hasRef()) {
            //noinspection ConstantConditions
            run(getWeakRef().get());
            onRunCompleted();
        }
    }

    public boolean isConsumed() {
        return mConsumed;
    }

    public abstract void run(@NonNull T t);

    @CallSuper
    public void onRunCompleted() {
        mConsumed = true;
    }
}


<|start_filename|>src/main/java/com/tenor/android/core/util/AbstractUIUtils.java<|end_filename|>
package com.tenor.android.core.util;

import android.app.Activity;
import android.content.Context;
import android.os.Build;
import android.support.annotation.DimenRes;
import android.support.annotation.FloatRange;
import android.support.annotation.IntRange;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.Display;
import android.view.View;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;

import com.tenor.android.core.R;
import com.tenor.android.core.checker.ScriptDirectionChecker;

import java.lang.reflect.Method;

/**
 * The UI utility class
 */
public abstract class AbstractUIUtils {

    public static void hideInputMethod(@Nullable final Activity activity) {
        hideInputMethod(activity, 0);
    }

    public static void hideInputMethod(@Nullable final View view) {
        hideInputMethod(view, 0);
    }

    /**
     * @param flags Provides additional operating flags.  Set to 0 if there are no additional flags.
     */
    public static void hideInputMethod(@Nullable final View view, int flags) {
        if (view == null || view.getContext() == null) {
            return;
        }

        final InputMethodManager imm = (InputMethodManager) view.getContext()
                .getSystemService(Context.INPUT_METHOD_SERVICE);
        if (imm == null) {
            return;
        }
        imm.hideSoftInputFromWindow(view.getWindowToken(), flags);
    }

    /**
     * @param flags Provides additional operating flags.  Set to 0 if there are no additional flags.
     */
    public static void hideInputMethod(@Nullable final Activity activity, int flags) {
        if (activity == null || activity.getCurrentFocus() == null) {
            return;
        }

        View view = activity.getCurrentFocus();
        view.clearFocus();
        hideInputMethod(view, flags);
    }

    /**
     * @param flags Provides additional operating flags.  Set to 0 if there are no additional flags.
     */
    public static void showInputMethod(@Nullable final Activity activity, int flags) {
        if (activity == null || activity.getCurrentFocus() == null) {
            return;
        }

        View view = activity.getCurrentFocus();
        view.clearFocus();
        showInputMethod(view, flags);
    }

    public static void showInputMethod(@Nullable final View view) {
        showInputMethod(view, 0);
    }

    /**
     * @param flags Provides additional operating flags.  Set to 0 if there are no additional flags.
     */
    public static void showInputMethod(@Nullable final View view, int flags) {
        if (view == null || view.getContext() == null) {
            return;
        }

        final InputMethodManager imm = (InputMethodManager) view.getContext()
                .getSystemService(Context.INPUT_METHOD_SERVICE);
        if (imm == null) {
            return;
        }
        imm.showSoftInput(view, flags);
    }

    /**
     * @param activity the activity
     * @return true if destroyed.  False if still alive, or call performed on device of API 16 and below
     * <p>
     * @targetApi 17
     * Calls Activity method isDestroyed() on supplied activity.
     */
    public static boolean isActivityDestroyed(@Nullable Activity activity) {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1
                && activity != null && activity.isDestroyed();
    }

    /**
     * @param context the context
     * @param dp      the density-independent pixels
     * @return the given value in pixels
     */
    public static int dpToPx(@NonNull final Context context,
                             @FloatRange(from = 0f, to = Float.MAX_VALUE) final float dp) {
        return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, context.getResources().getDisplayMetrics()));
    }

    /**
     * @param context the context
     * @param sp      scale-independent pixels
     * @return the given value in pixels
     */
    public static int spToPx(@NonNull final Context context,
                             @FloatRange(from = 0f, to = Float.MAX_VALUE) float sp) {
        return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, context.getResources().getDisplayMetrics()));
    }

    /**
     * Takes value stored as a <b>"dimen"</b> resource, and converts it into pixel length
     *
     * @param context        the context
     * @param dimensionResId <b>"dimen"</b> resource id name
     * @return conversion of the resource's value into pixel length
     */
    public static int getDimension(@NonNull final Context context, @DimenRes final int dimensionResId) {
        return Math.round(context.getResources().getDimension(dimensionResId));
    }

    /**
     * Get status bar height
     *
     * @param context the context
     * @return the height of status bar
     */
    public static int getStatusBarHeight(@NonNull Context context) {
        return getStatusBarHeight(context, 0);
    }

    /**
     * Get status bar height
     *
     * @param context the context
     * @param defVal  the default value if the height is not found
     * @return the height of status bar
     */
    public static int getStatusBarHeight(@NonNull Context context,
                                         @IntRange(from = 0) int defVal) {
        try {
            final int resourceId = context.getResources()
                    .getIdentifier("status_bar_height", "dimen", "android");
            if (resourceId > 0) {
                return context.getResources().getDimensionPixelSize(resourceId);
            }
        } catch (Throwable ignored) {
        }
        return defVal;
    }

    /**
     * Get status bar height
     *
     * @param view the view
     * @return the height of status bar
     */
    public static int getStatusBarHeight(@NonNull View view) {
        if (Build.VERSION.SDK_INT < 21) {
            return getStatusBarHeight(view.getContext(), dpToPx(view.getContext(), 24));
        }

        // this approach only works for API 21+
        final View v = view.getRootView().findViewById(android.R.id.statusBarBackground);
        if (v != null) {
            return v.getHeight();
        } else {
            // default status bar height in most cases is 24dp
            return dpToPx(view.getContext(), 24);
        }
    }

    /**
     * Get screen width in pixels
     *
     * @param context the context
     * @return the screen width in pixels
     */
    public static int getScreenWidth(@Nullable final Context context) {
        if (context == null) {
            return 0;
        }
        final DisplayMetrics metrics = context.getResources().getDisplayMetrics();
        return metrics.widthPixels;
    }

    /**
     * Get screen height in pixels
     *
     * @param context the context
     * @return the screen height in pixels
     */
    public static int getScreenHeight(@Nullable final Context context) {
        if (context == null) {
            return 0;
        }
        final DisplayMetrics metrics = context.getResources().getDisplayMetrics();
        return metrics.heightPixels;
    }

    /**
     * Get screen height in pixels
     *
     * @param context the context
     * @return true if there is system bar on screen
     */
    public static boolean hasOnScreenSystemBar(@NonNull final Context context) {
        final WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        final Display display = wm.getDefaultDisplay();
        int displayHeight = 0;
        try {
            final Method getRawHeight = Display.class.getMethod("getRawHeight");
            displayHeight = (Integer) getRawHeight.invoke(display);
        } catch (Exception ignored) {
        }

        int uiHeight = getScreenHeight(context);

        return displayHeight - uiHeight > 0;
    }

    /**
     * Check if the device is on landscape mode
     *
     * @param context the context
     * @return true if the device is on landscape mode
     */
    public static boolean isLandscape(@Nullable final Context context) {
        return context != null && context.getResources().getBoolean(R.bool.landscape);
    }

    /**
     * Check if the device is on right to left mode
     *
     * @param context the context
     * @return true if the device is on right to left mode
     */
    public static boolean isRightToLeft(@Nullable final Context context) {
        return ScriptDirectionChecker.checkSelfScriptDirection(context) == ScriptDirectionChecker.RIGHT_TO_LEFT;
    }
}

<|start_filename|>src/main/java/com/tenor/android/core/constant/StringConstant.java<|end_filename|>
package com.tenor.android.core.constant;

import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;

import com.tenor.android.core.util.AbstractListUtils;

import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.Collection;
import java.util.Iterator;

/**
 * The frequently used {@link String} constants
 */
public abstract class StringConstant {
    /**
     * ""
     */
    public static final String EMPTY = "";
    /**
     * " "
     */
    public static final String SPACE = " ";
    /**
     * "#"
     */
    public static final String HASH = "#";
    /**
     * ","
     */
    public static final String COMMA = ",";
    /**
     * "."
     */
    public static final String DOT = ".";
    /**
     * "-"
     */
    public static final String DASH = "-";
    /**
     * ":"
     */
    public static final String COLON = ":";
    /**
     * "@"
     */
    public static final String AT = "@";
    /**
     * "UNKNOWN"
     */
    public static final String UNKNOWN = "UNKNOWN";
    /**
     * "/"
     */
    public static final String SLASH = "/";
    /**
     * "|"
     */
    public static final String PIPE = "|";
    /**
     * \n
     */
    public static final String NEW_LINE = "\n";
    /**
     * "UTF-8"
     */
    public static final String UTF8 = "UTF-8";

    /**
     * Get string from given package with given resource name
     *
     * @param context      given context
     * @param packageName  the package name
     * @param resourceName the resource name
     * @return the string that the resource represents
     */
    @NonNull
    public static String getString(@NonNull final Context context,
                                   @NonNull final String packageName,
                                   @NonNull final String resourceName) {

        PackageManager pm = context.getPackageManager();

        try {
            //I want to use the clear_activities string in Package com.android.settings
            Resources res = pm.getResourcesForApplication(packageName);
            int resourceId = res.getIdentifier(packageName + ":string/" + resourceName, null, null);
            if (resourceId != 0) {
                return pm.getText(packageName, resourceId, null).toString();
            }
        } catch (Exception ignored) {
        }
        return EMPTY;
    }

    /**
     * Generate String in UTF8 format
     *
     * @param string initial String
     * @return UTF8 encoded String
     */
    @NonNull
    public static String encode(@NonNull final String string) {
        return encode(string, UTF8);
    }

    /**
     * Generate String in given format, UTF8 if none is specified
     *
     * @param string initial String
     * @param format specified encoding format
     * @return encoded String
     */
    @NonNull
    public static String encode(@NonNull final String string, @Nullable final String format) {
        if (TextUtils.isEmpty(string)) {
            return EMPTY;
        }

        try {
            return URLEncoder.encode(string,
                    !TextUtils.isEmpty(format) ? format : UTF8);
        } catch (Throwable ignored) {
            return string;
        }
    }

    /**
     * Generate String in UTF8 format
     *
     * @param string initial String
     * @return UTF8 encoded String
     */
    @NonNull
    public static String decode(@NonNull final String string) {
        return decode(string, UTF8);
    }

    /**
     * Generate String in given format, UTF8 if none is specified
     *
     * @param string initial String
     * @param format specified encoding format
     * @return encoded String
     */
    @NonNull
    public static String decode(@NonNull final String string, @Nullable final String format) {
        if (TextUtils.isEmpty(string)) {
            return EMPTY;
        }

        try {
            return URLDecoder.decode(string,
                    !TextUtils.isEmpty(format) ? format : UTF8);
        } catch (Throwable ignored) {
            return string;
        }
    }

    public interface IJoinable<T> {
        String getJoinableString(T t);
    }

    public static <T> String join(@Nullable Collection<T> words,
                                  @Nullable String separator,
                                  @Nullable IJoinable<T> joinable) {

        if (AbstractListUtils.isEmpty(words) || joinable == null) {
            return EMPTY;
        }

        if (separator == null) {
            join(words, EMPTY, joinable);
        }

        Iterator<T> it = words.iterator();
        String word;
        StringBuilder sb = new StringBuilder();
        boolean firstWord = true;
        while (it.hasNext()) {
            word = joinable.getJoinableString(it.next());

            if (firstWord) {
                firstWord = false;
            } else {
                sb.append(separator);
            }

            sb.append(word);
        }
        return sb.toString();
    }

    public static String join(@Nullable Collection<String> words,
                              @Nullable String separator) {

        return join(words, separator, new IJoinable<String>() {
            @Override
            public String getJoinableString(String s) {
                return s;
            }
        });
    }

    public static <T> T getOrDef(@Nullable T t, @NonNull T def) {
        return t != null ? t : def;
    }

    public static String getOrEmpty(@Nullable String str) {
        return getOrDef(str, EMPTY);
    }

    public static CharSequence getOrEmpty(@Nullable CharSequence str) {
        return getOrDef(str, EMPTY);
    }

    /**
     * Parse the given {@link String} into int
     *
     * @param str    the string
     * @param defVal the default value
     */
    public static int parse(@Nullable final String str, final int defVal) {
        if (TextUtils.isEmpty(str) || !TextUtils.isDigitsOnly(str)) {
            return defVal;
        }

        try {
            return Integer.parseInt(str);
        } catch (NumberFormatException ignored) {
            return defVal;
        }
    }

    /**
     * Save {@code content} into device clipboard with {@code label}
     */
    public static boolean copy(@NonNull final Context context,
                               @NonNull final String label,
                               @NonNull final String content) {
        if (TextUtils.isEmpty(label) || TextUtils.isEmpty(content)) {
            return false;
        }

        ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
        clipboard.setPrimaryClip(ClipData.newPlainText(label, content));
        return true;
    }
}


<|start_filename|>src/main/java/com/tenor/android/core/validator/AbstractValidator.java<|end_filename|>
package com.tenor.android.core.validator;

import android.support.annotation.NonNull;
import android.text.TextUtils;

import java.util.regex.Pattern;

public abstract class AbstractValidator<T> implements IValidator<T> {

    private static final long serialVersionUID = 1605380452419139489L;
    private final Pattern mPattern;

    public AbstractValidator(@NonNull String regex) {
        if (TextUtils.isEmpty(regex)) {
            throw new IllegalArgumentException("regex cannot be empty");
        }
        mPattern = Pattern.compile(regex);
    }

    @NonNull
    @Override
    public Pattern get() {
        return mPattern;
    }
}


<|start_filename|>src/main/java/com/tenor/android/core/presenter/impl/BasePresenter.java<|end_filename|>
package com.tenor.android.core.presenter.impl;

import android.support.annotation.NonNull;
import android.support.annotation.Nullable;

import com.tenor.android.core.presenter.IBasePresenter;
import com.tenor.android.core.response.WeakRefCallback;
import com.tenor.android.core.view.IBaseView;
import com.tenor.android.core.weakref.WeakRefObject;

import java.lang.ref.WeakReference;

/**
 * The base presenter class that all presenters should extend from
 */
public class BasePresenter<CTX extends IBaseView> extends WeakRefObject<CTX> implements IBasePresenter<CTX> {

    public BasePresenter(CTX ctx) {
        super(ctx);
    }

    @Nullable
    @Override
    public CTX getView() {
        return getWeakRef().get();
    }

    public boolean hasView() {
        return hasRef();
    }

    protected abstract class BaseWeakRefCallback<T> extends WeakRefCallback<CTX, T> {

        protected BaseWeakRefCallback(@NonNull CTX ctx) {
            this(new WeakReference<>(ctx));
        }

        protected BaseWeakRefCallback(@NonNull WeakReference<CTX> weakRef) {
            super(weakRef);
        }
    }
}


<|start_filename|>src/main/java/com/tenor/android/core/service/AaidService.java<|end_filename|>
package com.tenor.android.core.service;

import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;


public class AaidService {

    public static final int JOB_ID_GET_AAID = 0;
    public static final String ACTION_GET_AAID = "ACTION_GET_AAID";

    public static void requestAaid(@NonNull Context context) {
        requestAaid(context, null);
    }

    public static void requestAaid(@NonNull Context context, @Nullable IAaidListener listener) {

        try {
            AaidServiceApi26.requestAaid(context, listener);
        } catch (NoClassDefFoundError ignored) {
            /*
             * [ANDROID-2581]
             *
             * This is to catch a fatal runtime `NoClassDefFoundError` due to compiling a apk file
             * with `compileSdkVersion` and `support-compat` less than 26; the `JobIntentService`
             * required by the `AaidServiceApi26` is only available since 26.
             */
            AaidServiceCompat.requestAaid(context, listener);
        }
    }
}

<|start_filename|>src/main/java/com/tenor/android/core/model/impl/Suggestions.java<|end_filename|>
package com.tenor.android.core.model.impl;

import android.support.annotation.NonNull;

import com.google.gson.annotations.SerializedName;
import com.tenor.android.core.util.AbstractListUtils;

import java.io.Serializable;
import java.util.Collections;
import java.util.List;

/**
 * The model of {@link Suggestions}, which contains a collection of {@link String}
 */
public class Suggestions implements Serializable {
    private static final long serialVersionUID = 5805025178874996139L;

    @SerializedName("results")
    private List<String> suggestions;

    @NonNull
    public List<String> getSuggestions() {
        return !AbstractListUtils.isEmpty(suggestions) ? suggestions : Collections.<String>emptyList();
    }
}


<|start_filename|>src/main/java/com/tenor/android/core/measurable/IViewHolder.java<|end_filename|>
package com.tenor.android.core.measurable;

import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;

/**
 * Interface of {@link RecyclerView.ViewHolder} on support library
 *
 * @version v26.0.1
 * @since v22.0.0
 */
public interface IViewHolder {

    /**
     * Returns the position of the ViewHolder in terms of the latest layout pass.
     * <p>
     * This position is mostly used by RecyclerView components to be consistent while
     * RecyclerView lazily processes adapter updates.
     * <p>
     * For performance and animation reasons, RecyclerView batches all adapter updates until the
     * next layout pass. This may cause mismatches between the Adapter position of the item and
     * the position it had in the latest layout calculations.
     * <p>
     * LayoutManagers should always call this method while doing calculations based on item
     * positions. All methods in {@link RecyclerView.LayoutManager}, {@link RecyclerView.State},
     * {@link RecyclerView.Recycler} that receive a position expect it to be the layout position
     * of the item.
     * <p>
     * If LayoutManager needs to call an external method that requires the adapter position of
     * the item, it can use {@link #getAdapterPosition()} or
     * {@link RecyclerView.Recycler#convertPreLayoutPositionToPostLayout(int)}.
     *
     * @return Returns the adapter position of the ViewHolder in the latest layout pass.
     * @see #getAdapterPosition()
     */
    int getLayoutPosition();

    /**
     * Returns the Adapter position of the item represented by this ViewHolder.
     * <p>
     * Note that this might be different than the {@link #getLayoutPosition()} if there are
     * pending adapter updates but a new layout pass has not happened yet.
     * <p>
     * RecyclerView does not handle any adapter updates until the next layout traversal. This
     * may create temporary inconsistencies between what user sees on the screen and what
     * adapter contents have. This inconsistency is not important since it will be less than
     * 16ms but it might be a problem if you want to use ViewHolder position to access the
     * adapter. Sometimes, you may need to get the exact adapter position to do
     * some actions in response to user events. In that case, you should use this method which
     * will calculate the Adapter position of the ViewHolder.
     * <p>
     * Note that if you've called {@link RecyclerView.Adapter#notifyDataSetChanged()}, until the
     * next layout pass, the return value of this method will be {@link RecyclerView#NO_POSITION}.
     *
     * @return The adapter position of the item if it still exists in the adapter.
     * {@link RecyclerView#NO_POSITION} if item has been removed from the adapter,
     * {@link RecyclerView.Adapter#notifyDataSetChanged()} has been called after the last
     * layout pass or the ViewHolder has already been recycled.
     */
    int getAdapterPosition();

    /**
     * When LayoutManager supports animations, RecyclerView tracks 3 positions for ViewHolders
     * to perform animations.
     * <p>
     * If a ViewHolder was laid out in the previous onLayout call, old position will keep its
     * adapter index in the previous layout.
     *
     * @return The previous adapter index of the Item represented by this ViewHolder or
     * {@link RecyclerView#NO_POSITION} if old position does not exists or cleared (pre-layout is
     * complete).
     */
    int getOldPosition();

    /**
     * Returns The itemId represented by this ViewHolder.
     *
     * @return The item's id if adapter has stable ids, {@link RecyclerView#NO_ID}
     * otherwise
     */
    long getItemId();

    /**
     * @return The view type of this ViewHolder.
     */
    int getItemViewType();

    @NonNull
    String toString();
}


<|start_filename|>src/main/java/com/tenor/android/core/util/AbstractWeakReferenceUtils.java<|end_filename|>
package com.tenor.android.core.util;

import android.support.annotation.Nullable;

import java.lang.ref.WeakReference;

/**
 * The weak reference utility class
 */
public abstract class AbstractWeakReferenceUtils {

    public static <T> boolean isAlive(@Nullable final WeakReference<T> weakReference) {
        return weakReference != null && weakReference.get() != null;
    }
}


<|start_filename|>src/main/java/com/tenor/android/core/util/AbstractIOUtils.java<|end_filename|>
package com.tenor.android.core.util;

import java.io.Closeable;
import java.io.IOException;

public class AbstractIOUtils {

    public static void close(final Closeable... closeables) {
        if (closeables == null || closeables.length <= 0) {
            return;
        }

        try {
            for (Closeable closeable : closeables) {
                if (closeable == null) {
                    continue;
                }
                closeable.close();
            }
        } catch (final IOException ignored) {
            // do nothing
        }
    }
}


<|start_filename|>src/main/java/com/tenor/android/core/concurrent/IConcurrentFifoQueue.java<|end_filename|>
package com.tenor.android.core.concurrent;

import android.support.annotation.NonNull;

import java.util.Queue;

public interface IConcurrentFifoQueue<T> {

    /**
     * @return the size of the {@link Queue} of {@link T}
     */
    int add(@NonNull T event);

    /**
     * Retrieves and removes the head of {@link Queue},
     * or returns {@code null} if this queue is empty.
     *
     * @return the head of this queue, or {@code null} if this queue is empty
     */
    T poll();

    /**
     * Returns the number of elements in this queue.  If this queue
     * contains more than {@code Integer.MAX_VALUE} elements, returns
     * {@code Integer.MAX_VALUE}.
     *
     * @return the number of elements in this queue
     */
    int size();
}


<|start_filename|>src/main/java/com/tenor/android/core/constant/ViewAction.java<|end_filename|>
package com.tenor.android.core.constant;

import android.support.annotation.StringDef;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

public class ViewAction {

    @Retention(RetentionPolicy.CLASS)
    @StringDef({VIEW, SHARE, TAP})
    public @interface Value {
    }

    public static final String VIEW = "view";
    public static final String SHARE = "share";
    public static final String TAP = "tap";
}


<|start_filename|>src/main/java/com/tenor/android/core/util/AbstractLayoutManagerUtils.java<|end_filename|>
package com.tenor.android.core.util;

import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.OrientationHelper;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.StaggeredGridLayoutManager;
import android.view.View;
import android.view.ViewGroup;

import com.tenor.android.core.constant.ItemVisualPosition;

public abstract class AbstractLayoutManagerUtils {

    /**
     * @param layoutManager the given subclass of {@link RecyclerView.LayoutManager}
     * @return {@link OrientationHelper#HORIZONTAL} or {@link OrientationHelper#VERTICAL}
     */
    public static <T extends RecyclerView.LayoutManager> int getOrientation(@NonNull final T layoutManager) {
        if (layoutManager instanceof StaggeredGridLayoutManager) {
            return ((StaggeredGridLayoutManager) layoutManager).getOrientation();
        } else {
            return ((LinearLayoutManager) layoutManager).getOrientation();
        }
    }

    /**
     * @return the span count of {@link StaggeredGridLayoutManager} and {@link GridLayoutManager},
     * otherwise 1 for all other {@link RecyclerView.LayoutManager}
     */
    public static <T extends RecyclerView.LayoutManager> int getSpanCount(@Nullable final T layoutManager) {
        if (layoutManager instanceof StaggeredGridLayoutManager) {
            return ((StaggeredGridLayoutManager) layoutManager).getSpanCount();
        } else if (layoutManager instanceof GridLayoutManager) {
            return ((GridLayoutManager) layoutManager).getSpanCount();
        } else {
            return 1;
        }
    }

    /**
     * @return the span index of {@link StaggeredGridLayoutManager.LayoutParams} and {@link GridLayoutManager.LayoutParams},
     * otherwise 1 for all other {@link ViewGroup.LayoutParams}
     */
    public static <T extends ViewGroup.LayoutParams> int getSpanIndex(@Nullable final T layoutParams) {
        if (layoutParams instanceof StaggeredGridLayoutManager.LayoutParams) {
            return ((StaggeredGridLayoutManager.LayoutParams) layoutParams).getSpanIndex();
        } else if (layoutParams instanceof GridLayoutManager.LayoutParams) {
            return ((GridLayoutManager.LayoutParams) layoutParams).getSpanIndex();
        } else {
            return -1;
        }
    }

    @ItemVisualPosition.Value
    public static String getVisualPosition(@NonNull Context context, @Nullable final View view) {
        if (view == null) {
            return ItemVisualPosition.UNKNOWN;
        }
        final int spanIndex = getSpanIndex(view.getLayoutParams());
        return ItemVisualPosition.parse(context, spanIndex);
    }

    /**
     * @param layoutManager the given subclass of {@link RecyclerView.LayoutManager}
     * @param span          the span of interest
     * @return the last visible item position
     */
    public static <T extends RecyclerView.LayoutManager> int findLastVisibleItemPosition(@NonNull final T layoutManager,
                                                                                         final int span) {
        if (layoutManager instanceof StaggeredGridLayoutManager) {
            return ((StaggeredGridLayoutManager) layoutManager).findLastVisibleItemPositions(null)[span];
        } else {
            return ((LinearLayoutManager) layoutManager).findLastVisibleItemPosition();
        }
    }

    /**
     * @param layoutManager the given subclass of {@link RecyclerView.LayoutManager}
     * @return the last visible item position among such positions in all spans
     */
    public static <T extends RecyclerView.LayoutManager> int findLastVisibleItemPosition(@NonNull final T layoutManager) {
        final int count = getSpanCount(layoutManager);
        int position;
        int end = RecyclerView.NO_POSITION;
        for (int i = count - 1; i >= 0; i--) {
            position = findLastVisibleItemPosition(layoutManager, i);
            if (end == RecyclerView.NO_POSITION || end < position) {
                end = position;
            }
        }
        return end;
    }

    /**
     * @param layoutManager the given subclass of {@link RecyclerView.LayoutManager}
     * @param span          the span of interest
     * @return the last visible item position on a specific span
     */
    public static <T extends RecyclerView.LayoutManager> int findLastCompletelyVisibleItemPosition(@NonNull final T layoutManager,
                                                                                                   final int span) {
        if (layoutManager instanceof StaggeredGridLayoutManager) {
            return ((StaggeredGridLayoutManager) layoutManager).findLastCompletelyVisibleItemPositions(null)[span];
        } else {
            return ((LinearLayoutManager) layoutManager).findLastCompletelyVisibleItemPosition();
        }
    }

    /**
     * Find the last completely visible item position across all spans
     *
     * @param layoutManager the given subclass of {@link RecyclerView.LayoutManager}
     * @return the largest item position among such positions in all spans
     */
    public static <T extends RecyclerView.LayoutManager> int findLastCompletelyVisibleItemPosition(@NonNull final T layoutManager) {
        final int count = getSpanCount(layoutManager);
        int position;
        int end = RecyclerView.NO_POSITION;
        for (int i = count - 1; i >= 0; i--) {
            position = findLastCompletelyVisibleItemPosition(layoutManager, i);
            if (end == RecyclerView.NO_POSITION || end < position) {
                end = position;
            }
        }
        return end;
    }

    /**
     * @param layoutManager the given subclass of {@link RecyclerView.LayoutManager}
     * @param span          the span of interest
     * @return the first visible item position on a specific span
     */
    public static <T extends RecyclerView.LayoutManager> int findFirstVisibleItemPosition(@NonNull final T layoutManager,
                                                                                          final int span) {
        if (layoutManager instanceof StaggeredGridLayoutManager) {
            return ((StaggeredGridLayoutManager) layoutManager).findFirstVisibleItemPositions(null)[span];
        } else {
            return ((LinearLayoutManager) layoutManager).findFirstVisibleItemPosition();
        }
    }

    /**
     * Find the first visible item position across all spans
     *
     * @param layoutManager the given subclass of {@link RecyclerView.LayoutManager}
     * @return the smallest item position among the positions in all spans
     */
    public static <T extends RecyclerView.LayoutManager> int findFirstVisibleItemPosition(@NonNull final T layoutManager) {
        final int count = getSpanCount(layoutManager);
        int position;
        int start = RecyclerView.NO_POSITION;
        for (int i = 0; i < count; i++) {
            position = findFirstVisibleItemPosition(layoutManager, i);
            if (start == RecyclerView.NO_POSITION || start > position) {
                start = position;
            }
        }
        return start;
    }

    /**
     * @param layoutManager the given subclass of {@link RecyclerView.LayoutManager}
     * @param span          the span of interest
     * @return the first visible item position
     */
    public static <T extends RecyclerView.LayoutManager> int findFirstCompletelyVisibleItemPosition(@NonNull final T layoutManager,
                                                                                                    final int span) {
        if (layoutManager instanceof StaggeredGridLayoutManager) {
            return ((StaggeredGridLayoutManager) layoutManager).findFirstCompletelyVisibleItemPositions(null)[span];
        } else {
            return ((LinearLayoutManager) layoutManager).findFirstCompletelyVisibleItemPosition();
        }
    }

    /**
     * Find the first completely visible item position across all spans
     * <p>
     * Starting on finding the first completely visible item on the first span,
     * iterate through the spans to see if other span has smaller index for
     * first completely visible item.
     *
     * @param layoutManager the given subclass of {@link RecyclerView.LayoutManager}
     * @return the smallest item position among the positions in all spans
     */
    public static <T extends RecyclerView.LayoutManager> int findFirstCompletelyVisibleItemPosition(@NonNull final T layoutManager) {
        final int count = getSpanCount(layoutManager);
        int position;
        int start = RecyclerView.NO_POSITION;
        for (int i = 0; i < count; i++) {
            position = findFirstCompletelyVisibleItemPosition(layoutManager, i);
            if (start == RecyclerView.NO_POSITION || start > position) {
                start = position;
            }
        }
        return start;
    }

    /**
     * @param layoutManager the given subclass of {@link RecyclerView.LayoutManager}
     * @param rtl           whether the layout is right to left
     */
    public static <T extends RecyclerView.LayoutManager> void setReverseLayout(@Nullable final T layoutManager,
                                                                               final boolean rtl) {
        if (layoutManager instanceof StaggeredGridLayoutManager) {
            ((StaggeredGridLayoutManager) layoutManager).setReverseLayout(rtl);
        } else if (layoutManager instanceof LinearLayoutManager) {
            ((LinearLayoutManager) layoutManager).setReverseLayout(rtl);
        } else {
            // do nothing
        }
    }

    /**
     * Check if the device is on right to left mode
     *
     * @param recyclerView the {@link RecyclerView}
     * @return true if the device is on right to left mode
     */
    public static boolean isRightToLeft(@NonNull RecyclerView recyclerView) {
        return AbstractUIUtils.isRightToLeft(recyclerView.getContext())
                && getOrientation(recyclerView.getLayoutManager()) == OrientationHelper.HORIZONTAL;
    }

    /**
     * Get the rtl-safe visible range of a given {@link RecyclerView}
     *
     * @param recyclerView the {@link RecyclerView}
     * @return the int[] contains the range of interest, [0] contains the start and [1] contains the end
     */
    public static int[] getVisibleRange(@NonNull RecyclerView recyclerView) {
        final boolean rtl = isRightToLeft(recyclerView);
        int[] range = new int[]{RecyclerView.NO_POSITION, RecyclerView.NO_POSITION};
        int start, end;
        final int spanCount = getSpanCount(recyclerView.getLayoutManager());
        for (int span = 0; span < spanCount; span++) {
            /*
             * [ANDROID-2198]
             *
             * The order of return int[] from `#findFirstVisibleItemPositions()` method doesn't
             * reflect horizontal orientation of right to left languages
             */
            if (!rtl) {
                // left to right languages
                start = findFirstVisibleItemPosition(recyclerView.getLayoutManager(), span);
                end = findLastVisibleItemPosition(recyclerView.getLayoutManager(), span);
            } else {
                // right to left languages
                start = findLastVisibleItemPosition(recyclerView.getLayoutManager(), span);
                end = findFirstVisibleItemPosition(recyclerView.getLayoutManager(), span);
            }

            // update mDraggingStart and mDraggingEnd if the new bounds are wider than the old one
            if (range[0] == RecyclerView.NO_POSITION) {
                range[0] = start;
            }

            if (start > RecyclerView.NO_POSITION && start < range[0]) {
                range[0] = start;
            }

            if (range[1] == RecyclerView.NO_POSITION) {
                range[1] = end;
            }

            if (end > RecyclerView.NO_POSITION && end > range[1]) {
                range[1] = end;
            }
        }
        return range;
    }
}


<|start_filename|>src/main/java/com/tenor/android/core/constant/AspectRatioRange.java<|end_filename|>
package com.tenor.android.core.constant;

public class AspectRatioRange {

    public static final String ALL = "all";
    public static final String STANDARD = "standard";
    public static final String WIDE = "wide";
}


<|start_filename|>src/main/java/com/tenor/android/core/model/impl/BadgeInfo.java<|end_filename|>
package com.tenor.android.core.model.impl;


import android.support.annotation.FloatRange;
import android.support.annotation.NonNull;

import com.google.gson.annotations.SerializedName;
import com.tenor.android.core.util.AbstractListUtils;

import java.io.Serializable;
import java.util.Collections;
import java.util.List;

public class BadgeInfo implements Serializable {

    private static final long serialVersionUID = 2519123239149906317L;

    @SerializedName("view_acceptance_fraction")
    private float threshold;
    private List<ItemBadge> badges;

    @FloatRange(from = 0.01f, to = 1f)
    public float getThreshold() {
        return threshold >= 0.01f && threshold <= 1f ? threshold : 1f;
    }

    public boolean hasBadges() {
        return !AbstractListUtils.isEmpty(badges);
    }

    @NonNull
    public List<ItemBadge> getBadges() {
        return hasBadges() ? badges : Collections.<ItemBadge>emptyList();
    }
}


<|start_filename|>src/main/java/com/tenor/android/core/response/impl/GifsResponse.java<|end_filename|>
package com.tenor.android.core.response.impl;

import android.support.annotation.NonNull;
import android.text.TextUtils;

import com.tenor.android.core.constant.StringConstant;
import com.tenor.android.core.model.impl.Result;
import com.tenor.android.core.response.AbstractResponse;
import com.tenor.android.core.util.AbstractListUtils;

import java.util.Collections;
import java.util.List;

/**
 * The response of GIFs
 */
public class GifsResponse extends AbstractResponse {
    private static final long serialVersionUID = 2805215887381282883L;
    private String next;
    private List<Result> results;

    public boolean hasNext() {
        return !TextUtils.isEmpty(next);
    }

    @NonNull
    public String getNext() {
        return StringConstant.getOrEmpty(next);
    }

    @NonNull
    public List<Result> getResults() {
        return !AbstractListUtils.isEmpty(results) ? results : Collections.<Result>emptyList();
    }
}


<|start_filename|>src/main/java/com/tenor/android/core/network/IApiService.java<|end_filename|>
package com.tenor.android.core.network;


import android.support.annotation.NonNull;

public interface IApiService<T> {

    T get();

    T create(@NonNull ApiService.Builder<T> builder);

    @NonNull
    String getApiKey();

    /**
     * Return host endpoint.
     * If in BuildConfig.DEBUG, use the developer servers.
     * Otherwise, use the production servers
     *
     * @return String of the api <b>endpoint host</b>
     */
    @NonNull
    String getEndpoint();
}


<|start_filename|>src/main/java/com/tenor/android/core/weakref/WeakRefHandler.java<|end_filename|>
package com.tenor.android.core.weakref;

import android.app.Activity;
import android.os.Handler;
import android.os.Looper;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;

import com.tenor.android.core.util.AbstractWeakReferenceUtils;

import java.lang.ref.WeakReference;

/**
 * {@link Handler} with a {@link WeakReference} on its calling context
 * <p/>
 * This is intended to avoid unintentional leakage on {@link Activity} and {@link android.app.Fragment}
 */
public class WeakRefHandler<CTX> extends Handler implements IWeakRefObject<CTX> {

    private final WeakReference<CTX> mWeakRef;

    public WeakRefHandler(@NonNull CTX ctx, @Nullable Looper looper) {
        this(new WeakReference<>(ctx), looper);
    }

    public WeakRefHandler(@NonNull WeakReference<CTX> weakRef, @Nullable Looper looper) {
        this(weakRef, looper, null);
    }

    public WeakRefHandler(@NonNull CTX ctx, @Nullable Looper looper, @Nullable Callback callback) {
        this(new WeakReference<>(ctx), looper, callback);
    }

    public WeakRefHandler(@NonNull WeakReference<CTX> weakRef, @Nullable Looper looper, @Nullable Callback callback) {
        super(looper, callback);
        mWeakRef = weakRef;
        /*
         * This exception is mirror from Handler(Callback callback, boolean async), and
         * the reason for having a @Nullable annotated Looper to throw out RuntimeException are:
         *
         * (1) the constructor of Handler is not annotated with @NonNull even thought its document states
         * its Looper input "must not be null".
         *
         * (2) methods, such as Looper.myLooper() is annotated with @Nullable
         */
        if (looper == null) {
            throw new RuntimeException(
                    "Can't create handler inside thread that has not called Looper.prepare()");
        }
    }

    @NonNull
    @Override
    public WeakReference<CTX> getWeakRef() {
        return mWeakRef;
    }

    @Override
    public boolean hasRef() {
        return AbstractWeakReferenceUtils.isAlive(mWeakRef);
    }
}

<|start_filename|>src/main/java/com/tenor/android/core/model/impl/FeaturedInfo.java<|end_filename|>
package com.tenor.android.core.model.impl;


import android.support.annotation.NonNull;

import com.google.gson.annotations.SerializedName;
import com.tenor.android.core.constant.StringConstant;

import java.io.Serializable;

public class FeaturedInfo implements Serializable {
    private static final long serialVersionUID = -1486902705491791354L;

    @SerializedName("feature_text")
    private String mFeatureText;

    @SerializedName("button_link")
    private String mButtonLink;

    @SerializedName("button_text")
    private String mButtonText;

    @NonNull
    public String getFeatureText() {
        return StringConstant.getOrEmpty(mFeatureText);
    }

    @NonNull
    public String getButtonLink() {
        return StringConstant.getOrEmpty(mButtonLink);
    }

    @NonNull
    public String getButtonText() {
        return StringConstant.getOrEmpty(mButtonText);
    }
}


<|start_filename|>src/main/java/com/tenor/android/core/response/impl/TrendingTermResponse.java<|end_filename|>
package com.tenor.android.core.response.impl;

import android.support.annotation.NonNull;

import com.tenor.android.core.response.AbstractResponse;
import com.tenor.android.core.util.AbstractListUtils;

import java.util.Collections;
import java.util.List;

/**
 * The response of trending terms
 */
public class TrendingTermResponse extends AbstractResponse {

    private static final long serialVersionUID = -8215681889738059793L;
    private List<String> results;

    @NonNull
    public List<String> getTrendingTerms() {
        return !AbstractListUtils.isEmpty(results) ? results : Collections.<String>emptyList();
    }
}


<|start_filename|>src/main/java/com/tenor/android/core/response/impl/PackResponse.java<|end_filename|>
package com.tenor.android.core.response.impl;

import android.support.annotation.NonNull;

import com.tenor.android.core.constant.StringConstant;

public class PackResponse extends GifsResponse {

    private static final long serialVersionUID = -6425188470381821462L;

    private String id;
    private String name;

    @NonNull
    public String getId() {
        return StringConstant.getOrEmpty(id);
    }

    @NonNull
    public String getName() {
        return StringConstant.getOrEmpty(name);
    }
}

<|start_filename|>src/main/java/com/tenor/android/core/widget/adapter/AbstractRVAdapter.java<|end_filename|>
package com.tenor.android.core.widget.adapter;

import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;

import com.tenor.android.core.util.AbstractWeakReferenceUtils;
import com.tenor.android.core.view.IBaseView;
import com.tenor.android.core.weakref.IWeakRefObject;

import java.lang.ref.WeakReference;

/**
 * The abstract recycler view adapter class that all recycler view adapter classes should extend from
 *
 * @param <CTX> the referenced context
 * @param <VH>  the view holder type extending from {@link RecyclerView.ViewHolder}
 */
public abstract class AbstractRVAdapter<CTX extends IBaseView, VH extends RecyclerView.ViewHolder>
        extends RecyclerView.Adapter<VH> implements IWeakRefObject<CTX> {

    private final WeakReference<CTX> mWeakRef;

    public AbstractRVAdapter(@Nullable final CTX ctx) {
        mWeakRef = new WeakReference<>(ctx);
    }

    public AbstractRVAdapter(@NonNull final WeakReference<CTX> weakRef) {
        mWeakRef = weakRef;
    }

    /**
     * Get context, use hasContext() to check its existence first
     */
    @Nullable
    protected Context getContext() {
        return hasRef() ? getWeakRef().get().getContext() : null;
    }

    protected boolean hasContext() {
        return hasRef() && getContext() != null;
    }

    @NonNull
    @Override
    public WeakReference<CTX> getWeakRef() {
        return mWeakRef;
    }

    @Override
    public boolean hasRef() {
        return AbstractWeakReferenceUtils.isAlive(mWeakRef);
    }
}


<|start_filename|>src/main/java/com/tenor/android/core/response/impl/AnonIdResponse.java<|end_filename|>
package com.tenor.android.core.response.impl;

import android.support.annotation.NonNull;

import com.google.gson.annotations.SerializedName;
import com.tenor.android.core.constant.StringConstant;
import com.tenor.android.core.response.AbstractResponse;

/**
 * The response of anon id
 */
public class AnonIdResponse extends AbstractResponse {

    private static final long serialVersionUID = -1814022099833831972L;
    @SerializedName(value = "anonid", alternate = {"anon_id"})
    private String id;
    @SerializedName("localid")
    private int localId;

    @NonNull
    public String getId() {
        return StringConstant.getOrEmpty(id);
    }

    public int getLocalId() {
        return localId;
    }
}


<|start_filename|>src/main/java/com/tenor/android/core/weakref/IWeakRefHandler.java<|end_filename|>
package com.tenor.android.core.weakref;

import android.support.annotation.NonNull;

/**
 * Interface of {@link WeakRefHandler}
 */
public interface IWeakRefHandler<CTX> {
    @NonNull
    WeakRefHandler<CTX> getHandler();
}

<|start_filename|>src/main/java/com/tenor/android/core/constant/MediaFilter.java<|end_filename|>
package com.tenor.android.core.constant;

import android.support.annotation.StringDef;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

public class MediaFilter {

    @Retention(RetentionPolicy.CLASS)
    @StringDef({MINIMAL, BASIC})
    public @interface Value {
    }

    public static final String MINIMAL = "minimal";
    public static final String BASIC = "basic";
}


<|start_filename|>src/main/java/com/tenor/android/core/loader/ITaskParams.java<|end_filename|>
package com.tenor.android.core.loader;


import android.support.annotation.NonNull;

import java.io.Serializable;

public interface ITaskParams extends Serializable {

    /**
     * @return the unique id of the task
     */
    @NonNull
    String getId();
}


<|start_filename|>src/main/java/com/tenor/android/core/util/AbstractLocaleUtils.java<|end_filename|>
package com.tenor.android.core.util;

import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.telephony.TelephonyManager;

import com.tenor.android.core.constant.StringConstant;

import java.text.SimpleDateFormat;
import java.util.Locale;

/**
 * The locale utility class
 */
public abstract class AbstractLocaleUtils {

    public static final String ISO_US = "us";

    /**
     * Get the {@link Locale} that is currently used by the device
     *
     * @return the {@link Locale} that is currently used by the device
     */
    @NonNull
    public static Locale getCurrentLocale(@Nullable Context context) {
        if (context == null) {
            return Locale.getDefault();
        }

        Resources resources = context.getResources();
        if (resources == null) {
            return Locale.getDefault();
        }

        Configuration configuration = resources.getConfiguration();
        if (configuration == null || configuration.locale == null) {
            return Locale.getDefault();
        }
        return configuration.locale;
    }

    /**
     * Get the name of {@link Locale} that is currently used by the device
     *
     * @return the name {@link Locale} that is currently used by the device
     */
    @NonNull
    public static String getCurrentLocaleName(@NonNull final Context context) {
        return getCurrentLocale(context).toString();
    }

    /**
     * Get current UTC offset, such as "-0700" using the {@link Locale#US}
     *
     * @param context the given context
     * @return the current UTC offset, such as "-0700"
     */
    @NonNull
    public static String getUtcOffset(@Nullable Context context) {
        return getUtcOffset(context, Locale.US);
    }

    /**
     * Get current UTC offset, such as "-0700"
     *
     * @param context the given context
     * @param locale  the {@link Locale}
     * @return the current UTC offset, such as "-0700"
     */
    @NonNull
    public static String getUtcOffset(@Nullable Context context, @Nullable Locale locale) {
        if (context == null) {
            return StringConstant.EMPTY;
        }

        if (locale == null) {
            return getUtcOffset(context, Locale.US);
        }
        return new SimpleDateFormat("ZZZ", locale).format(System.currentTimeMillis());
    }

    /**
     * Is the phone currently within the U.S.
     *
     * @param context the given context
     * @return true if the phone is currently within the U.S.
     */
    public static boolean isUSRegion(@Nullable final Context context) {
        if (context == null) {
            return false;
        }

        /*
         * TELEPHONY_SERVICE needs permission, "android.permission.READ_PHONE_STATE"
         */
        final TelephonyManager telephonyManager;
        try {
            telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
        } catch (Throwable ignored) {
            // no "android.permission.READ_PHONE_STATE" permission
            return false;
        }

        if (telephonyManager == null) {
            return false;
        }

        if (AbstractNetworkUtils.isWifiConnected(context)
                && telephonyManager.getPhoneType() != TelephonyManager.NETWORK_TYPE_CDMA) {
            return ISO_US.equals(telephonyManager.getNetworkCountryIso());
        }

        return telephonyManager.getSimState() != TelephonyManager.SIM_STATE_ABSENT
                && ISO_US.equals(telephonyManager.getSimCountryIso());
    }
}


<|start_filename|>src/main/java/com/tenor/android/core/loader/GlideLoader.java<|end_filename|>
package com.tenor.android.core.loader;

import android.graphics.drawable.Drawable;
import android.support.annotation.NonNull;
import android.widget.ImageView;

import com.bumptech.glide.GenericRequestBuilder;
import com.bumptech.glide.load.resource.drawable.GlideDrawable;
import com.bumptech.glide.request.animation.GlideAnimation;
import com.bumptech.glide.request.target.GlideDrawableImageViewTarget;
import com.tenor.android.core.model.impl.Media;

public class GlideLoader {

    public static GenericRequestBuilder applyDimens(@NonNull GenericRequestBuilder requestBuilder,
                                                    @NonNull GlideTaskParams payload) {
        final Media media = payload.getMedia();
        if (media != null) {
            requestBuilder.override(media.getWidth(), media.getHeight());
        }
        return requestBuilder;
    }

    public static <T extends ImageView> void load(@NonNull final GenericRequestBuilder requestBuilder,
                                                  @NonNull final GlideTaskParams<T> payload) {

        if (payload.isThumbnail()) {
            requestBuilder.thumbnail(payload.getThumbnailMultiplier());
        }

        requestBuilder.placeholder(payload.getPlaceholder())
                .into(new GlideDrawableImageViewTarget(payload.getTarget()) {
                    @Override
                    public void onLoadFailed(Exception e, Drawable errorDrawable) {
                        if (payload.getCurrentRetry() < payload.getMaxRetry()) {
                            payload.incrementCurrentRetry();
                            load(requestBuilder, payload);
                        } else {
                            super.onLoadFailed(e, errorDrawable);
                            payload.getListener().failure(payload.getTarget(), errorDrawable);
                        }
                    }

                    @Override
                    public void onResourceReady(GlideDrawable resource, GlideAnimation<? super GlideDrawable> animation) {
                        super.onResourceReady(resource, animation);
                        payload.getListener().success(payload.getTarget(), resource);
                    }
                });
    }
}


<|start_filename|>src/main/java/com/tenor/android/core/model/impl/Tag.java<|end_filename|>
package com.tenor.android.core.model.impl;

import android.support.annotation.NonNull;

import com.google.gson.annotations.SerializedName;
import com.tenor.android.core.constant.StringConstant;
import com.tenor.android.core.model.IGif;

/**
 * The model of {@link Tag}, which contains a collection of {@link Result}
 */
public class Tag implements IGif {
    private static final long serialVersionUID = 2978652640985303628L;
    private String image;
    private String name;

    @SerializedName(value = "path", alternate = {"url"})
    private String path;
    @SerializedName("searchterm")
    String searchTerm;

    @SerializedName("dims")
    private int[] dimensions;

    public int getWidth() {
        return dimensions != null && dimensions.length == 2 ? dimensions[0] : -1;
    }

    public int getHeight() {
        return dimensions != null && dimensions.length == 2 ? dimensions[1] : -1;
    }

    public float getAspectRatio() {
        return (float) getWidth() / getHeight();
    }

    public boolean hasAspectRatio() {
        return getWidth() > 0 && getHeight() > 0;
    }

    /**
     * @return the media url of a GIF that describes this {@link Tag}{@link Tag}
     */
    @NonNull
    public String getImage() {
        return StringConstant.getOrEmpty(image);
    }

    /**
     * @return the name of the {@link Tag}
     */
    @NonNull
    public String getName() {
        return StringConstant.getOrEmpty(name);
    }

    @NonNull
    public String getPath() {
        return StringConstant.getOrEmpty(path);
    }

    /**
     * @return the {@link String} to be used to search the GIFs related to this {@link Tag}
     */
    public String getSearchTerm() {
        return StringConstant.getOrEmpty(searchTerm);
    }

    @NonNull
    @Override
    public String getId() {
        return StringConstant.getOrEmpty(name);
    }
}


<|start_filename|>src/main/java/com/tenor/android/core/response/WeakRefCallback.java<|end_filename|>
package com.tenor.android.core.response;

import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;

import com.tenor.android.core.constant.StringConstant;
import com.tenor.android.core.util.AbstractIOUtils;
import com.tenor.android.core.weakref.WeakRefObject;
import com.tenor.android.core.weakref.WeakRefRunnable;
import com.tenor.android.core.weakref.WeakRefUiHandler;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.ref.WeakReference;
import java.net.UnknownHostException;

import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;

/**
 * A {@link Callback} with {@link WeakReference} associated with the caller {@link Context}
 * <p>
 * In case of the view is being recycled, this callback will end gracefully
 */
public abstract class WeakRefCallback<CTX, T> extends WeakRefObject<CTX> implements Callback<T> {

    public static final String ERROR_UNKNOWN = "unknown error";
    public static final String ERROR_NULL_RESPONSE = "response is null";

    private final WeakRefUiHandler<CTX> mUiThread;
    private boolean mReportNetworkDropAsException;

    /**
     * Constructor
     * <p>
     * In case of the view is being recycled, this callback will end gracefully
     *
     * @param ctx the caller view
     */
    public WeakRefCallback(@NonNull CTX ctx) {
        this(new WeakReference<>(ctx));
    }

    public WeakRefCallback(@NonNull WeakReference<CTX> weakRef) {
        super(weakRef);
        mUiThread = new WeakRefUiHandler<>(weakRef);
    }

    protected WeakRefUiHandler<CTX> getUiThread() {
        return mUiThread;
    }

    public void setReportNetworkDropAsException(boolean report) {
        mReportNetworkDropAsException = report;
    }

    public boolean isReportNetworkDropAsException() {
        return mReportNetworkDropAsException;
    }

    /**
     * Override this method to conduct general network performance measures
     */
    public void measureResponse(@NonNull CTX ctx, @Nullable okhttp3.Response rawResponse) {

    }

    @Override
    public void onResponse(Call<T> call, @Nullable final Response<T> response) {
        if (!hasRef()) {
            return;
        }

        getUiThread().post(new WeakRefRunnable<CTX>(getWeakRef()) {
            @Override
            public void run(@NonNull CTX ctx) {
                if (response == null) {
                    failure(ctx, new NullPointerException(ERROR_NULL_RESPONSE), null);
                    return;
                }

                if (response.isSuccessful() && response.body() != null) {
                    success(ctx, response.body(), response.raw());
                } else {
                    failure(ctx, createThrowable(response.errorBody()), response.raw());
                }
            }
        });
    }

    @NonNull
    private static String parseError(@NonNull InputStream is) {
        BufferedReader bufferedReader = null;
        StringBuilder sb = new StringBuilder();
        try {
            bufferedReader = new BufferedReader(new InputStreamReader(is));
            String line;
            while ((line = bufferedReader.readLine()) != null) {
                sb.append(line);
            }
        } catch (Exception ignored) {
        } finally {
            AbstractIOUtils.close(bufferedReader);
        }
        return sb.toString();
    }

    @NonNull
    private static Throwable createThrowable(@Nullable ResponseBody errorBody) {
        if (errorBody == null) {
            return new Throwable(ERROR_UNKNOWN);
        }

        String message = StringConstant.EMPTY;
        try {
            message = errorBody.string();
        } catch (Throwable ignored) {
        }

        if (!TextUtils.isEmpty(message)) {
            return new Throwable(message);
        }

        try {
            message = parseError(errorBody.byteStream());
        } catch (Throwable ignored) {
        }

        if (!TextUtils.isEmpty(message)) {
            return new Throwable(message);
        }
        return new Throwable(ERROR_UNKNOWN);
    }

    @Override
    public final void onFailure(Call<T> call, @Nullable final Throwable throwable) {
        if (!hasRef()) {
            return;
        }

        getUiThread().post(new WeakRefRunnable<CTX>(getWeakRef()) {
            @Override
            public void run(@NonNull CTX ctx) {

                if (throwable == null) {
                    failure(ctx, new Throwable(ERROR_UNKNOWN), null);
                    return;
                }

                if (isReportNetworkDropAsException()) {
                    failure(ctx, throwable, null);
                    return;
                }

                final boolean isNetworkDrop = "canceled".equalsIgnoreCase(throwable.getMessage())
                        || throwable instanceof UnknownHostException;
                if (isNetworkDrop) {
                    onNetworkDropCaught(throwable);
                    return;
                }
                failure(ctx, throwable, null);
            }
        });
    }

    public void onNetworkDropCaught(@NonNull Throwable throwable) {

    }

    public abstract void success(@NonNull CTX ctx, @Nullable T response);

    public abstract void failure(@NonNull CTX ctx, @Nullable Throwable throwable);

    private void success(@NonNull CTX ctx, @Nullable T response, @NonNull okhttp3.Response rawResponse) {
        try {
            measureResponse(ctx, rawResponse);
        } catch (Throwable ignored) {
        }
        success(ctx, response);
    }

    private void failure(@NonNull CTX ctx, @Nullable Throwable throwable, @Nullable okhttp3.Response rawResponse) {
        try {
            measureResponse(ctx, rawResponse);
        } catch (Throwable ignored) {
        }
        failure(ctx, throwable);
    }
}


<|start_filename|>src/main/java/com/tenor/android/core/service/IAaidListener.java<|end_filename|>
package com.tenor.android.core.service;

import android.support.annotation.NonNull;

import java.io.Serializable;


public interface IAaidListener extends Serializable {
    void success(@NonNull String aaid);

    void failure(@AaidInfo.State int state);
}


<|start_filename|>src/main/java/com/tenor/android/core/measurable/ViewHolderDataManager.java<|end_filename|>
package com.tenor.android.core.measurable;

import android.content.Context;
import android.support.annotation.NonNull;

import com.tenor.android.core.concurrent.ConcurrentFifoQueue;
import com.tenor.android.core.network.ApiClient;
import com.tenor.android.core.util.AbstractLocaleUtils;

import java.util.ArrayList;
import java.util.List;

/**
 * Queue and send {@link MeasurableViewHolderData}
 */
public class ViewHolderDataManager extends ConcurrentFifoQueue<MeasurableViewHolderEvent>
        implements IViewHolderDataManager {

    private static final int BATCH_SIZE = 5;
    private static final int MAX_POOL_SIZE = 6 * BATCH_SIZE;

    public ViewHolderDataManager() {
        super();
    }

    /**
     * Push a {@link MeasurableViewHolderEvent} to queue
     *
     * @param context the context
     * @param event   the serialized {@link MeasurableViewHolderEvent}
     */
    public synchronized void push(@NonNull final Context context,
                                  @NonNull final MeasurableViewHolderEvent event) {
        try {
            add(event);
        } catch (Throwable ignored) {
        }

        if (size() >= MAX_POOL_SIZE) {
            // flush
            send(context, Integer.MAX_VALUE);
        }

        if (size() >= BATCH_SIZE) {
            send(context, BATCH_SIZE);
        }
    }

    /**
     * Send analytic events to server
     *
     * @param context   the context
     * @param batchSize the size of the analytic data batch
     */
    public synchronized void send(@NonNull final Context context, int batchSize) {

        List<MeasurableViewHolderEvent> list = new ArrayList<>();
        try {
            while (size() > 0 && list.size() < batchSize) {
                list.add(poll());
            }
            // TODO: make this a schedule task in the future
            ApiClient.registerActions(context, list);
        } catch (Throwable throwable) {
        }
    }

    /*
     * ==============
     * Static Methods
     * ==============
     */
    private static ViewHolderDataManager sManager;

    public static ViewHolderDataManager get() {
        if (sManager == null) {
            sManager = new ViewHolderDataManager();
        }
        return sManager;
    }

    /**
     * Queue up a {@link MeasurableViewHolderData}
     *
     * @param context the context
     * @param data    the serialized {@link MeasurableViewHolderData}
     */
    public synchronized static void push(@NonNull final Context context,
                                         @NonNull final MeasurableViewHolderData data) {
        final String utcOffset = AbstractLocaleUtils.getUtcOffset(context);
        get().push(context, new MeasurableViewHolderEvent(data, utcOffset));
    }
}


<|start_filename|>src/main/java/com/tenor/android/core/util/AbstractListUtils.java<|end_filename|>
package com.tenor.android.core.util;

import android.support.annotation.Nullable;

import java.util.Collection;
import java.util.List;

/**
 * Contains helper methods to manipulate {@link List}
 */
public abstract class AbstractListUtils {

    /**
     * Check if the given array is null or with no item in it
     *
     * @param list the given {@link List}
     * @param <T>  the type of the list items
     */
    public static <T> boolean isEmpty(@Nullable final T[] list) {
        return list == null || list.length <= 0;
    }

    /**
     * Check if the given list is null or with no item in it
     *
     * @param list the given {@link List}
     * @param <T>  the type of the list items
     */
    public static <T> boolean isEmpty(@Nullable final Collection<T> list) {
        return list == null || list.isEmpty();
    }
}


<|start_filename|>src/main/java/com/tenor/android/core/weakref/WeakRefCountDownTimer.java<|end_filename|>
package com.tenor.android.core.weakref;

import android.os.CountDownTimer;
import android.support.annotation.NonNull;

import com.tenor.android.core.util.AbstractWeakReferenceUtils;

import java.lang.ref.WeakReference;

public abstract class WeakRefCountDownTimer<CTX> extends CountDownTimer implements IWeakRefObject<CTX> {

    private final WeakReference<CTX> mWeakRef;

    public WeakRefCountDownTimer(@NonNull final CTX ctx,
                                 long millisInFuture, long countDownInterval) {
        super(millisInFuture, countDownInterval);
        mWeakRef = new WeakReference<>(ctx);
    }

    public WeakRefCountDownTimer(@NonNull final WeakReference<CTX> weakRef,
                                 long millisInFuture, long countDownInterval) {
        super(millisInFuture, countDownInterval);
        mWeakRef = weakRef;
    }

    @NonNull
    @Override
    public WeakReference<CTX> getWeakRef() {
        return mWeakRef;
    }

    @Override
    public boolean hasRef() {
        return AbstractWeakReferenceUtils.isAlive(mWeakRef);
    }

    @Override
    public final void onTick(long millisUntilFinished) {
        if (!hasRef()) {
            cancel();
            return;
        }
        //noinspection ConstantConditions
        onTick(getWeakRef().get(), millisUntilFinished);
    }

    @Override
    public final void onFinish() {
        if (!hasRef()) {
            cancel();
            return;
        }
        //noinspection ConstantConditions
        onFinish(getWeakRef().get());
    }

    public abstract void onTick(@NonNull CTX ctx, long millisUntilFinished);

    public abstract void onFinish(@NonNull CTX ctx);
}


<|start_filename|>src/main/java/com/tenor/android/core/measurable/MeasurableViewHolderHelper.java<|end_filename|>
package com.tenor.android.core.measurable;


import android.graphics.Rect;
import android.support.annotation.FloatRange;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.View;

import com.tenor.android.core.util.AbstractUIUtils;

/**
 * Helper of {@link MeasurableViewHolder}
 */
public class MeasurableViewHolderHelper {

    /**
     * Get the visible fraction of the given  {@link View} in the given {@link RecyclerView}
     *
     * @param recyclerView the given {@link RecyclerView}
     * @param itemView     the given {@link View}
     * @param threshold    the percentage of a view's area to be shown in order to consider it as being viewed
     * @return the visible fraction of the given  {@link View} in the given {@link RecyclerView}
     */
    @FloatRange(from = 0.01f, to = 1f)
    public static float calculateVisibleFraction(@NonNull RecyclerView recyclerView,
                                                 @NonNull View itemView,
                                                 float threshold) {
        /*
         * Special distributive measuring approach to handle the following issues:
         *
         * [ANDROID-1776]
         * view just went off visible region will report visible width equals to
         * the actual width of the width due to pre-caching
         *
         * [ANDROID-1786]
         * Incorrect reporting of in/visible status on some OEMs
         * issue caused by OEM custom implementation on how to define
         * the left and right of a off visible region
         *
         * Stock OS define off visible region left as negative number,
         * whereas some other OEM defines it as 0, so use #getLocationInWindow() instead
         */
        Rect ivRect = new Rect();
        itemView.getGlobalVisibleRect(ivRect);
        if (ivRect.isEmpty()) {
            return 0.01f;
        }

        Rect rvRect = new Rect();
        recyclerView.getGlobalVisibleRect(rvRect);

        int[] location = new int[2];
        itemView.getLocationInWindow(location);

        final boolean rtl = AbstractUIUtils.isRightToLeft(recyclerView.getContext());
        final int width = itemView.getMeasuredWidth();
        final int height = itemView.getMeasuredHeight();

        // ratio for left bound
        final float leftBoundRatio;
        if (!rtl) {
            // left to right languages
            leftBoundRatio = 1f - threshold;
        } else {
            // right to left languages
            leftBoundRatio = threshold;
        }

        final int left = (int) (rvRect.left - leftBoundRatio * width);
        final int right = (int) (rvRect.right - (1f - leftBoundRatio) * width);
        final int top = (int) (rvRect.top - (1f - threshold) * height);
        final int bottom = (int) (rvRect.bottom - threshold * height);

        // horizontally out of bound
        final boolean hoob = location[0] < left || location[0] > right;

        // vertically out of bound
        final boolean voob = location[1] < top || location[1] > bottom;
        if (hoob || voob) {
            return 0.01f;
        }

        float wRatio = (float) ivRect.width() / width;
        float hRatio = (float) ivRect.height() / height;

        /*
         * the non-moving direction will be always 1.0f,
         * so the direction of interest is the smaller one of the two,
         * and the final number is capped between 0.01f and 1.0f
         */
        float percentage = Math.min(wRatio, hRatio);
        percentage = Math.min(percentage, 1f);
        percentage = Math.max(percentage, 0.01f);
        return percentage;
    }
}


<|start_filename|>src/main/java/com/tenor/android/core/widget/viewholder/StaggeredGridLayoutItemViewHolder.java<|end_filename|>
package com.tenor.android.core.widget.viewholder;

import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.widget.StaggeredGridLayoutManager;
import android.view.View;
import android.view.ViewGroup;

import com.tenor.android.core.measurable.MeasurableViewHolder;
import com.tenor.android.core.util.AbstractUIUtils;
import com.tenor.android.core.view.IBaseView;

/**
 * The abstract recycler view view holder class that all recycler view view holder classes should extend from
 *
 * @param <CTX> the referenced context
 */
public class StaggeredGridLayoutItemViewHolder<CTX extends IBaseView> extends MeasurableViewHolder<CTX> {

    /**
     * Interface used to communicate info between adapter and view holder
     */
    public interface OnClickListener {
        /**
         * Send back the current select position to adapter
         */
        void onClick(int position);
    }

    public StaggeredGridLayoutItemViewHolder(@NonNull View itemView, @NonNull CTX context) {
        super(itemView, context);
    }

    /**
     * Get context, use hasWeakRef() to check its existence first
     */
    @Nullable
    public Context getContext() {
        return hasRef() ? getWeakRef().get().getContext() : null;
    }

    public boolean hasContext() {
        return hasRef() && getContext() != null;
    }

    /**
     * Set full span with {@link ViewGroup.LayoutParams#WRAP_CONTENT} height
     */
    public void setFullWidthWithHeight() {
        setFullWidthWithHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
    }

    public void setFullHeightWithWidth() {
        setFullHeightWithWidth(ViewGroup.LayoutParams.WRAP_CONTENT);
    }

    /**
     * @param height height in px
     */
    public void setFullWidthWithHeightInPixel(int height) {
        StaggeredGridLayoutManager.LayoutParams params =
                new StaggeredGridLayoutManager.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, height);
        params.setFullSpan(true);
        itemView.setLayoutParams(params);
    }

    /**
     * @param height height in dp
     */
    public void setFullWidthWithHeight(int height) {
        if (!hasContext()) {
            return;
        }
        final int h = height >= 0 ? AbstractUIUtils.dpToPx(getContext(), height) : height;
        setFullWidthWithHeightInPixel(h);
    }

    /**
     * @param width width in dp
     */
    public void setFullHeightWithWidth(int width) {
        if (!hasContext()) {
            return;
        }
        final int w = width >= 0 ? AbstractUIUtils.dpToPx(getContext(), width) : width;
        StaggeredGridLayoutManager.LayoutParams params =
                new StaggeredGridLayoutManager.LayoutParams(w, ViewGroup.LayoutParams.MATCH_PARENT);
        params.setFullSpan(true);
        itemView.setLayoutParams(params);
    }

    /**
     * @param height height in dp
     */
    public void setHeight(int height) {
        if (!hasContext()) {
            return;
        }
        setHeightInPixel(AbstractUIUtils.dpToPx(getContext(), height));
    }

    /**
     * @param height height in px
     */
    public void setHeightInPixel(int height) {
        StaggeredGridLayoutManager.LayoutParams params =
                new StaggeredGridLayoutManager.LayoutParams(
                        ViewGroup.LayoutParams.MATCH_PARENT,
                        height);

        itemView.setLayoutParams(params);
    }

    /**
     * @param width width in px
     */
    public void setWidthInPixel(int width) {
        StaggeredGridLayoutManager.LayoutParams params =
                new StaggeredGridLayoutManager.LayoutParams(
                        width,
                        ViewGroup.LayoutParams.MATCH_PARENT);

        itemView.setLayoutParams(params);
    }

    public void setFullSpan(boolean fullSpan) {
        StaggeredGridLayoutManager.LayoutParams params = (StaggeredGridLayoutManager.LayoutParams) itemView.getLayoutParams();
        params.setFullSpan(fullSpan);
        itemView.setLayoutParams(params);
    }

    /**
     * @param width  width in px
     * @param height height in px
     */
    public void setParams(int width, int height) {
        StaggeredGridLayoutManager.LayoutParams params =
                new StaggeredGridLayoutManager.LayoutParams(
                        width,
                        height);

        itemView.setLayoutParams(params);
    }
}


<|start_filename|>src/main/java/com/tenor/android/core/view/IBaseView.java<|end_filename|>
package com.tenor.android.core.view;

import android.content.Context;

/**
 * The base view that all activities should implement
 */
public interface IBaseView {
    Context getContext();
}


<|start_filename|>src/main/java/com/tenor/android/core/response/impl/TrendingGifResponse.java<|end_filename|>
package com.tenor.android.core.response.impl;

/**
 * The response of trending GIFs
 */
public class TrendingGifResponse extends GifsResponse {
    private static final long serialVersionUID = -4532798057539811043L;
}


<|start_filename|>src/main/java/com/tenor/android/core/constant/ContentFormat.java<|end_filename|>
package com.tenor.android.core.constant;

import android.support.annotation.StringDef;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
 * The supported content formats
 */
public class ContentFormat {

    @Retention(RetentionPolicy.CLASS)
    @StringDef({IMAGE_GIF, IMAGE_MP4, IMAGE_JPEG, IMAGE_PNG})
    public @interface Value {
    }

    /**
     * "image/gif"
     */
    public static final String IMAGE_GIF = "image/" + MediaFormat.GIF;

    /**
     * "image/jpeg"
     */
    public static final String IMAGE_JPEG = "image/" + MediaFormat.JPEG;

    /**
     * "image/png"
     */
    public static final String IMAGE_PNG = "image/" + MediaFormat.PNG;

    /**
     * "image/mp4"
     */
    public static final String IMAGE_MP4 = "video/" + MediaFormat.MP4;
}


<|start_filename|>src/main/java/com/tenor/android/core/measurable/MeasurableViewHolder.java<|end_filename|>
package com.tenor.android.core.measurable;

import android.support.annotation.CallSuper;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.view.ViewGroup;

import com.tenor.android.core.model.impl.BadgeInfo;
import com.tenor.android.core.model.impl.Result;
import com.tenor.android.core.util.AbstractLayoutManagerUtils;
import com.tenor.android.core.view.IBaseView;
import com.tenor.android.core.widget.viewholder.WeakRefViewHolder;

/**
 * A subclass of {@link WeakRefViewHolder} that can measure user impression of GIFs and use the data
 * to improve future content delivery experience
 * <p>
 * Developers can decide whether to utilize this future by setting the boolean value of
 * {@link #onBindMeasurableViewHolderData(Result, boolean)}
 */
public abstract class MeasurableViewHolder<CTX extends IBaseView> extends WeakRefViewHolder<CTX>
        implements IMeasurableViewHolder {

    @NonNull
    private final MeasurableViewHolderData<MeasurableViewHolder<CTX>> mMeasurableViewHolderData;
    private MeasurableRecyclerView mRecyclerView;
    private boolean mAttached;
    private boolean mDetached;
    private boolean mInitialized;

    /**
     * Order of operation:
     * <p>
     * 1. {@link RecyclerView.Adapter#onCreateViewHolder(ViewGroup, int)}
     * calls {@link MeasurableViewHolder} constructor
     * <p>
     * 2. {@link RecyclerView.OnChildAttachStateChangeListener#onChildViewAttachedToWindow(View)}
     * calls {@link MeasurableViewHolder#attachMeasurer(RecyclerView)}
     * <p>
     * 3. {@link RecyclerView.Adapter#onBindViewHolder(RecyclerView.ViewHolder, int)}
     * calls {@link MeasurableViewHolder#onBindMeasurableViewHolderData(Result, boolean)}
     * <p>
     * 4. {@link RecyclerView.OnChildAttachStateChangeListener#onChildViewDetachedFromWindow(View)}
     * calls {@link MeasurableViewHolder#detachMeasurer()}
     * <p><p>
     * Lifecycle fo MeasurableViewHolder:
     * <p>
     * attachMeasurer() -> measure() -> detachMeasurer()
     */
    public MeasurableViewHolder(@NonNull View itemView, @NonNull CTX context) {
        super(itemView, context);
        mMeasurableViewHolderData = new MeasurableViewHolderData<>(this);
    }

    /**
     * Call on the {@link RecyclerView.Adapter#onBindViewHolder(RecyclerView.ViewHolder, int)}
     * to initialize {@link MeasurableViewHolderData}
     *
     * @param result          the {@link Result}
     * @param enhancedContent true if {@link MeasurableViewHolderData} should be used to improve
     *                        future content delivery experience
     */
    public synchronized void onBindMeasurableViewHolderData(@NonNull Result result,
                                                            boolean enhancedContent) {
        mMeasurableViewHolderData.setId(result.getSourceId());
        mMeasurableViewHolderData.setThreshold(getThreshold(result));
        mMeasurableViewHolderData.setEnhancedContent(enhancedContent);
        mMeasurableViewHolderData.updateTimestamp();
        mMeasurableViewHolderData.getAdapterPosition();
        mInitialized = true;
    }

    @Nullable
    protected RecyclerView getRecyclerView() {
        return mRecyclerView;
    }

    @NonNull
    protected MeasurableViewHolderData getMeasurableData() {
        return mMeasurableViewHolderData;
    }

    @Override
    public boolean isAttached() {
        return mAttached;
    }

    @Override
    public boolean isDetached() {
        return mDetached;
    }

    public synchronized float measure() {
        return measure(getRecyclerView());
    }

    @Override
    public synchronized float measure(@Nullable RecyclerView recyclerView) {
        if (!isAttached() || isDetached() || !mInitialized || getAdapterPosition() == -1) {
            mMeasurableViewHolderData.setVisibleFraction(0f);
            return 0f;
        }

        if (recyclerView == null) {
            mMeasurableViewHolderData.setVisibleFraction(0f);
            throw new IllegalStateException("ViewHolder must be attached to a non-null RecyclerView");
        }

        float visibleFraction = MeasurableViewHolderHelper.calculateVisibleFraction(recyclerView, itemView, mMeasurableViewHolderData.getThreshold());
        mMeasurableViewHolderData.setVisibleFraction(visibleFraction);

        if (mMeasurableViewHolderData.isVisualPositionUnknown()) {
            final String visualPosition = AbstractLayoutManagerUtils.getVisualPosition(getContext(), itemView);
            mMeasurableViewHolderData.setVisualPosition(visualPosition);
        }
        return visibleFraction;
    }

    @CallSuper
    @Override
    public synchronized void attachMeasurer(@NonNull RecyclerView recyclerView) {
        if (!(recyclerView instanceof MeasurableRecyclerView)) {
            throw new IllegalStateException("Measurer can only be attached to a MeasurableRecyclerView");
        }
        mRecyclerView = (MeasurableRecyclerView) recyclerView;
        mAttached = true;
        mDetached = false;
        mMeasurableViewHolderData.clear();
    }

    @CallSuper
    @Override
    public synchronized void resumeMeasurer(@NonNull RecyclerView recyclerView) {
        mMeasurableViewHolderData.resume();
    }

    @CallSuper
    @Override
    public synchronized void pauseMeasurer(@NonNull RecyclerView recyclerView) {
        mMeasurableViewHolderData.pause();
    }

    @CallSuper
    @Override
    public synchronized void detachMeasurer() {
        mAttached = false;
        mDetached = true;
        mMeasurableViewHolderData.flush(getContext());
    }

    @CallSuper
    @Override
    public void flush() {
        mMeasurableViewHolderData.flush(getContext());
    }

    /**
     * @return the percentage of content seen by users in order to be considered as viewed,
     * such as {@link BadgeInfo#getThreshold()}, which {@link BadgeInfo} is available
     * through {@link Result#getBadgeInfo()}
     */
    private static float getThreshold(@Nullable Result result) {
        if (result == null || result.getBadgeInfo() == null) {
            return 1f;
        }
        return result.getBadgeInfo().getThreshold();
    }
}


<|start_filename|>src/main/java/com/tenor/android/core/model/impl/Image.java<|end_filename|>
package com.tenor.android.core.model.impl;

import android.support.annotation.FloatRange;
import android.support.annotation.NonNull;

import com.google.gson.annotations.SerializedName;
import com.tenor.android.core.constant.StringConstant;

import java.io.Serializable;

/**
 * The model of {@link Image}
 */
public class Image implements Serializable {
    private static final long serialVersionUID = -8616498739266612929L;
    private String url;

    @SerializedName("dims")
    private int[] dimensions;

    /**
     * @return url of the raw asset
     */
    @NonNull
    public String getUrl() {
        return StringConstant.getOrEmpty(url);
    }

    public int getWidth() {
        return dimensions != null && dimensions.length == 2 ? dimensions[0] : -1;
    }

    public int getHeight() {
        return dimensions != null && dimensions.length == 2 ? dimensions[1] : -1;
    }

    /**
     * @return aspect ratio of this {@link Image} or the default 1080p aspect ratio, 1.778f
     */
    @FloatRange(from = 0.01f, to = 5.01f)
    public float getAspectRatio() {
        final float aspectRatio = (float) getWidth() / getHeight();
        return aspectRatio >= 0.01f && aspectRatio <= 5.01f ? aspectRatio : 1.778f;
    }
}


<|start_filename|>src/main/java/com/tenor/android/core/widget/adapter/IRVItem.java<|end_filename|>
package com.tenor.android.core.widget.adapter;

import android.support.annotation.NonNull;

/**
 * Interface for all recycler view items
 */
public interface IRVItem {
    int getType();

    @NonNull
    String getId();

    /**
     * Get the position that is relative to the same type, instead of the adapter position
     *
     * @return the relative position to the same type
     */
    int getRelativePosition();
}


<|start_filename|>src/main/java/com/tenor/android/core/measurable/MeasurableRecyclerViewHelper.java<|end_filename|>
package com.tenor.android.core.measurable;

import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import android.view.View;

import java.util.ArrayList;
import java.util.List;

public class MeasurableRecyclerViewHelper {

    public static void onConstruct(@NonNull final RecyclerView recyclerView) {
        recyclerView.addOnScrollListener(new MeasurableOnScrollListener());

        recyclerView.addOnChildAttachStateChangeListener(new RecyclerView.OnChildAttachStateChangeListener() {

            @Override
            public void onChildViewAttachedToWindow(View view) {
                RecyclerView.ViewHolder vh = recyclerView.findContainingViewHolder(view);
                if (vh instanceof IMeasurableViewHolder) {
                    IMeasurableViewHolder holder = (IMeasurableViewHolder) vh;
                    holder.attachMeasurer(recyclerView);
                }
            }

            @Override
            public void onChildViewDetachedFromWindow(View view) {
                RecyclerView.ViewHolder vh = recyclerView.findContainingViewHolder(view);
                if (vh instanceof IMeasurableViewHolder) {
                    IMeasurableViewHolder holder = (IMeasurableViewHolder) vh;
                    holder.detachMeasurer();
                }
            }
        });
    }

    /**
     * Notify {@link RecyclerView.ViewHolder}, which has implemented {@link IMeasurableViewHolder}
     * in the given {@link RecyclerView} that a {@link IMeasurableViewHolder#measure(RecyclerView)}
     * needs to be performed
     * <p>
     * Method got called once a scroll is reached the idle state
     * <p>
     * This updates all the stored {@link MeasurableViewHolderData} data in case any of them is not
     * properly updated due to reason, such as fast scrolling
     *
     * @param recyclerView the given {@link RecyclerView}
     */
    public static void notifyMeasurableViewHolderDataSetChanged(@NonNull RecyclerView recyclerView) {
        final int end = recyclerView.getAdapter().getItemCount() - 1;
        notifyMeasurableViewHolderDataRangeChanged(recyclerView, 0, end);
    }

    /**
     * Notify {@link RecyclerView.ViewHolder}, which has implemented {@link IMeasurableViewHolder}
     * in the given {@link RecyclerView} located between {@code start} and {@code end} that a
     * {@link IMeasurableViewHolder#measure(RecyclerView)} needs to be performed
     * <p>
     * Method got called once a scroll is reached the idle state
     * <p>
     * This updates all the stored {@link MeasurableViewHolderData} data in case any of them is not
     * properly updated due to reason, such as fast scrolling
     *
     * @param recyclerView the given {@link RecyclerView}
     * @param start        the start position
     * @param end          the end position
     */
    public static void notifyMeasurableViewHolderDataRangeChanged(@NonNull RecyclerView recyclerView, int start, int end) {

        if (start == RecyclerView.NO_POSITION || end == RecyclerView.NO_POSITION) {
            return;
        }

        List<IMeasurableViewHolder> holders = getViewHolders(recyclerView, IMeasurableViewHolder.class, start, end);
        /*
         * [ANDROID-1830,1851]
         *
         * check if it is actually completely visible, only check the cases that we know
         * it is not completely visible, a.k.a visible fraction < 1.0f
         */
        for (IMeasurableViewHolder holder : holders) {
            holder.measure(recyclerView);
        }
    }

    public static void notifyViewHoldersOnPause(@NonNull RecyclerView recyclerView) {
        // handle pause
        List<IMeasurableViewHolder> holders =
                MeasurableRecyclerViewHelper.getViewHolders(recyclerView, IMeasurableViewHolder.class);
        for (IMeasurableViewHolder holder : holders) {
            holder.pauseMeasurer(recyclerView);
        }
    }

    public static void notifyViewHoldersOnResume(@NonNull RecyclerView recyclerView) {
        // handle resume
        List<IMeasurableViewHolder> holders =
                MeasurableRecyclerViewHelper.getViewHolders(recyclerView, IMeasurableViewHolder.class);
        for (IMeasurableViewHolder holder : holders) {
            holder.resumeMeasurer(recyclerView);
        }
    }

    public static void notifyMeasurableViewHoldersOnRefresh(@NonNull RecyclerView recyclerView) {
        flushMeasurableViewHolderDataSet(recyclerView);
    }

    public static void flushMeasurableViewHolderDataSet(@NonNull RecyclerView recyclerView) {
        /*
         * for all views those are still visible, mark them all as invisible, update the time,
         * and calculate the time accumulation
         */
        List<IMeasurableViewHolder> holders =
                MeasurableRecyclerViewHelper.getViewHolders(recyclerView, IMeasurableViewHolder.class);
        for (IMeasurableViewHolder holder : holders) {
            holder.flush();
        }
    }

    /**
     * Get all <b>attached</b> {@link RecyclerView.ViewHolder} in the given {@link RecyclerView}
     * that has implemented or extended the the given {@link T} class
     *
     * @param recyclerView the given {@link RecyclerView}
     * @param cls          the class of {@link android.support.v7.widget.RecyclerView.ViewHolder} of interest in the given {@link RecyclerView}
     */
    public static <T extends IViewHolder> List<T> getViewHolders(@Nullable RecyclerView recyclerView, Class<T> cls) {
        final List<T> list = new ArrayList<>();
        if (recyclerView == null) {
            return list;
        }

        final int end = recyclerView.getAdapter().getItemCount() - 1;
        return getViewHolders(recyclerView, cls, 0, end);
    }

    /**
     * Get all <b>attached</b> {@link RecyclerView.ViewHolder} in the given {@link RecyclerView}
     * that has implemented or extended the the given {@link T} class in the given range between
     * {@code start} and {@code end}
     *
     * @param recyclerView the given {@link RecyclerView}
     * @param cls          the class of {@link android.support.v7.widget.RecyclerView.ViewHolder} of interest in the given {@link RecyclerView}
     * @param start        the start position
     * @param end          the end position
     */
    public static <T extends IViewHolder> List<T> getViewHolders(@Nullable RecyclerView recyclerView, Class<T> cls, int start, int end) {
        final int capacity = Math.max(end - start + 1, 0);
        final List<T> list = new ArrayList<>(capacity);
        if (recyclerView == null || recyclerView.getAdapter() == null) {
            return list;
        }

        final int count = recyclerView.getAdapter().getItemCount();
        if (start < 0 || end >= count) {
            return list;
        }

        RecyclerView.ViewHolder holder;
        for (int i = start; i <= end; i++) {
            holder = recyclerView.findViewHolderForAdapterPosition(i);
            if (cls.isInstance(holder)) {
                //noinspection unchecked
                list.add((T) holder);
            }
        }
        return list;
    }
}


<|start_filename|>src/main/java/com/tenor/android/core/weakref/IWeakRefObject.java<|end_filename|>
package com.tenor.android.core.weakref;

import android.support.annotation.NonNull;

import java.lang.ref.WeakReference;

/**
 * Interface of {@link WeakRefObject}
 */
public interface IWeakRefObject<CTX> {

    @NonNull
    WeakReference<CTX> getWeakRef();

    boolean hasRef();
}


<|start_filename|>src/main/java/com/tenor/android/core/response/impl/EmojiResponse.java<|end_filename|>
package com.tenor.android.core.response.impl;

import android.support.annotation.NonNull;

import com.tenor.android.core.model.impl.EmojiTag;
import com.tenor.android.core.response.AbstractResponse;
import com.tenor.android.core.util.AbstractListUtils;

import java.util.Collections;
import java.util.List;

/**
 * The response of Emoji
 */
public class EmojiResponse extends AbstractResponse {
    private static final long serialVersionUID = 5979803531976871364L;
    private List<EmojiTag> tags;

    @NonNull
    public List<EmojiTag> getTags() {
        return !AbstractListUtils.isEmpty(tags) ? tags : Collections.<EmojiTag>emptyList();
    }
}

<|start_filename|>src/main/java/com/tenor/android/core/weakref/WeakRefCurrentHandler.java<|end_filename|>
package com.tenor.android.core.weakref;

import android.os.Looper;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;

import java.lang.ref.WeakReference;

/**
 * {@link WeakRefHandler} with a {@link Looper#myLooper()}, which is associated with the current thread
 */
public class WeakRefCurrentHandler<CTX> extends WeakRefHandler<CTX> implements IWeakRefObject<CTX> {

    public WeakRefCurrentHandler(@NonNull CTX ctx) {
        super(ctx, Looper.myLooper());
    }

    public WeakRefCurrentHandler(@NonNull WeakReference<CTX> weakRef) {
        super(weakRef, Looper.myLooper());
    }

    public WeakRefCurrentHandler(@NonNull CTX ctx, @Nullable Callback callback) {
        super(ctx, Looper.myLooper(), callback);
    }

    public WeakRefCurrentHandler(@NonNull WeakReference<CTX> weakRef, @Nullable Callback callback) {
        super(weakRef, Looper.myLooper(), callback);
    }
}

<|start_filename|>src/main/java/com/tenor/android/core/widget/viewholder/WeakRefViewHolder.java<|end_filename|>
package com.tenor.android.core.widget.viewholder;

import android.content.Context;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.View;

import com.tenor.android.core.util.AbstractWeakReferenceUtils;
import com.tenor.android.core.weakref.IWeakRefObject;

import java.lang.ref.WeakReference;

/**
 * The abstract recycler view view holder class that all recycler view view holder classes should extend from
 *
 * @param <CTX> the referenced context
 */
public abstract class WeakRefViewHolder<CTX> extends RecyclerView.ViewHolder
        implements IWeakRefObject<CTX> {

    private final WeakReference<CTX> mWeakRef;

    public WeakRefViewHolder(@NonNull View itemView, @NonNull CTX context) {
        super(itemView);
        mWeakRef = new WeakReference<>(context);
    }

    @NonNull
    @Override
    public WeakReference<CTX> getWeakRef() {
        return mWeakRef;
    }

    @Override
    public boolean hasRef() {
        return AbstractWeakReferenceUtils.isAlive(mWeakRef);
    }

    public abstract Context getContext();

    public abstract boolean hasContext();
}


<|start_filename|>src/main/java/com/tenor/android/core/concurrent/ConcurrentFifoQueue.java<|end_filename|>
package com.tenor.android.core.concurrent;

import android.support.annotation.NonNull;

import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Queue and send {@link T}
 */
public class ConcurrentFifoQueue<T> implements IConcurrentFifoQueue<T> {

    @NonNull
    private final ConcurrentLinkedQueue<T> mQueue;

    /**
     * Use explicit size counter to compensate the non-constant-time size() operation
     * in {@link ConcurrentLinkedQueue}
     */
    private final AtomicInteger mQueueSize;

    public ConcurrentFifoQueue() {
        mQueue = new ConcurrentLinkedQueue<>();
        mQueueSize = new AtomicInteger();
    }

    /**
     * @return the size of the {@link ConcurrentLinkedQueue} of {@link T}
     */
    public int add(@NonNull T event) {
        mQueue.add(event);
        return mQueueSize.incrementAndGet();
    }

    /**
     * Retrieves and removes the head of {@link ConcurrentLinkedQueue},
     * or returns {@code null} if this queue is empty.
     *
     * @return the head of this queue, or {@code null} if this queue is empty
     */
    public T poll() {
        // decrement the index only after retrieving the content
        final T t = mQueue.poll();
        mQueueSize.decrementAndGet();
        return t;
    }

    public int size() {
        return mQueueSize.get();
    }
}


<|start_filename|>src/main/java/com/tenor/android/core/validator/IValidator.java<|end_filename|>
package com.tenor.android.core.validator;

import android.support.annotation.NonNull;

import java.io.Serializable;
import java.util.regex.Pattern;

public interface IValidator<T> extends Serializable {
    @NonNull
    Pattern get();

    /**
     * @return true if validation result is positive, else false
     */
    boolean validate(T t);
}


<|start_filename|>src/main/java/com/tenor/android/core/constant/ScreenDensity.java<|end_filename|>
package com.tenor.android.core.constant;

import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.StringDef;
import android.util.DisplayMetrics;

import com.tenor.android.core.R;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Locale;

public class ScreenDensity {

    @Retention(RetentionPolicy.CLASS)
    @StringDef({UNKNOWN, SD_075, SD_100, SD_150, SD_200, SD_300, SD_400})
    public @interface Value {
    }

    public static final String UNKNOWN = "0.0";
    public static final String SD_075 = "0.75";
    public static final String SD_100 = "1.0";
    public static final String SD_150 = "1.5";
    public static final String SD_200 = "2.0";
    public static final String SD_300 = "3.0";
    public static final String SD_400 = "4.0";

    /**
     * Parse screen density
     *
     * @param context the {@link Context}
     * @return the {@link ScreenDensity.Value}
     */
    @Value
    public static String get(@NonNull Context context) {

        final DisplayMetrics metrics = context.getResources().getDisplayMetrics();
        final String formatter = (metrics.density < 1f) ? "%.2f" : "%.1f";
        final String density = parse(String.format(Locale.US, formatter, metrics.density), UNKNOWN);
        if (UNKNOWN.equals(density)) {
            return parse(context.getResources().getString(R.string.screen_density), SD_300);
        }
        return density;
    }

    @Value
    private static String parse(@Nullable String density, @Value String defVal) {
        if (density == null) {
            return UNKNOWN;
        }

        switch (density) {
            case SD_075:
                return SD_075;
            case SD_100:
                return SD_100;
            case SD_150:
                return SD_150;
            case SD_200:
                return SD_200;
            case SD_300:
                return SD_300;
            case SD_400:
                return SD_400;
            default:
                return defVal;
        }
    }
}


<|start_filename|>src/main/java/com/tenor/android/core/constant/ItemVisualPosition.java<|end_filename|>
package com.tenor.android.core.constant;

import android.content.Context;
import android.support.annotation.IntRange;
import android.support.annotation.Nullable;
import android.support.annotation.StringDef;

import com.tenor.android.core.util.AbstractUIUtils;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

public class ItemVisualPosition {

    @Retention(RetentionPolicy.CLASS)
    @StringDef({UNKNOWN, LEFT, RIGHT})
    public @interface Value {
    }

    public static final String UNKNOWN = StringConstant.UNKNOWN;
    public static final String LEFT = "LEFT";
    public static final String RIGHT = "RIGHT";

    @Value
    public static String parse(@Nullable Context context,
                               @IntRange(from = -1, to = Integer.MAX_VALUE) final int spanIndex) {
        if (context == null) {
            return UNKNOWN;
        }
        return parse(spanIndex, AbstractUIUtils.isRightToLeft(context));
    }

    @Value
    public static String parse(@IntRange(from = -1, to = Integer.MAX_VALUE) final int spanIndex,
                               final boolean rtl) {
        switch (spanIndex) {
            case -1:
                // GridLayoutManager.LayoutParams.INVALID_SPAN_ID
                // StaggeredGridLayoutManager.LayoutParams.INVALID_SPAN_ID
                return UNKNOWN;
            case 0:
                // item on left
                return !rtl ? LEFT : RIGHT;
            default:
                // item on right
                return !rtl ? RIGHT : LEFT;
        }
    }
}


<|start_filename|>src/main/java/com/tenor/android/core/response/impl/TagsResponse.java<|end_filename|>
package com.tenor.android.core.response.impl;

import android.support.annotation.NonNull;

import com.tenor.android.core.model.impl.Tag;
import com.tenor.android.core.response.AbstractResponse;
import com.tenor.android.core.util.AbstractListUtils;

import java.util.Collections;
import java.util.List;

/**
 * The response of tags
 */
public class TagsResponse extends AbstractResponse {

    private static final long serialVersionUID = -8215681889738059793L;
    private List<Tag> tags;

    @NonNull
    public List<Tag> getTags() {
        return !AbstractListUtils.isEmpty(tags) ? tags : Collections.<Tag>emptyList();
    }
}


<|start_filename|>src/main/java/com/tenor/android/core/weakref/WeakRefUiRunnable.java<|end_filename|>
package com.tenor.android.core.weakref;


import android.os.Handler;
import android.os.Looper;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;

import java.lang.ref.WeakReference;

/**
 * {@link WeakRefRunnable} with a convenient {@link #runOnUiThread} method to run things on UI thread
 */
public abstract class WeakRefUiRunnable<T> extends WeakRefRunnable<T> {

    private static Handler sUiThread;

    public WeakRefUiRunnable(@Nullable T t) {
        super(t);
    }

    public WeakRefUiRunnable(@NonNull WeakReference<T> weakRef) {
        super(weakRef);
    }

    private static Handler getUiThread() {
        if (sUiThread == null) {
            sUiThread = new Handler(Looper.getMainLooper());
        }
        return sUiThread;
    }

    protected static void runOnUiThread(Runnable runnable) {
        getUiThread().post(runnable);
    }
}


<|start_filename|>src/main/java/com/tenor/android/core/service/AaidClient.java<|end_filename|>
package com.tenor.android.core.service;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.os.IBinder;
import android.os.IInterface;
import android.os.Looper;
import android.os.Parcel;
import android.os.RemoteException;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.WorkerThread;

import com.tenor.android.core.constant.StringConstant;
import com.tenor.android.core.util.AbstractLogUtils;
import com.tenor.android.core.util.AbstractSessionUtils;

import java.util.concurrent.LinkedBlockingQueue;

final class AaidClient {

    @WorkerThread
    public static void init(@NonNull Context app, @Nullable IAaidListener listener) {
        if (isOnMainThread()) {
            throw new IllegalStateException("Cannot be called from the main thread");
        }

        final AaidInfo info = getAdvertisingId(app);
        switch (info.getState()) {
            case AaidInfo.AAID_GRANTED:
                AbstractSessionUtils.setAndroidAdvertiseId(app, info.getId());
                break;
            case AaidInfo.AAID_DENIED:
                // remove the stored AAID if user explicitly chose to opt out of ad personalization
                AbstractSessionUtils.setAndroidAdvertiseId(app, StringConstant.EMPTY);
                break;
            default:
                // do nothing
                break;
        }

        AbstractLogUtils.e(app, "AAID: " + info.getId()
                + ", Opt out: " + info.isLimitAdTrackingEnabled()
                + ", state: " + info.getState());

        if (listener != null) {
            if (info.getState() == AaidInfo.AAID_GRANTED) {
                listener.success(info.getId());
            } else {
                listener.failure(info.getState());
            }
        }
    }

    @WorkerThread
    @NonNull
    private static AaidInfo getAdvertisingId(Context context) {

        try {
            PackageManager pm = context.getPackageManager();
            pm.getPackageInfo("com.android.vending", 0);
        } catch (Throwable throwable) {
            // Google Play is not installed
            return new AaidInfo(AaidInfo.AAID_FAILURE_NO_GOOGLE_PLAY);
        }

        AdvertisingConnection connection = new AdvertisingConnection();
        Intent intent = new Intent("com.google.android.gms.ads.identifier.service.START");
        intent.setPackage("com.google.android.gms");

        final boolean googleServiceBinded;
        try {
            googleServiceBinded = context.bindService(intent, connection, Context.BIND_AUTO_CREATE);
        } catch (Throwable ignored) {
            // Cannot connect to Google Play advertising identifier service
            return new AaidInfo(AaidInfo.AAID_FAILURE_NO_AAID_LIBRARY);
        }

        try {
            if (googleServiceBinded) {
                AdvertisingInterface adInterface = new AdvertisingInterface(connection.getBinder());
                return new AaidInfo(adInterface.getId(), adInterface.isLimitAdTrackingEnabled(true));
            }
        } catch (Throwable ignored) {
            return new AaidInfo(AaidInfo.AAID_FAILURE);
        } finally {
            context.unbindService(connection);
        }
        return new AaidInfo(AaidInfo.AAID_FAILURE);
    }

    /**
     * @return {@code true} if called on the main thread, {@code false} otherwise.
     */
    private static boolean isOnMainThread() {
        return Looper.myLooper() == Looper.getMainLooper();
    }

    private static final class AdvertisingConnection implements ServiceConnection {
        private boolean mRetrieved = false;
        private final LinkedBlockingQueue<IBinder> mQueue = new LinkedBlockingQueue<>(1);

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            try {
                mQueue.put(service);
            } catch (InterruptedException ignored) {
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
        }

        public IBinder getBinder() throws InterruptedException {
            if (mRetrieved) {
                throw new IllegalStateException();
            }
            mRetrieved = true;
            return mQueue.take();
        }
    }

    private static final class AdvertisingInterface implements IInterface {
        @NonNull
        private final IBinder mBinder;

        public AdvertisingInterface(@NonNull final IBinder binder) {
            mBinder = binder;
        }

        @NonNull
        public IBinder asBinder() {
            return mBinder;
        }

        public String getId() throws RemoteException {
            Parcel data = Parcel.obtain();
            Parcel reply = Parcel.obtain();
            String id;
            try {
                data.writeInterfaceToken("com.google.android.gms.ads.identifier.internal.IAdvertisingIdService");
                mBinder.transact(1, data, reply, 0);
                reply.readException();
                id = reply.readString();
            } finally {
                reply.recycle();
                data.recycle();
            }
            return id;
        }

        public boolean isLimitAdTrackingEnabled(boolean paramBoolean) throws RemoteException {
            Parcel data = Parcel.obtain();
            Parcel reply = Parcel.obtain();
            boolean limitAdTracking;
            try {
                data.writeInterfaceToken("com.google.android.gms.ads.identifier.internal.IAdvertisingIdService");
                data.writeInt(paramBoolean ? 1 : 0);
                mBinder.transact(2, data, reply, 0);
                reply.readException();
                limitAdTracking = 0 != reply.readInt();
            } finally {
                reply.recycle();
                data.recycle();
            }
            return limitAdTracking;
        }
    }
}


<|start_filename|>src/main/java/com/tenor/android/core/loader/WeakRefContentLoaderTaskListener.java<|end_filename|>
package com.tenor.android.core.loader;

import android.graphics.drawable.Drawable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.widget.ImageView;

import com.tenor.android.core.weakref.WeakRefObject;

import java.lang.ref.WeakReference;

public abstract class WeakRefContentLoaderTaskListener<CTX, T extends ImageView>
        extends WeakRefObject<CTX>
        implements IDrawableLoaderTaskListener<T, Drawable> {

    public WeakRefContentLoaderTaskListener(@NonNull CTX ctx) {
        super(ctx);
    }

    public WeakRefContentLoaderTaskListener(@NonNull WeakReference<CTX> weakRef) {
        super(weakRef);
    }

    @Override
    public void success(@NonNull T target, @NonNull Drawable drawable) {
        if (hasRef()) {
            //noinspection ConstantConditions
            success(getWeakRef().get(), target, drawable);
        }
    }

    @Override
    public void failure(@NonNull T target, @NonNull Drawable errorDrawable) {
        if (hasRef()) {
            //noinspection ConstantConditions
            failure(getWeakRef().get(), target, errorDrawable);
        }
    }

    public abstract void success(@NonNull CTX ctx, @NonNull T target, @Nullable Drawable taskResult);

    public abstract void failure(@NonNull CTX ctx, @NonNull T target, @Nullable Drawable errorResult);
}


<|start_filename|>src/main/java/com/tenor/android/core/model/IGif.java<|end_filename|>
package com.tenor.android.core.model;

import android.support.annotation.NonNull;

import java.io.Serializable;

/**
 * The Interface of GIF
 */
public interface IGif extends Serializable {
    /**
     * Get the id of this {@link IGif}
     */
    @NonNull
    String getId();

    /**
     * Get the name of this {@link IGif}
     */
    @NonNull
    String getName();
}


<|start_filename|>src/main/java/com/tenor/android/core/loader/IDrawableLoaderTaskParams.java<|end_filename|>
package com.tenor.android.core.loader;


import android.graphics.drawable.Drawable;
import android.support.annotation.NonNull;
import android.widget.ImageView;

/**
 * @param <T> the type of the target to load resource into
 */
public interface IDrawableLoaderTaskParams<T extends ImageView, R extends Drawable> extends ITaskParams {

    /**
     * @return the target to load the resource into
     */
    @NonNull
    T getTarget();

    /**
     * @return the drawable to display as a placeholder
     */
    @NonNull
    R getPlaceholder();

    /**
     * @return a file path, or a uri or url
     */
    @NonNull
    String getPath();

    @NonNull
    IDrawableLoaderTaskListener<T, R> getListener();
}


<|start_filename|>src/main/java/com/tenor/android/core/network/constant/Protocol.java<|end_filename|>
package com.tenor.android.core.network.constant;

import android.support.annotation.StringDef;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PACKAGE;
import static java.lang.annotation.ElementType.PARAMETER;

@Retention(RetentionPolicy.CLASS)
@StringDef({Protocols.HTTP, Protocols.HTTPS})
@Target({METHOD, PARAMETER, FIELD, ANNOTATION_TYPE, PACKAGE})
public @interface Protocol {
}


<|start_filename|>src/main/java/com/tenor/android/core/loader/gif/GifLoader.java<|end_filename|>
package com.tenor.android.core.loader.gif;

import android.content.Context;
import android.support.annotation.NonNull;
import android.widget.ImageView;

import com.bumptech.glide.GifRequestBuilder;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.tenor.android.core.loader.GlideLoader;
import com.tenor.android.core.loader.GlideTaskParams;
import com.tenor.android.core.util.AbstractWeakReferenceUtils;

import java.lang.ref.WeakReference;

public abstract class GifLoader extends GlideLoader {

    /**
     * Uses Glide to load a gif or image into an ImageView
     *
     * @param ctx    the subclass of {@link Context}
     * @param params the {@link GlideTaskParams}
     */
    public static <CTX extends Context, T extends ImageView> void loadGif(@NonNull CTX ctx,
                                                                          @NonNull GlideTaskParams<T> params) {
        loadGif(new WeakReference<>(ctx), params);
    }

    /**
     * Uses Glide to load a gif or image into an ImageView
     *
     * @param weakRef the {@link WeakReference} of a given subclass of {@link Context}
     * @param params  the {@link GlideTaskParams}
     */
    public static <CTX extends Context, T extends ImageView> void loadGif(@NonNull WeakReference<CTX> weakRef,
                                                                          @NonNull GlideTaskParams<T> params) {

        if (!AbstractWeakReferenceUtils.isAlive(weakRef)) {
            return;
        }

        GifRequestBuilder<String> requestBuilder = Glide.with(weakRef.get()).load(params.getPath()).asGif()
                .diskCacheStrategy(DiskCacheStrategy.ALL);

        load(applyDimens(requestBuilder, params), params);
    }
}


<|start_filename|>src/main/java/com/tenor/android/core/weakref/WeakRefObject.java<|end_filename|>
package com.tenor.android.core.weakref;


import android.support.annotation.NonNull;

import com.tenor.android.core.util.AbstractWeakReferenceUtils;

import java.lang.ref.WeakReference;

public class WeakRefObject<CTX> implements IWeakRefObject<CTX> {

    private final WeakReference<CTX> mWeakRef;

    public WeakRefObject(@NonNull final CTX ctx) {
        this(new WeakReference<>(ctx));
    }

    public WeakRefObject(@NonNull final WeakReference<CTX> weakRef) {
        mWeakRef = weakRef;
    }

    @NonNull
    @Override
    public WeakReference<CTX> getWeakRef() {
        return mWeakRef;
    }

    @Override
    public boolean hasRef() {
        return AbstractWeakReferenceUtils.isAlive(mWeakRef);
    }
}


<|start_filename|>src/main/java/com/tenor/android/core/measurable/IRecyclerView.java<|end_filename|>
package com.tenor.android.core.measurable;


import android.graphics.Canvas;
import android.graphics.Rect;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerViewAccessibilityDelegate;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.view.animation.Interpolator;

import java.util.ArrayList;


/**
 * Interface of {@link RecyclerView} on support library
 *
 * @version v26.0.1
 * @since v22.0.0
 */
public interface IRecyclerView {

    void addFocusables(ArrayList<View> views, int direction, int focusableMode);

    void addItemDecoration(RecyclerView.ItemDecoration decor);

    void addItemDecoration(RecyclerView.ItemDecoration decor, int index);

    void addOnChildAttachStateChangeListener(RecyclerView.OnChildAttachStateChangeListener listener);

    void addOnItemTouchListener(RecyclerView.OnItemTouchListener listener);

    /**
     * @since v22.1.0
     */
    void addOnScrollListener(RecyclerView.OnScrollListener listener);

    /**
     * @since v22.2.0
     */
    void clearOnChildAttachStateChangeListeners();

    /**
     * @since v22.1.0
     */
    void clearOnScrollListeners();

    /**
     * @since v22.1.0
     */
    int computeHorizontalScrollExtent();

    /**
     * @since v22.1.0
     */
    int computeHorizontalScrollOffset();

    /**
     * @since v22.1.0
     */
    int computeHorizontalScrollRange();

    /**
     * @since v22.1.0
     */
    int computeVerticalScrollExtent();

    /**
     * @since v22.1.0
     */
    int computeVerticalScrollOffset();

    /**
     * @since v22.1.0
     */
    int computeVerticalScrollRange();

    boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed);

    boolean dispatchNestedPreFling(float velocityX, float velocityY);

    boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow);

    /**
     * @since v26.0.0
     */
    boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow, int type);

    /**
     * @since v26.0.0
     */
    boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow, int type);

    boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow);

    void draw(Canvas c);

    /**
     * @since v22.2.0
     */
    boolean drawChild(Canvas canvas, View child, long drawingTime);

    View findChildViewUnder(float x, float y);

    /**
     * @since v23.2.0
     */
    View findContainingItemView(View view);

    @Nullable
    RecyclerView.ViewHolder findContainingViewHolder(View view);

    @Nullable
    RecyclerView.ViewHolder findViewHolderForAdapterPosition(int position);

    @Nullable
    RecyclerView.ViewHolder findViewHolderForItemId(long id);

    @Nullable
    RecyclerView.ViewHolder findViewHolderForLayoutPosition(int position);

    @Nullable
    RecyclerView.ViewHolder findViewHolderForPosition(int position);

    boolean fling(int velocityX, int velocityY);

    @Nullable
    View focusSearch(View focused, int direction);

    @NonNull
    ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs);

    RecyclerView.Adapter getAdapter();

    int getBaseline();

    int getChildAdapterPosition(View child);

    long getChildItemId(View child);

    int getChildLayoutPosition(View child);

    int getChildPosition(View child);

    RecyclerView.ViewHolder getChildViewHolder(View child);

    boolean getClipToPadding();

    RecyclerViewAccessibilityDelegate getCompatAccessibilityDelegate();

    /**
     * @since v25.0.0
     */
    void getDecoratedBoundsWithMargins(View view, Rect outBounds);

    RecyclerView.ItemAnimator getItemAnimator();

    /**
     * @since v26.0.0
     */
    RecyclerView.ItemDecoration getItemDecorationAt(int index);

    RecyclerView.LayoutManager getLayoutManager();

    /**
     * @since v22.2.0
     */
    int getMaxFlingVelocity();

    /**
     * @since v22.2.0
     */
    int getMinFlingVelocity();

    /**
     * @since v24.2.0
     */
    RecyclerView.OnFlingListener getOnFlingListener();

    /**
     * @since v24.0.0
     */
    boolean getPreserveFocusAfterLayout();

    RecyclerView.RecycledViewPool getRecycledViewPool();

    int getScrollState();

    boolean hasFixedSize();

    boolean hasNestedScrollingParent();

    /**
     * @since v26.0.0
     */
    boolean hasNestedScrollingParent(int type);

    /**
     * @since v22.1.0
     */
    boolean hasPendingAdapterUpdates();

    void invalidateItemDecorations();

    /**
     * @since v22.2.0
     */
    boolean isAnimating();

    boolean isAttachedToWindow();

    /**
     * @since v22.2.1
     */
    boolean isComputingLayout();

    /**
     * @since v23.0.0
     */
    boolean isLayoutFrozen();

    boolean isNestedScrollingEnabled();

    void offsetChildrenHorizontal(int dx);

    void offsetChildrenVertical(int dy);

    void onChildAttachedToWindow(View child);

    void onChildDetachedFromWindow(View child);

    void onDraw(Canvas c);

    boolean onGenericMotionEvent(MotionEvent event);

    boolean onInterceptTouchEvent(MotionEvent e);

    /**
     * @since v22.1.0
     */
    void onScrollStateChanged(int state);

    /**
     * @since v22.1.0
     */
    void onScrolled(int dx, int dy);

    boolean onTouchEvent(MotionEvent e);

    void removeItemDecoration(RecyclerView.ItemDecoration decor);

    /**
     * @since v22.2.0
     */
    void removeOnChildAttachStateChangeListener(RecyclerView.OnChildAttachStateChangeListener listener);

    void removeOnItemTouchListener(RecyclerView.OnItemTouchListener listener);

    /**
     * @since v22.1.0
     */
    void removeOnScrollListener(RecyclerView.OnScrollListener listener);

    void requestChildFocus(View child, View focused);

    boolean requestChildRectangleOnScreen(View child, Rect rect, boolean immediate);

    void requestDisallowInterceptTouchEvent(boolean disallowIntercept);

    void requestLayout();

    void scrollBy(int x, int y);

    void scrollTo(int x, int y);

    void scrollToPosition(int position);

    void sendAccessibilityEventUnchecked(AccessibilityEvent event);

    void setAccessibilityDelegateCompat(RecyclerViewAccessibilityDelegate accessibilityDelegate);

    void setAdapter(RecyclerView.Adapter adapter);

    /**
     * @since v22.2.0
     */
    void setChildDrawingOrderCallback(RecyclerView.ChildDrawingOrderCallback childDrawingOrderCallback);

    void setClipToPadding(boolean clipToPadding);

    void setHasFixedSize(boolean hasFixedSize);

    void setItemAnimator(RecyclerView.ItemAnimator animator);

    void setItemViewCacheSize(int size);

    /**
     * @since v23.0.0
     */
    void setLayoutFrozen(boolean frozen);

    void setLayoutManager(RecyclerView.LayoutManager layout);

    void setNestedScrollingEnabled(boolean enabled);

    /**
     * @since v24.2.0
     */
    void setOnFlingListener(RecyclerView.OnFlingListener onFlingListener);

    void setOnScrollListener(RecyclerView.OnScrollListener listener);

    /**
     * @since v24.0.0
     */
    void setPreserveFocusAfterLayout(boolean preserveFocusAfterLayout);

    void setRecycledViewPool(RecyclerView.RecycledViewPool pool);

    void setRecyclerListener(RecyclerView.RecyclerListener listener);

    void setScrollingTouchSlop(int slopConstant);

    void setViewCacheExtension(RecyclerView.ViewCacheExtension extension);

    void smoothScrollBy(int dx, int dy);

    /**
     * @since v25.1.0
     */
    void smoothScrollBy(int dx, int dy, Interpolator interpolator);

    void smoothScrollToPosition(int position);

    boolean startNestedScroll(int axes);

    /**
     * @since v26.0.0
     */
    boolean startNestedScroll(int axes, int type);

    void stopNestedScroll();

    /**
     * @since v26.0.0
     */
    void stopNestedScroll(int type);

    void stopScroll();

    void swapAdapter(RecyclerView.Adapter adapter, boolean removeAndRecycleExistingViews);
}



<|start_filename|>src/main/java/com/tenor/android/core/loader/IDrawableLoaderTaskListener.java<|end_filename|>
package com.tenor.android.core.loader;

import android.graphics.drawable.Drawable;
import android.support.annotation.NonNull;
import android.widget.ImageView;

/**
 * Listener of loading {@link Drawable} task is completed
 */
public interface IDrawableLoaderTaskListener<T extends ImageView, R extends Drawable> {
    /**
     * Load task success case
     */
    void success(@NonNull T target, @NonNull R taskResult);

    /**
     * Load task fail case
     */
    void failure(@NonNull T target, @NonNull R errorResult);
}


<|start_filename|>src/main/java/com/tenor/android/core/model/impl/EmojiTag.java<|end_filename|>
package com.tenor.android.core.model.impl;

import android.support.annotation.NonNull;

import com.google.gson.annotations.SerializedName;
import com.tenor.android.core.constant.StringConstant;

import java.io.Serializable;

/**
 * The model of {@link EmojiTag}
 */
public class EmojiTag implements Serializable {
    private static final long serialVersionUID = -2207206185861282031L;
    @SerializedName("character")
    private String unicodeChars;
    @SerializedName("name")
    private String searchName;
    @SerializedName("path")
    private String imgUrl;
    @SerializedName("searchterm")
    private String searchTerm;

    /**
     * @return String of characters that make up the emoji in unicode form
     */
    @NonNull
    public String getUnicodeChars() {
        return StringConstant.getOrEmpty(unicodeChars);
    }

    /**
     * @return full text of corresponding search word, for display purposes
     */
    @NonNull
    public String getSearchName() {
        return StringConstant.getOrEmpty(searchName);
    }

    /**
     * @return url of the api search endpoint corresponding to the emoji
     */
    @NonNull
    public String getImgUrl() {
        return StringConstant.getOrEmpty(imgUrl);
    }

    /**
     * @return full text of corresponding search word
     */
    @NonNull
    public String getSearchTerm() {
        return StringConstant.getOrEmpty(searchTerm);
    }
}

<|start_filename|>src/main/java/com/tenor/android/core/util/AbstractLogUtils.java<|end_filename|>
package com.tenor.android.core.util;

import android.support.annotation.NonNull;
import android.util.Log;

public abstract class AbstractLogUtils {

    public static boolean sDebuggable = false;

    public static boolean isDebuggable() {
        return sDebuggable;
    }

    public static void setDebuggable(boolean debuggable) {
        sDebuggable = debuggable;
    }

    /**
     * Send an {@link Log#ERROR} log message.
     *
     * @param caller the {@link Object} class
     * @param msg    The message you would like logged.
     */
    public static int e(@NonNull Object caller, @NonNull String msg) {
        return isDebuggable() ? Log.e(caller.getClass().getCanonicalName(), msg) : 0;
    }

    /**
     * Send an {@link Log#ERROR} log message.
     *
     * @param tag Used to identify the source of a log message.  It usually identifies
     *            the class or activity where the log call occurs.
     * @param msg The message you would like logged.
     */
    public static int e(@NonNull String tag, @NonNull String msg) {
        return isDebuggable() ? Log.e(tag, msg) : 0;
    }

    /**
     * Send a {@link Log#ERROR} log message and log the exception.
     *
     * @param caller the {@link Object} class
     * @param msg    The message you would like logged.
     * @param tr     An exception to log
     */
    public static int e(@NonNull Object caller, @NonNull String msg, @NonNull Throwable tr) {
        return isDebuggable() ? e(caller.getClass().getCanonicalName(), msg, tr) : 0;
    }

    /**
     * Send a {@link Log#ERROR} log message and log the exception.
     *
     * @param tag Used to identify the source of a log message.  It usually identifies
     *            the class or activity where the log call occurs.
     * @param msg The message you would like logged.
     * @param tr  An exception to log
     */
    public static int e(@NonNull String tag, @NonNull String msg, @NonNull Throwable tr) {
        return isDebuggable() ? Log.e(tag, msg, tr) : 0;
    }
}


<|start_filename|>src/main/java/com/tenor/android/core/loader/GlideTaskParams.java<|end_filename|>
package com.tenor.android.core.loader;

import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.widget.ImageView;

import com.bumptech.glide.request.target.Target;
import com.tenor.android.core.model.impl.Media;

/**
 * Configuration of loading a gif
 */
public class GlideTaskParams<T extends ImageView> extends DrawableLoaderTaskParams<T> {

    private static final long serialVersionUID = -5195385185012871394L;

    @Nullable
    private Media mMedia;

    private float mThumbnailMultiplier = 1f;
    private int mCurrentRetry;
    private int mMaxRetry = 3;

    private int mWidth = Target.SIZE_ORIGINAL;
    private int mHeight = Target.SIZE_ORIGINAL;

    public GlideTaskParams(@NonNull T imageView,
                           @NonNull String path) {
        super(imageView, path);
    }

    @Nullable
    public Media getMedia() {
        return mMedia;
    }

    public GlideTaskParams<T> setMedia(@Nullable final Media media) {
        if (media != null) {
            mMedia = media;
            setWidth(media.getWidth());
            setHeight(media.getHeight());
        }
        return this;
    }

    public int getWidth() {
        return mWidth;
    }

    public GlideTaskParams<T> setWidth(int width) {
        if (width > 0) {
            this.mWidth = width;
        }
        return this;
    }

    public int getHeight() {
        return mHeight;
    }

    public GlideTaskParams<T> setHeight(int height) {
        if (height > 0) {
            this.mHeight = height;
        }
        return this;
    }

    public boolean isThumbnail() {
        return mThumbnailMultiplier >= 0f && mThumbnailMultiplier < 1f;
    }

    public float getThumbnailMultiplier() {
        return mThumbnailMultiplier;
    }

    /**
     * default is 1f
     */
    public GlideTaskParams<T> setThumbnailMultiplier(float multiplier) {
        if (multiplier >= 0f && multiplier <= 1f) {
            mThumbnailMultiplier = multiplier;
        }
        return this;
    }

    public int getCurrentRetry() {
        return mCurrentRetry;
    }

    /**
     * default is 0
     */
    public GlideTaskParams<T> setCurrentRetry(int retry) {
        if (retry >= 0) {
            mCurrentRetry = retry;
        }
        return this;
    }

    public GlideTaskParams<T> incrementCurrentRetry() {
        mCurrentRetry++;
        return this;
    }

    public int getMaxRetry() {
        return mMaxRetry;
    }

    /**
     * default is 3
     */
    public GlideTaskParams<T> setMaxRetry(int maxRetry) {
        mMaxRetry = maxRetry;
        return this;
    }
}


<|start_filename|>src/main/java/com/tenor/android/core/measurable/IMeasurableRecyclerView.java<|end_filename|>
package com.tenor.android.core.measurable;

import android.support.v7.widget.RecyclerView;

/**
 *
 */
public interface IMeasurableRecyclerView extends IRecyclerView {

    /**
     * Method to be called on the referenced view, activity, or fragment is paused
     */
    void notifyMeasurableViewHoldersOnPause();

    /**
     * Method to be called on the referenced view, activity, or fragment is resumed
     */
    void notifyMeasurableViewHoldersOnResume();

    /**
     * Method to be called on the referenced view, activity, or fragment is refreshed
     */
    void notifyMeasurableViewHoldersOnRefresh();

    /**
     * Method to be called on the referenced view, activity, or fragment is destroyed
     */
    void flushMeasurableViewHolderDataSet();

    /**
     * Notify {@link RecyclerView.ViewHolder}, which has implemented {@link IMeasurableViewHolder}
     * in the given {@link RecyclerView} that a {@link IMeasurableViewHolder#measure(RecyclerView)}
     * needs to be performed
     * <p>
     * Method got called once a scroll is reached the idle state
     * <p>
     * This updates all the stored {@link MeasurableViewHolderData} data in case any of them is not
     * properly updated due to reason, such as fast scrolling
     */
    void notifyViewHolderDataSetChanged();

    /**
     * Notify {@link RecyclerView.ViewHolder}, which has implemented {@link IMeasurableViewHolder}
     * in the given {@link RecyclerView} located between {@code start} and {@code end} that a
     * {@link IMeasurableViewHolder#measure(RecyclerView)} needs to be performed
     * <p>
     * Method got called once a scroll is reached the idle state
     * <p>
     * This updates all the stored {@link MeasurableViewHolderData} data in case any of them is not
     * properly updated due to reason, such as fast scrolling
     *
     * @param start the start position
     * @param end   the end position
     */
    void notifyViewHolderDataRangeChanged(int start, int end);
}



<|start_filename|>src/main/java/com/tenor/android/core/validator/ColorHex.java<|end_filename|>
package com.tenor.android.core.validator;

import android.graphics.Color;
import android.support.annotation.ColorInt;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;

public class ColorHex extends AbstractValidator<CharSequence> {

    private static final long serialVersionUID = -5985057771273176145L;

    public ColorHex(@NonNull String regex) {
        super(regex);
    }

    @Override
    public boolean validate(CharSequence text) {
        return get().matcher(text).matches();
    }

    /*
     * ==============
     * Static Methods
     * ==============
     */
    private static final String COLOR_HEX_PATTERN = "^#([A-Fa-f0-9]{8}|[A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$";
    private static final ColorHex COLOR_HEX = new ColorHex(COLOR_HEX_PATTERN);

    /**
     * Check if a color hex code is valid
     *
     * @param colorHex a color hex code in {@link String}
     * @return true if {@code colorHex} is a valid color hex code that in compliance with {@link #COLOR_HEX_PATTERN}
     */
    public static boolean isValid(@Nullable CharSequence colorHex) {
        return !TextUtils.isEmpty(colorHex) && COLOR_HEX.validate(colorHex);
    }

    /**
     * Parse the given {@link String} into color hex
     *
     * @param str    the string
     * @param defVal the default value
     */
    public static String parse(@Nullable String str, String defVal) {
        if (!isValid(str)) {
            if (!isValid(defVal)) {
                throw new IllegalArgumentException("default value must be a valid hex color code");
            }
            return defVal;
        }
        return str;
    }

    /**
     * Parse the given {@link String} into {@link ColorInt}
     *
     * @param str the string
     */
    @ColorInt
    public static int parse(@Nullable String str) {
        return parse(str, Color.TRANSPARENT);
    }

    /**
     * Parse the given {@link String} into {@link ColorInt}
     *
     * @param str    the string
     * @param defVal the default value
     */
    @ColorInt
    public static int parse(@Nullable String str, @ColorInt int defVal) {
        return isValid(str) ? Color.parseColor(str) : defVal;
    }
}


<|start_filename|>src/main/java/com/tenor/android/core/model/impl/Media.java<|end_filename|>
package com.tenor.android.core.model.impl;

import android.support.annotation.NonNull;

import com.tenor.android.core.constant.StringConstant;

/**
 * The model of {@link Media}
 */
public class Media extends Image {
    private static final long serialVersionUID = -8616498739266612929L;
    private String preview;
    private double duration;

    /**
     * @return url of a static image preview
     */
    @NonNull
    public String getPreviewUrl() {
        return StringConstant.getOrEmpty(preview);
    }

    /**
     * @return duration of each loop, in seconds
     */
    public double getDuration() {
        return duration;
    }
}


<|start_filename|>src/main/java/com/tenor/android/core/weakref/WeakRefUiHandler.java<|end_filename|>
package com.tenor.android.core.weakref;

import android.os.Looper;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;

import java.lang.ref.WeakReference;

/**
 * {@link WeakRefHandler} with a {@link Looper#getMainLooper()}, which is associated with the UI thread
 */
public class WeakRefUiHandler<CTX> extends WeakRefHandler<CTX> implements IWeakRefObject<CTX> {

    /**
     * @param ctx {@link CTX} must be in {@link @UiThread}
     */
    public WeakRefUiHandler(@NonNull CTX ctx) {
        super(ctx, Looper.getMainLooper());
    }

    public WeakRefUiHandler(@NonNull WeakReference<CTX> weakRef) {
        super(weakRef, Looper.getMainLooper());
    }

    public WeakRefUiHandler(@NonNull CTX ctx, @Nullable Callback callback) {
        super(ctx, Looper.getMainLooper(), callback);
    }

    public WeakRefUiHandler(@NonNull WeakReference<CTX> weakRef, @Nullable Callback callback) {
        super(weakRef, Looper.getMainLooper(), callback);
    }
}

<|start_filename|>src/main/java/com/tenor/android/core/validator/FloatString.java<|end_filename|>
package com.tenor.android.core.validator;

import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;

public class FloatString extends AbstractValidator<CharSequence> {

    private static final long serialVersionUID = -5985057771273176145L;

    public FloatString(@NonNull String regex) {
        super(regex);
    }

    @Override
    public boolean validate(CharSequence text) {
        return get().matcher(text).matches();
    }

    /*
     * ==============
     * Static Methods
     * ==============
     */
    private static final String FLOAT_STRING_PATTERN = "^[0-9]+\\.[0-9]+$";
    private static final FloatString FLOAT_STRING = new FloatString(FLOAT_STRING_PATTERN);

    /**
     * Check if a color hex code is valid
     *
     * @param floatString a color hex code in {@link String}
     * @return true if {@code colorHex} is a valid color hex code that in compliance with {@link #FLOAT_STRING_PATTERN}
     */
    public static boolean isValid(@Nullable CharSequence floatString) {
        return !TextUtils.isEmpty(floatString) && FLOAT_STRING.validate(floatString);
    }

    /**
     * Parse the given {@link String} into float
     *
     * @param str    the string
     * @param defVal the default value
     */
    public static float parse(@Nullable String str, float defVal) {
        if (!isValid(str)) {
            return defVal;
        }

        try {
            return Float.parseFloat(str);
        } catch (NumberFormatException ignored) {
            return defVal;
        }
    }
}


<|start_filename|>src/main/java/com/tenor/android/core/network/IApiClient.java<|end_filename|>
package com.tenor.android.core.network;


import android.support.annotation.IntRange;
import android.support.annotation.NonNull;

import com.tenor.android.core.constant.AspectRatioRange;
import com.tenor.android.core.constant.MediaFilter;
import com.tenor.android.core.measurable.MeasurableViewHolderEvent;
import com.tenor.android.core.model.impl.Result;
import com.tenor.android.core.model.impl.Suggestions;
import com.tenor.android.core.response.impl.AnonIdResponse;
import com.tenor.android.core.response.impl.EmojiResponse;
import com.tenor.android.core.response.impl.GifsResponse;
import com.tenor.android.core.response.impl.PackResponse;
import com.tenor.android.core.response.impl.SearchSuggestionResponse;
import com.tenor.android.core.response.impl.TagsResponse;
import com.tenor.android.core.response.impl.TrendingGifResponse;
import com.tenor.android.core.response.impl.TrendingTermResponse;

import java.util.Map;

import retrofit2.Call;
import retrofit2.http.Field;
import retrofit2.http.FieldMap;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.GET;
import retrofit2.http.HEAD;
import retrofit2.http.POST;
import retrofit2.http.Query;
import retrofit2.http.QueryMap;
import retrofit2.http.Url;

/**
 * The network calls
 */
public interface IApiClient {
    /**
     * Search for gifs based on a query tag
     *
     * @param serviceIds       a {@link Map} of a collection of ids for better content delivery experience
     * @param query            <b>term</b> being searched for
     * @param limit            <b>bucket</b> size of each response
     * @param pos              <b>index</b> for where the first result should come from.  If <b>empty</b>, start at the first result
     * @param mediaFilter      one of the options from {@link MediaFilter}
     * @param aspectRatioRange one of the options from {@link AspectRatioRange}
     * @return {@link Call}<{@link GifsResponse}>
     */
    @GET("search")
    @NonNull
    Call<GifsResponse> search(@QueryMap Map<String, String> serviceIds,
                              @Query("q") @NonNull String query,
                              @Query("limit") int limit,
                              @Query("pos") @NonNull String pos,
                              @Query("media_filter") @MediaFilter.Value String mediaFilter,
                              @Query("ar_range") String aspectRatioRange);

    /**
     * Retrieves a set of tags to be displayed as gif categories,
     * which when clicked will perform a gif search of that tag
     *
     * @param serviceIds a {@link Map} of a collection of ids for better content delivery experience
     * @param type       what <b>subset of tags</b> you wish to retrieve
     * @param utcOffset  the timezone
     * @return {@link Call}<{@link TagsResponse}>
     */
    @GET("tags")
    Call<TagsResponse> getTags(@QueryMap Map<String, String> serviceIds, @Query("type") String type,
                               @Query("timezone") String utcOffset);

    /**
     * Retrieves a set of emojis to use in gif searching.
     * Clicking on these emojis can perform searches with the emoji itselft as the query
     *
     * @param serviceIds a {@link Map} of a collection of ids for better content delivery experience
     * @return {@link Call}<{@link EmojiResponse}>
     */
    @GET("tags?platform=android&type=emoji")
    Call<EmojiResponse> getEmojiTags(@QueryMap Map<String, String> serviceIds);

    /**
     * Retrieves a set of <b>GIFs</b> a.k.a {@link Result} representing those that are currently trending
     *
     * @param serviceIds       a {@link Map} of a collection of ids for better content delivery experience
     * @param limit            <b>bucket</b> size of each response
     * @param pos              <b>index</b> for where the first result should come from.  If <b>empty</b>, start at the first result
     * @param mediaFilter      one of the options from {@link MediaFilter}
     * @param aspectRatioRange one of the options from {@link AspectRatioRange}
     * @return {@link Call}<{@link TrendingGifResponse}>
     */
    @GET("trending")
    Call<TrendingGifResponse> getTrending(@QueryMap Map<String, String> serviceIds,
                                          @Query("limit") Integer limit,
                                          @Query("pos") String pos,
                                          @Query("media_filter") @MediaFilter.Value String mediaFilter,
                                          @Query("ar_range") String aspectRatioRange);

    /**
     * Retrieves a set of <b>terms</b> a.k.a {@link String} representing those that are currently trending
     *
     * @param serviceIds a {@link Map} of a collection of ids for better content delivery experience
     * @param limit      <b>bucket</b> size of each response
     * @return {@link Call}<{@link TrendingTermResponse}>
     */
    @GET("autocomplete?type=trending")
    Call<TrendingTermResponse> getTrending(@QueryMap Map<String, String> serviceIds,
                                           @Query("limit") Integer limit);

    /**
     * Retrieve gifs with specific ids
     *
     * @param serviceIds       a {@link Map} of a collection of ids for better content delivery experience
     * @param ids              a comma separated string containing a list of unique ids of the desired gifs
     * @param mediaFilter      one of the options from {@link MediaFilter}
     * @param aspectRatioRange one of the options from {@link AspectRatioRange}
     * @return {@link Call}<{@link GifsResponse}>
     */
    @GET("gifs")
    Call<GifsResponse> getGifs(@QueryMap Map<String, String> serviceIds,
                               @Query("ids") String ids,
                               @Query("media_filter") @MediaFilter.Value String mediaFilter,
                               @Query("ar_range") String aspectRatioRange);

    /**
     * Search
     * Takes in a tag, and a set of related and similar tags, represented as a Pivot object
     *
     * @param serviceIds a {@link Map} of a collection of ids for better content delivery experience
     * @param tag        initial term
     * @param limit      <b>bucket</b> size of each response
     * @return {@link Call}<{@link SearchSuggestionResponse}>
     */
    @GET("search_suggestions?platform=android")
    Call<SearchSuggestionResponse> getSearchSuggestions(@QueryMap Map<String, String> serviceIds,
                                                        @Query("tag") String tag,
                                                        @Query("limit") @IntRange(from = 1, to = 50) Integer limit);

    /**
     * Returns the set of gifs uploaded by the current user
     *
     * @param serviceIds  a {@link Map} of a collection of ids for better content delivery experience
     * @param userId      <b>id</b> of the user
     * @param accessToken login <b>token</b>
     * @return {@link Call}<{@link GifsResponse}>
     */
    @GET("user")
    Call<GifsResponse> getUserGifs(@QueryMap Map<String, String> serviceIds,
                                   @Query("id") String userId,
                                   @Query("access_token") String accessToken);

    @HEAD
    Call<Void> getImageSize(@Url String imageUrl);

    @GET("suggest")
    Call<Suggestions> getSuggestions(@QueryMap Map<String, String> serviceIds,
                                     @Query("tag") String tag,
                                     @Query("limit") Integer limit,
                                     @Query("type") String type,
                                     @Query("timezone") String utcOffset,
                                     @Query("allterms") boolean isAllTerms);

    /**
     * Report shared GIF id for better search experience in the future
     *
     * @param serviceIds a {@link Map} of a collection of ids for better content delivery experience
     * @param id         the gif id
     * @param query      the search query that led to this GIF, or {@code null} for trending GIFs
     * @return {@link Call}<{@link Void}>
     */
    @GET("registershare")
    Call<Void> registerShare(@QueryMap Map<String, String> serviceIds,
                             @Query("id") Integer id,
                             @Query("q") String query);

    /**
     * Report search for better search experience in the future
     *
     * @param serviceIds a {@link Map} of a collection of ids for better content delivery experience
     * @param query      the search query that led to this GIF, or {@code null} for trending GIFs
     * @param pos        either {@link GifsResponse#getNext()} or {@code null}
     * @return {@link Call}<{@link Void}>
     */
    @GET("registersearch")
    Call<Void> registerSearch(@QueryMap Map<String, String> serviceIds,
                              @Query("q") String query,
                              @Query("pos") String pos);

    /**
     * Get keyboard id
     *
     * @param apiKey api key to access the endpoint
     * @param locale <b>current language</b> set by the user
     * @return {@link Call}<{@link AnonIdResponse}>
     */
    @GET("anonid?platform=android")
    Call<AnonIdResponse> getAnonId(@Query("key") String apiKey,
                                   @Query("locale") String locale);


    @GET("pack")
    Call<PackResponse> getPack(@Query("key") String apiKey,
                               @Query("publicid") String publicId);

    /**
     * Register batch action
     *
     * @param serviceIds a {@link Map} of a collection of ids for better content delivery experience
     * @param data       a serialized list of {@link MeasurableViewHolderEvent}
     * @return {@link Call}<{@link Void}>
     */
    @POST("registeraction")
    @FormUrlEncoded
    @NonNull
    Call<Void> registerActions(@FieldMap Map<String, String> serviceIds,
                               @Field("data") @NonNull String data);
}


<|start_filename|>src/main/java/com/tenor/android/core/service/AaidInfo.java<|end_filename|>
package com.tenor.android.core.service;

import android.content.pm.PackageManager;
import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;

import com.tenor.android.core.constant.StringConstant;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
 * Subclass of AdvertisingIdClient#Info
 */
public final class AaidInfo {

    @IntDef({AAID_GRANTED, AAID_DENIED, AAID_FAILURE_NO_GOOGLE_PLAY, AAID_FAILURE_NO_AAID_LIBRARY})
    @Retention(RetentionPolicy.CLASS)
    public @interface State {
    }

    /**
     * Android Advertising ID is granted
     */
    public static final int AAID_GRANTED = PackageManager.PERMISSION_GRANTED;

    /**
     * Android Advertising ID is denied because {@link #isLimitAdTrackingEnabled()} is {@code true} or it is {@link StringConstant#EMPTY}
     */
    public static final int AAID_DENIED = PackageManager.PERMISSION_DENIED;

    public static final int AAID_FAILURE_NO_GOOGLE_PLAY = AAID_DENIED - 1;
    public static final int AAID_FAILURE_NO_AAID_LIBRARY = AAID_DENIED - 2;
    public static final int AAID_FAILURE = AAID_DENIED - 3;

    @NonNull
    private final String mAdvertisingId;
    private final boolean mLimitAdTrackingEnabled;

    private final int mState;

    AaidInfo(@State int state) {
        this(StringConstant.EMPTY, false, state);
    }

    AaidInfo(@Nullable String advertisingId, boolean limitAdTrackingEnabled) {
        this(advertisingId, limitAdTrackingEnabled,
                TextUtils.isEmpty(advertisingId) || limitAdTrackingEnabled ? AAID_DENIED : AAID_GRANTED);
    }

    private AaidInfo(@Nullable String advertisingId, boolean limitAdTrackingEnabled, int state) {
        mAdvertisingId = StringConstant.getOrEmpty(advertisingId);
        mLimitAdTrackingEnabled = limitAdTrackingEnabled;
        mState = state;
    }

    @NonNull
    public String getId() {
        return mAdvertisingId;
    }

    public boolean isLimitAdTrackingEnabled() {
        return mLimitAdTrackingEnabled;
    }

    @State
    public int getState() {
        return mState;
    }
}


<|start_filename|>src/main/java/com/tenor/android/core/constant/SupportMessenger.java<|end_filename|>
package com.tenor.android.core.constant;

import android.support.annotation.StringDef;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
 * Contains package name of all supported messengers are sdk can work with.
 * Additional apps may be added if needed or desired.
 */
public class SupportMessenger {

    @Retention(RetentionPolicy.CLASS)
    @StringDef({NONE,
            FB_MESSENGER,
            WHATSAPP,
            MESSAGES,
            HANGOUTS,
            CHOMP,
            SKYPE,
            EIGHT_SMS,
            TWITTER,
            GO_SMS,
            TANGO,
            KIK,
            WE_CHAT,
            GOOGLE_MESSENGER,
            HIKE,
            HTC_MESSAGES,
            TELEGRAM,
            HOVERCHAT,
            VIBER,
            KAKAO_TALK,
            SLACK,
            VODAFONE,
            FACEBOOK,
            LINE,
            REDDIT,
            GMAIL,
            AOSP_MESSAGES})
    public @interface Value {
    }

    /**
     * Empty string, ""
     */
    public static final String NONE = StringConstant.EMPTY;

    /**
     * Facebook Messenger
     */
    public static final String FB_MESSENGER = "com.facebook.orca";


    /**
     * WhatsApp Messenger
     */
    public static final String WHATSAPP = "com.whatsapp";

    /**
     * Android Default Messages
     * Samsung Messages
     */
    public static final String MESSAGES = "com.android.mms";

    /**
     * Hangouts
     */
    public static final String HANGOUTS = "com.google.android.talk";

    /**
     * chomp SMS
     */
    public static final String CHOMP = "com.p1.chompsms";

    /**
     * Skype
     */
    public static final String SKYPE = "com.skype.raider";

    /**
     * Eight sms
     */
    public static final String EIGHT_SMS = "com.thinkleft.eightyeightsms.mms";

    /**
     * Twitter
     */
    public static final String TWITTER = "com.twitter.android";

    /**
     * GO SMS Pro
     */
    public static final String GO_SMS = "com.jb.gosms";

    /**
     * Tango
     */
    public static final String TANGO = "com.sgiggle.production";

    /**
     * Kik
     */
    public static final String KIK = "kik.android";

    /**
     * WeChat
     */
    public static final String WE_CHAT = "com.tencent.mm";

    /**
     * Messenger
     */
    public static final String GOOGLE_MESSENGER = "com.google.android.apps.messaging";

    /**
     * hike messenger
     */
    public static final String HIKE = "com.bsb.hike";

    /**
     * Messages
     */
    public static final String HTC_MESSAGES = "com.htc.sense.mms";

    /**
     * Telegram
     */
    public static final String TELEGRAM = "org.telegram.messenger";

    /**
     * HoverChat
     */
    public static final String HOVERCHAT = "com.ninja.sms.promo";

    /**
     * Viber
     */
    public static final String VIBER = "com.viber.voip";

    /**
     * KakaoTalk
     */
    public static final String KAKAO_TALK = "com.kakao.talk";

    /**
     * Slack
     */
    public static final String SLACK = "com.Slack";

    /**
     * Vodafone Message+
     */
    public static final String VODAFONE = "com.vodafone.messaging";

    /**
     * Facebook
     */
    public static final String FACEBOOK = "com.facebook.katana";

    /**
     * LINE
     */
    public static final String LINE = "jp.naver.line.android";

    /**
     * Reddit
     */
    public static final String REDDIT = "com.reddit.frontpage";

    /**
     * Gmail
     */
    public static final String GMAIL = "com.google.android.gm";

    /**
     * AOSP Messages
     */
    public static final String AOSP_MESSAGES = "com.android.messaging";

    /**
     * Samsung Messaging
     */
    public static final String SAMSUNG_MESSAGING = "com.samsung.android.messaging";
}


<|start_filename|>src/main/java/com/tenor/android/core/loader/DrawableLoaderTaskParams.java<|end_filename|>
package com.tenor.android.core.loader;

import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.support.annotation.ColorInt;
import android.support.annotation.ColorRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.content.ContextCompat;
import android.widget.ImageView;

import com.tenor.android.core.validator.ColorHex;

public class DrawableLoaderTaskParams<T extends ImageView> implements IDrawableLoaderTaskParams<T, Drawable> {

    private static final long serialVersionUID = -3764658332353857684L;

    /**
     * Required
     */
    @NonNull
    private final T mImageView;

    /**
     * Required
     */
    @NonNull
    private final String mPath;

    /**
     * Placeholder {@link ColorDrawable}, default is {@link Color#TRANSPARENT}
     */
    private Drawable mPlaceholder;

    private IDrawableLoaderTaskListener<T, Drawable> mListener;

    public DrawableLoaderTaskParams(@NonNull T imageView,
                                    @NonNull String path) {
        mImageView = imageView;
        mPath = path;
    }

    @NonNull
    @Override
    public String getId() {
        return getPath();
    }

    @NonNull
    public T getTarget() {
        return mImageView;
    }

    @NonNull
    public String getPath() {
        return mPath;
    }

    @NonNull
    public Drawable getPlaceholder() {
        return mPlaceholder != null ? mPlaceholder : new ColorDrawable(Color.TRANSPARENT);
    }

    /**
     * Set placeholder color, the default is android.R.color.transparent
     *
     * @param context    the context
     * @param colorResId the color resource
     */
    public DrawableLoaderTaskParams<T> setPlaceholder(@NonNull Context context, @ColorRes int colorResId) {
        setPlaceholder(ContextCompat.getDrawable(context, colorResId));
        return this;
    }

    /**
     * Set placeholder color, the default is android.R.color.transparent
     *
     * @param drawable the drawable
     */
    public DrawableLoaderTaskParams<T> setPlaceholder(@NonNull Drawable drawable) {
        mPlaceholder = drawable;
        return this;
    }

    /**
     * Set placeholder color, the default is android.R.color.transparent
     *
     * @param colorHex hex color code in {@link String}, such as {@code #000000}
     */
    public DrawableLoaderTaskParams<T> setPlaceholder(@Nullable String colorHex) {
        if (!ColorHex.isValid(colorHex)) {
            throw new IllegalArgumentException("color must be in a valid hex color code");
        }
        setPlaceholder(Color.parseColor(colorHex));
        return this;
    }

    /**
     * Set placeholder color, the default is android.R.color.transparent
     *
     * @param colorInt the color integer
     */
    public DrawableLoaderTaskParams<T> setPlaceholder(@ColorInt int colorInt) {
        setPlaceholder(new ColorDrawable(colorInt));
        return this;
    }

    @NonNull
    public IDrawableLoaderTaskListener<T, Drawable> getListener() {
        return mListener != null ? mListener :
                new IDrawableLoaderTaskListener<T, Drawable>() {
                    @Override
                    public void success(@NonNull T target, @NonNull Drawable taskResult) {

                    }

                    @Override
                    public void failure(@NonNull T target, @NonNull Drawable errorResult) {

                    }
                };
    }

    /**
     * default is do nothing
     */
    public DrawableLoaderTaskParams<T> setListener(@Nullable final IDrawableLoaderTaskListener<T, Drawable> listener) {
        mListener = listener;
        return this;
    }
}


<|start_filename|>src/main/java/com/tenor/android/core/measurable/MeasurableViewHolderData.java<|end_filename|>
package com.tenor.android.core.measurable;

import android.content.Context;
import android.support.annotation.FloatRange;
import android.support.annotation.IntRange;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.View;

import com.tenor.android.core.constant.ItemVisualPosition;
import com.tenor.android.core.constant.StringConstant;
import com.tenor.android.core.model.impl.Result;
import com.tenor.android.core.util.AbstractLogUtils;
import com.tenor.android.core.weakref.WeakRefObject;

import java.io.Serializable;

public class MeasurableViewHolderData<VH extends IMeasurableViewHolder> extends WeakRefObject<VH> implements Serializable {
    private static final long serialVersionUID = 675815364606601681L;

    private int mVisibility = View.INVISIBLE;

    @IntRange(from = 0)
    private int mAccumulatedVisibleDuration = 0;

    @IntRange(from = 0)
    private int mAccumulatedVisibleCount = 0;

    @IntRange(from = -1)
    private long mTimestampOnVisible = -1;

    @FloatRange(from = 0f, to = 1f)
    private float mVisibleFraction = 0f;

    @ItemVisualPosition.Value
    private String mVisualPosition = ItemVisualPosition.UNKNOWN;

    private String mId = StringConstant.EMPTY;

    private boolean mEnhancedContent = false;

    @FloatRange(from = 0.01f, to = 1f)
    private float mThreshold = 1f;

    @IntRange(from = -1, to = Integer.MAX_VALUE)
    private int mAdapterPosition = RecyclerView.NO_POSITION;

    public MeasurableViewHolderData(@NonNull VH viewHolder) {
        super(viewHolder);
        resetTimestamp();
        resetCounts();
    }

    @NonNull
    public String getId() {
        return mId;
    }

    /**
     * Set identifier to uniquely identify the referenced {@link IMeasurableViewHolder}
     *
     * @param id the id, such as {@link Result#getSourceId()}
     */
    public void setId(@NonNull String id) {
        mId = StringConstant.getOrEmpty(id);
    }

    /**
     * Set to true if this data should be used to improve future content delivery experience
     *
     * @param enhancedContent the boolean to determine if this data should be used to improve
     *                        future content delivery experience
     */
    public void setEnhancedContent(boolean enhancedContent) {
        mEnhancedContent = enhancedContent;
    }

    public void setThreshold(@FloatRange(from = 0.01f, to = 1f) float threshold) {
        mThreshold = threshold;
    }

    @FloatRange(from = 0.01f, to = 1f)
    public float getThreshold() {
        return mThreshold;
    }

    public boolean isVisualPositionUnknown() {
        return ItemVisualPosition.UNKNOWN.equals(getVisualPosition());
    }

    @ItemVisualPosition.Value
    public String getVisualPosition() {
        return mVisualPosition;
    }

    public void setVisualPosition(@ItemVisualPosition.Value String visualPosition) {
        mVisualPosition = visualPosition;
    }

    public synchronized void clear() {
        mVisibility = View.INVISIBLE;
        resetTimestamp();
        resetCounts();
        mVisibleFraction = 0f;
    }

    private synchronized void resetCounts() {
        mAccumulatedVisibleDuration = 0;
        mAccumulatedVisibleCount = 0;
    }

    private synchronized void resetTimestamp() {
        mTimestampOnVisible = -1;
    }

    public synchronized void updateTimestamp() {
        mTimestampOnVisible = System.currentTimeMillis();
    }

    public int getAdapterPosition() {
        // reference GCed
        if (!hasRef()) {
            return mAdapterPosition;
        }

        // adapter position is not initialized
        if (mAdapterPosition == RecyclerView.NO_POSITION) {
            mAdapterPosition = getWeakRef().get().getAdapterPosition();
        }

        // adapter position changed to a non NO_POSITION position
        if (getWeakRef().get().getAdapterPosition() != RecyclerView.NO_POSITION
                && mAdapterPosition != getWeakRef().get().getAdapterPosition()) {
            mAdapterPosition = getWeakRef().get().getAdapterPosition();
        }
        return mAdapterPosition;
    }

    public synchronized int getAccumulatedVisibleDuration() {
        return mAccumulatedVisibleDuration;
    }

    public synchronized int getAccumulatedVisibleCount() {
        return mAccumulatedVisibleCount;
    }

    @FloatRange(from = 0f, to = 1f)
    public float getVisibleFraction() {
        return mVisibleFraction;
    }

    public synchronized void pause() {
        // becomes invisible
        if (mTimestampOnVisible < 0) {
            return;
        }
        final long duration = System.currentTimeMillis() - mTimestampOnVisible;
        mAccumulatedVisibleDuration += duration;
        resetTimestamp();
    }

    public synchronized void resume() {
        updateTimestamp();
    }

    public synchronized void flush(@NonNull Context context) {
        setVisibleFraction(0f);

        final boolean useful = mEnhancedContent
                && getAccumulatedVisibleDuration() > 0 && getAccumulatedVisibleCount() > 0;
        AbstractLogUtils.e(this, "==> flushed\n" + (useful ? toString() : StringConstant.EMPTY));
        if (useful) {
            ViewHolderDataManager.push(context, this);
        }
        clear();
    }

    public synchronized void setVisibleFraction(@FloatRange(from = 0f, to = 1f) float visibleFraction) {

        final boolean wasVisible = mVisibleFraction >= mThreshold;
        final boolean isVisible = visibleFraction >= mThreshold;
        mVisibleFraction = visibleFraction;

        final boolean visibilityChanged = wasVisible ^ isVisible;
        if (!visibilityChanged) {
            // visibility hasn't changed, do nothing
            return;
        }

        // visibility has changed
        if (isVisible) {
            becomesVisible();
        } else {
            becomesInvisible();
        }
    }

    private void becomesVisible() {
        updateTimestamp();
        mAccumulatedVisibleCount++;
        AbstractLogUtils.e(this, "==> item[" + getAdapterPosition() + "] becomes Visible");
    }

    private void becomesInvisible() {
        if (mTimestampOnVisible < 0) {
            return;
        }
        final long duration = System.currentTimeMillis() - mTimestampOnVisible;
        mAccumulatedVisibleDuration += duration;
        resetTimestamp();
        AbstractLogUtils.e(this, "==> item[" + getAdapterPosition() + "] becomes Invisible");
    }

    public String toString() {
        return "adapter_pos: " + getAdapterPosition() + "\n"
                + "source_id: " + mId + "\n"
                + "visual_pos: " + mVisualPosition + "\n"
                + "viewed: " + getAccumulatedVisibleDuration() + " ms\n"
                + "counted: " + getAccumulatedVisibleCount() + "\n"
                + "enhanced_content: " + mEnhancedContent + "\n";
    }
}


<|start_filename|>src/main/java/com/tenor/android/core/service/AaidServiceCompat.java<|end_filename|>
package com.tenor.android.core.service;

import android.app.Application;
import android.app.IntentService;
import android.content.Context;
import android.content.Intent;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;

import com.tenor.android.core.constant.StringConstant;


/**
 * Base class for {@code AaidService} to be use with v3 APIs.
 */
public final class AaidServiceCompat extends IntentService {

    @Nullable
    private static IAaidListener sListener;

    public AaidServiceCompat() {
        super(AaidServiceCompat.class.getName());
    }

    @Override
    protected void onHandleIntent(@Nullable Intent intent) {

        if (intent == null || intent.getAction() == null) {
            if (sListener != null) {
                sListener.failure(AaidInfo.AAID_FAILURE);
            }
            return;
        }

        String action = StringConstant.getOrEmpty(intent.getAction());
        switch (action) {
            case AaidService.ACTION_GET_AAID:
                AaidClient.init(getApplicationContext(), sListener);
                break;
            default:
                // do nothing
                break;
        }
    }

    public static void requestAaid(@NonNull Context context) {
        requestAaid(context, null);
    }

    public static void requestAaid(@NonNull Context context, @Nullable IAaidListener listener) {
        sListener = listener;
        Context ctx = context;
        if (!(ctx instanceof Application)) {
            ctx = context.getApplicationContext();
        }
        Intent intent = new Intent(ctx, AaidServiceCompat.class).setAction(AaidService.ACTION_GET_AAID);
        context.startService(intent);
    }
}

<|start_filename|>src/main/java/com/tenor/android/core/util/AbstractNetworkUtils.java<|end_filename|>
package com.tenor.android.core.util;

import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.telephony.TelephonyManager;
import android.text.TextUtils;

import com.tenor.android.core.constant.StringConstant;

import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Enumeration;

/**
 * Contains methods to access the network status
 */
public abstract class AbstractNetworkUtils {

    private static int sBatchSize = -1;

    /**
     * Get the batch size
     */
    public static int getBatchSize(@Nullable final Context context) {
        // Use the cached sBatchSize
        return sBatchSize > 0 ? sBatchSize : updateBatchSize(context);
    }

    /**
     * Update batch size based on network type and status
     *
     * @param context the context, usually the application context
     */
    public static int updateBatchSize(@Nullable final Context context) {
        if (context == null) {
            sBatchSize = 6;
            return sBatchSize;
        }

        // No network, use small batch size
        NetworkInfo info = getNetworkInfo(context);
        if (info == null || !info.isConnected()) {
            sBatchSize = 6;
            return sBatchSize;
        }

        // In WiFi, use large batch size
        if (info.getType() == ConnectivityManager.TYPE_WIFI) {
            sBatchSize = 24;
            return sBatchSize;
        }

        if (info.getType() == ConnectivityManager.TYPE_MOBILE) {
            switch (info.getSubtype()) {
                case TelephonyManager.NETWORK_TYPE_HSDPA: // ~ 2-14 Mbps
                case TelephonyManager.NETWORK_TYPE_HSUPA: // ~ 1-23 Mbps
                case TelephonyManager.NETWORK_TYPE_EHRPD: // ~ 1-2 Mbps
                case TelephonyManager.NETWORK_TYPE_EVDO_B: // ~ 5 Mbps
                case TelephonyManager.NETWORK_TYPE_HSPAP: // ~ 10-20 Mbps
                case TelephonyManager.NETWORK_TYPE_LTE: // ~ 10+ Mbps
                    sBatchSize = 24;
                    break;
                case TelephonyManager.NETWORK_TYPE_EVDO_0: // ~ 400-1000 kbps
                case TelephonyManager.NETWORK_TYPE_EVDO_A: // ~ 600-1400 kbps
                case TelephonyManager.NETWORK_TYPE_HSPA: // ~ 700-1700 kbps
                case TelephonyManager.NETWORK_TYPE_UMTS: // ~ 400-7000 kbps
                    sBatchSize = 12;
                    break;
                case TelephonyManager.NETWORK_TYPE_IDEN: // ~25 kbps
                case TelephonyManager.NETWORK_TYPE_CDMA: // ~ 14-64 kbps
                case TelephonyManager.NETWORK_TYPE_1xRTT: // ~ 50-100 kbps
                case TelephonyManager.NETWORK_TYPE_EDGE: // ~ 50-100 kbps
                case TelephonyManager.NETWORK_TYPE_GPRS: // ~ 100 kbps
                case TelephonyManager.NETWORK_TYPE_UNKNOWN:
                default:
                    sBatchSize = 6;
                    break;
            }
        } else {
            // In other undetermined network, use small batch size
            sBatchSize = 6;
        }
        return sBatchSize;
    }

    /**
     * Update batch size based on network speed
     *
     * @param speed network speed measured in KB/s
     */
    public static void updateBatchSize(float speed) {
        final int kbps = (int) (speed * 8);
        final int batchSize;
        if (kbps > 1000) {
            batchSize = 24;
        } else if (kbps > 400) {
            batchSize = 12;
        } else {
            batchSize = 6;
        }
        sBatchSize = batchSize;
    }

    /**
     * Gets the network info of the device
     *
     * @param context the context
     * @return current network info
     */
    @Nullable
    public static NetworkInfo getNetworkInfo(@Nullable final Context context) {
        if (context == null) {
            return null;
        }

        try {
            /*
             * [ANDROID-1380]
             *
             * Some OEM devices have custom permission implementation that ask
             * `ACCESS_NETWORK_STATE` even though it has been granted to the app
             */
            ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
            return cm != null ? cm.getActiveNetworkInfo() : null;
        } catch (SecurityException ignored) {
            return null;
        }
    }

    /**
     * Gets the network info of the device
     *
     * @param context the context
     * @return the current network type name
     */
    @NonNull
    public static String getNetworkTypeName(@Nullable final Context context) {
        if (context == null) {
            return StringConstant.EMPTY;
        }
        NetworkInfo info = getNetworkInfo(context);
        return (info != null && info.isConnected()) ? info.getTypeName() : StringConstant.UNKNOWN;
    }

    /**
     * Gets the network info of the device
     *
     * @param context the context
     * @return the current network type name
     */
    @NonNull
    public static String getNetworkTypeNameCompat(@Nullable final Context context) {
        if (context == null) {
            return StringConstant.EMPTY;
        }
        NetworkInfo info = getNetworkInfo(context);
        return (info != null && info.isConnected()) ? getNetworkTypeNameCompat(info.getType(), info.getSubtype()) : StringConstant.EMPTY;
    }

    /**
     * Gets the network info of the device
     *
     * @param context the context
     * @return the current network subtype name
     */
    @NonNull
    public static String getNetworkSubtypeName(@Nullable final Context context) {
        if (context == null) {
            return StringConstant.EMPTY;
        }
        NetworkInfo info = getNetworkInfo(context);
        return (info != null && info.isConnected()) ? info.getSubtypeName() : StringConstant.UNKNOWN;
    }

    /**
     * Checks if device is connected to any network
     *
     * @param context the context
     * @return true if connected
     */
    public static boolean isNetworkConnected(@NonNull Context context) {
        NetworkInfo info = getNetworkInfo(context);
        return (info != null && info.isConnected());
    }

    /**
     * Checks if device is connected to a wifi network
     *
     * @param context the context
     * @return true if device is connected to Wifi
     */
    public static boolean isWifiConnected(@NonNull Context context) {
        NetworkInfo info = getNetworkInfo(context);
        return (info != null && info.isConnected() && info.getType() == ConnectivityManager.TYPE_WIFI);
    }

    /**
     * Checks if device is connected to a cellular network
     *
     * @param context the context
     * @return true if connected
     */
    public static boolean isCellularConnected(@NonNull Context context) {
        NetworkInfo info = getNetworkInfo(context);
        return (info != null && info.isConnected() && info.getType() == ConnectivityManager.TYPE_MOBILE);
    }

    /**
     * Checks if device is connected to a strong and fast network
     *
     * @param context the context
     * @return true if device is connected to a fast network connection
     */
    public static boolean isFastNetworkConnected(@NonNull Context context) {
        NetworkInfo info = getNetworkInfo(context);
        return (info != null && info.isConnected() && isNetworkConnected(info.getType(), info.getSubtype()));
    }

    /**
     * Checks network type and subtype, to determine if network is fast (in kbps)
     *
     * @param type    ConnectivityManager type
     * @param subType TelephonyManager type for mobile
     * @return true if device is connected to a fast network connection
     */
    private static boolean isNetworkConnected(int type, int subType) {
        if (type == ConnectivityManager.TYPE_WIFI) return true;

        if (type == ConnectivityManager.TYPE_MOBILE) {
            switch (subType) {
                case TelephonyManager.NETWORK_TYPE_1xRTT:
                    return false; // ~ 50-100 kbps

                case TelephonyManager.NETWORK_TYPE_CDMA:
                    return false; // ~ 14-64 kbps

                case TelephonyManager.NETWORK_TYPE_EDGE:
                    return false; // ~ 50-100 kbps

                case TelephonyManager.NETWORK_TYPE_EVDO_0:
                    return true; // ~ 400-1000 kbps

                case TelephonyManager.NETWORK_TYPE_EVDO_A:
                    return true; // ~ 600-1400 kbps

                case TelephonyManager.NETWORK_TYPE_GPRS:
                    return false; // ~ 100 kbps

                case TelephonyManager.NETWORK_TYPE_HSDPA:
                    return true; // ~ 2-14 Mbps

                case TelephonyManager.NETWORK_TYPE_HSPA:
                    return true; // ~ 700-1700 kbps

                case TelephonyManager.NETWORK_TYPE_HSUPA:
                    return true; // ~ 1-23 Mbps

                case TelephonyManager.NETWORK_TYPE_UMTS:
                    return true; // ~ 400-7000 kbps

                case TelephonyManager.NETWORK_TYPE_EHRPD:
                    return true; // ~ 1-2 Mbps

                case TelephonyManager.NETWORK_TYPE_EVDO_B:
                    return true; // ~ 5 Mbps

                case TelephonyManager.NETWORK_TYPE_HSPAP:
                    return true; // ~ 10-20 Mbps

                case TelephonyManager.NETWORK_TYPE_IDEN:
                    return false; // ~25 kbps

                case TelephonyManager.NETWORK_TYPE_LTE:
                    return true; // ~ 10+ Mbps

                case TelephonyManager.NETWORK_TYPE_UNKNOWN:
                default:
                    return false;
            }
        }
        return false;
    }

    /**
     * Checks network type and subtype, to determine if network is fast (in kbps)
     *
     * @param type    ConnectivityManager type
     * @param subType TelephonyManager type for mobile
     * @return true if device is connected to a fast network connection
     */
    private static String getNetworkTypeNameCompat(int type, int subType) {

        final String t;
        switch (type) {
            case ConnectivityManager.TYPE_BLUETOOTH:
                t = "BLUETOOTH";
                break;
            case ConnectivityManager.TYPE_DUMMY:
                t = "DUMMY";
                break;
            case ConnectivityManager.TYPE_ETHERNET:
                t = "ETHERNET";
                break;
            case ConnectivityManager.TYPE_MOBILE:
                t = "MOBILE";
                break;
            case ConnectivityManager.TYPE_MOBILE_DUN:
                t = "MOBILE_DUN";
                break;
            case ConnectivityManager.TYPE_MOBILE_HIPRI:
                t = "MOBILE_HIPRI";
                break;
            case ConnectivityManager.TYPE_MOBILE_MMS:
                t = "MOBILE_MMS";
                break;
            case ConnectivityManager.TYPE_MOBILE_SUPL:
                t = "MOBILE_SUPL";
                break;
            case ConnectivityManager.TYPE_VPN:
                t = "VPN";
                break;
            case ConnectivityManager.TYPE_WIFI:
                t = "WIFI";
                break;
            case ConnectivityManager.TYPE_WIMAX:
                t = "WIMAX";
                break;
            default:
                t = StringConstant.UNKNOWN;
                break;
        }

        final String st;
        switch (subType) {
            case TelephonyManager.NETWORK_TYPE_1xRTT:
                st = "1xRTT"; // ~ 50-100 kbps
                break;
            case TelephonyManager.NETWORK_TYPE_CDMA:
                st = "CDMA"; // ~ 14-64 kbps
                break;
            case TelephonyManager.NETWORK_TYPE_EDGE:
                st = "EDGE"; // ~ 50-100 kbps
                break;
            case TelephonyManager.NETWORK_TYPE_EVDO_0:
                st = "EVDO_0"; // ~ 400-1000 kbps
                break;
            case TelephonyManager.NETWORK_TYPE_EVDO_A:
                st = "EVDO_A"; // ~ 600-1400 kbps
                break;
            case TelephonyManager.NETWORK_TYPE_GPRS:
                st = "GPRS"; // ~ 100 kbps
                break;
            case TelephonyManager.NETWORK_TYPE_HSDPA:
                st = "HSDPA"; // ~ 2-14 Mbps
                break;
            case TelephonyManager.NETWORK_TYPE_HSPA:
                st = "HSPA"; // ~ 700-1700 kbps
                break;
            case TelephonyManager.NETWORK_TYPE_HSUPA:
                st = "HSUPA"; // ~ 1-23 Mbps
                break;
            case TelephonyManager.NETWORK_TYPE_UMTS:
                st = "UMTS"; // ~ 400-7000 kbps
                break;
            case TelephonyManager.NETWORK_TYPE_EHRPD:
                st = "EHRPD"; // ~ 1-2 Mbps
                break;
            case TelephonyManager.NETWORK_TYPE_EVDO_B:
                st = "EVDO_B"; // ~ 5 Mbps
                break;
            case TelephonyManager.NETWORK_TYPE_HSPAP:
                st = "HSPAP"; // ~ 10-20 Mbps
                break;
            case TelephonyManager.NETWORK_TYPE_IDEN:
                st = "IDEN"; // ~25 kbps
                break;
            case TelephonyManager.NETWORK_TYPE_LTE:
                st = "LTE"; // ~ 10+ Mbps
                break;
            case TelephonyManager.NETWORK_TYPE_GSM:
                st = "GSM"; // ~ 50-60 kbps
                break;
            case TelephonyManager.NETWORK_TYPE_IWLAN:
                st = "IWLAN"; // ~ 11-54 Mbps
                break;
            case TelephonyManager.NETWORK_TYPE_TD_SCDMA:
                st = "TD_SCDMA"; // ~ 400-7000 kbps
                break;
            case TelephonyManager.NETWORK_TYPE_UNKNOWN:
            default:
                st = StringConstant.UNKNOWN;
                break;
        }
        return t + StringConstant.DASH + st;
    }

    public static String parseIpAddress(@Nullable final String ip) {
        if (TextUtils.isEmpty(ip)) {
            throw new IllegalArgumentException("input: " + ip + " is neither IPv4, nor IPv6");
        }

        if (ip.indexOf(':') < 0) {
            // it is already a IPv4
            return ip;
        }

        int mark = ip.indexOf('%'); // drop ip6 zone suffix
        return mark < 0 ? ip.toUpperCase() : ip.substring(0, mark).toUpperCase();
    }

    public static String getIpAddress() {
        final Enumeration<NetworkInterface> nis;
        try {
            nis = NetworkInterface.getNetworkInterfaces();
        } catch (SocketException e) {
            return StringConstant.EMPTY;
        }

        NetworkInterface ni;
        Enumeration<InetAddress> eia;
        InetAddress ia;
        String ip;

        while (nis.hasMoreElements()) {
            ni = nis.nextElement();
            eia = ni.getInetAddresses();

            while (eia.hasMoreElements()) {
                ia = eia.nextElement();

                if (!ia.isLoopbackAddress()) {
                    ip = ia.getHostAddress();

                    if (!TextUtils.isEmpty(ip)) {
                        return parseIpAddress(ip);
                    }
                }
            }
        }
        return StringConstant.EMPTY;
    }
}

<|start_filename|>src/main/java/com/tenor/android/core/model/impl/ItemBadge.java<|end_filename|>
package com.tenor.android.core.model.impl;

import android.support.annotation.NonNull;

import com.tenor.android.core.constant.ItemBadgePosition;
import com.tenor.android.core.constant.StringConstant;

public class ItemBadge extends Image {

    private static final long serialVersionUID = 5769727680233855104L;

    private int position;
    private String provider;

    @ItemBadgePosition.Value
    public int getPosition() {
        return ItemBadgePosition.parse(position);
    }

    @NonNull
    public String getProvider() {
        return StringConstant.getOrEmpty(provider);
    }
}


<|start_filename|>src/main/java/com/tenor/android/core/loader/asset/AssetLoader.java<|end_filename|>
package com.tenor.android.core.loader.asset;

import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.widget.ImageView;

import com.bumptech.glide.Glide;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.tenor.android.core.util.AbstractWeakReferenceUtils;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.WeakReference;

/**
 * Uses the Glide library to load gifs into a specified ImageView
 */
public class AssetLoader {

    /**
     * Uses Glide to load a local gif asset into an ImageView
     *
     * @param ctx       the subclass of {@link Context}
     * @param imageView where the gif will load into
     * @param assetPath local path of the gif asset
     */
    public static <CTX extends Context> void loadAsset(@NonNull CTX ctx,
                                                       @NonNull ImageView imageView,
                                                       @NonNull String assetPath) {
        loadAsset(new WeakReference<>(ctx), imageView, assetPath);
    }

    /**
     * Uses Glide to load a local gif asset into an ImageView
     *
     * @param weakRef   the {@link WeakReference} of a given subclass of {@link Context}
     * @param imageView where the gif will load into
     * @param assetPath local path of the gif asset
     */
    public static <CTX extends Context> void loadAsset(@NonNull WeakReference<CTX> weakRef,
                                                       @NonNull ImageView imageView,
                                                       @NonNull String assetPath) {

        if (!AbstractWeakReferenceUtils.isAlive(weakRef)) {
            return;
        }

        byte[] bytes = toByteArray(weakRef.get(), assetPath);
        if (bytes == null) {
            return;
        }

        Glide.with(weakRef.get()).load(bytes).asGif().diskCacheStrategy(DiskCacheStrategy.ALL)
                .into(imageView);
    }

    @Nullable
    public static byte[] toByteArray(@NonNull Context context, @NonNull String assetPath) {
        byte[] data = null;

        try {
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            InputStream is = context.getAssets().open(assetPath);

            // Read the entire asset into a local byte buffer.
            byte[] buffer = new byte[65536];
            while (is.read(buffer) != -1) {
                outputStream.write(buffer);
            }

            data = outputStream.toByteArray();
            outputStream.close();
            is.close();
        } catch (IOException ignored) {
        }
        return data;
    }
}


<|start_filename|>src/main/java/com/tenor/android/core/measurable/MeasurableRecyclerView.java<|end_filename|>
package com.tenor.android.core.measurable;

import android.content.Context;
import android.support.annotation.Nullable;
import android.util.AttributeSet;

import com.tenor.android.core.widget.WeakRefRecyclerView;

public class MeasurableRecyclerView extends WeakRefRecyclerView implements IMeasurableRecyclerView {

    public MeasurableRecyclerView(Context context) {
        this(context, null);
    }

    public MeasurableRecyclerView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MeasurableRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        MeasurableRecyclerViewHelper.onConstruct(this);
    }

    @Override
    public void notifyMeasurableViewHoldersOnPause() {
        MeasurableRecyclerViewHelper.notifyViewHoldersOnPause(this);
    }

    @Override
    public void notifyMeasurableViewHoldersOnResume() {
        MeasurableRecyclerViewHelper.notifyViewHoldersOnResume(this);
    }

    @Override
    public void notifyMeasurableViewHoldersOnRefresh() {
        MeasurableRecyclerViewHelper.notifyMeasurableViewHoldersOnRefresh(this);
    }

    @Override
    public void flushMeasurableViewHolderDataSet() {
        MeasurableRecyclerViewHelper.flushMeasurableViewHolderDataSet(this);
    }

    @Override
    public void notifyViewHolderDataSetChanged() {
        MeasurableRecyclerViewHelper.notifyMeasurableViewHolderDataSetChanged(this);
    }

    @Override
    public void notifyViewHolderDataRangeChanged(int start, int end) {
        MeasurableRecyclerViewHelper.notifyMeasurableViewHolderDataRangeChanged(this, start, end);
    }
}


<|start_filename|>src/main/java/com/tenor/android/core/model/impl/MediaCollection.java<|end_filename|>
package com.tenor.android.core.model.impl;

import android.support.annotation.NonNull;
import android.support.annotation.Nullable;

import com.tenor.android.core.constant.MediaCollectionFormat;

import java.io.Serializable;

/**
 * The model of {@link MediaCollection}, which contains a collection of {@link Media} formats of {@link Result}
 */
public class MediaCollection implements Serializable {
    private static final long serialVersionUID = -8824214919408809561L;

    private Media gif;
    private Media tinygif;
    private Media mediumgif;
    private Media nanogif;

    private Media mp4;
    private Media loopedmp4;
    private Media tinymp4;
    private Media nanomp4;

    private Media webm;
    private Media tinywebm;
    private Media nanowebm;

    /**
     * @param type one of {@link MediaCollectionFormat}
     */
    @NonNull
    public Media get(@MediaCollectionFormat.Value String type) {
        switch (type) {
            case MediaCollectionFormat.GIF:
                return getOrEmptyMedia(gif);
            case MediaCollectionFormat.GIF_TINY:
                return getOrEmptyMedia(tinygif);
            case MediaCollectionFormat.GIF_MEDIUM:
                return getOrEmptyMedia(mediumgif);
            case MediaCollectionFormat.GIF_NANO:
                return getOrEmptyMedia(nanogif);
            case MediaCollectionFormat.MP4:
                return getOrEmptyMedia(mp4);
            case MediaCollectionFormat.MP4_TINY:
                return getOrEmptyMedia(tinymp4);
            case MediaCollectionFormat.MP4_NANO:
                return getOrEmptyMedia(nanomp4);
            case MediaCollectionFormat.MP4_LOOPED:
                return getOrEmptyMedia(loopedmp4);
            case MediaCollectionFormat.WEBM:
                return getOrEmptyMedia(webm);
            case MediaCollectionFormat.WEBM_TINY:
                return getOrEmptyMedia(tinywebm);
            case MediaCollectionFormat.WEBM_NANO:
                return getOrEmptyMedia(nanowebm);
            default:
                return new Media();
        }
    }

    @NonNull
    private static Media getOrEmptyMedia(@Nullable Media media) {
        return media != null ? media : new Media();
    }
}


<|start_filename|>src/main/java/com/tenor/android/core/network/VoidCallBack.java<|end_filename|>
package com.tenor.android.core.network;

import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;

public class VoidCallBack implements Callback<Void> {
    @Override
    public final void onResponse(Call<Void> call, Response<Void> response) {
        // do nothing
    }

    @Override
    public final void onFailure(Call<Void> call, Throwable t) {
        // do nothing
    }
}


<|start_filename|>src/main/java/com/tenor/android/core/network/constant/Protocols.java<|end_filename|>
package com.tenor.android.core.network.constant;

import android.support.annotation.Nullable;

/**
 * The supported protocols formats
 */
public class Protocols {
    public static final String HTTP = "http";
    public static final String HTTPS = "https";

    @Protocol
    public static String getOrHttps(@Nullable String protocol) {
        switch (protocol) {
            case HTTP:
                return HTTP;
            case HTTPS:
            default:
                return HTTPS;

        }
    }
}


<|start_filename|>src/main/java/com/tenor/android/core/network/ApiService.java<|end_filename|>
package com.tenor.android.core.network;

import android.app.Application;
import android.content.Context;
import android.support.annotation.IntRange;
import android.support.annotation.NonNull;
import android.text.TextUtils;

import com.google.gson.Gson;
import com.tenor.android.core.constant.StringConstant;
import com.tenor.android.core.network.constant.Protocol;
import com.tenor.android.core.network.constant.Protocols;
import com.tenor.android.core.util.AbstractGsonUtils;
import com.tenor.android.core.util.AbstractListUtils;

import java.io.File;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

import okhttp3.Cache;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;

/**
 * Do NOT call {@link ApiService} directly, use its wrapper, {@link ApiClient}
 */
public class ApiService<T> implements IApiService<T> {

    private final T mClient;
    private final String mApiKey;
    private final String mEndpoint;

    protected ApiService(Builder<T> builder) {
        mClient = create(builder);
        mApiKey = builder.apiKey;
        mEndpoint = builder.endpoint;
    }

    @Override
    public synchronized T get() {
        return mClient;
    }

    @Override
    public synchronized T create(@NonNull Builder<T> builder) {
        Context ctx = builder.context;
        if (!(ctx instanceof Application)) {
            ctx = ctx.getApplicationContext();
        }

        final File cacheDir = new File(ctx.getCacheDir().getAbsolutePath(), ctx.getPackageName());
        final Cache cache = new Cache(cacheDir, 10 * 1024 * 1024);

        OkHttpClient.Builder http = new OkHttpClient.Builder()
                .cache(cache)
                .writeTimeout(builder.timeout, TimeUnit.SECONDS);

        for (Interceptor interceptor : builder.interceptors) {
            http.addInterceptor(interceptor);
        }

        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(builder.endpoint)
                .client(http.build())
                .addConverterFactory(GsonConverterFactory.create(builder.gson))
                .build();

        return retrofit.create(builder.cls);
    }

    @NonNull
    @Override
    public String getEndpoint() {
        return mEndpoint;
    }


    @NonNull
    @Override
    public String getApiKey() {
        if (TextUtils.isEmpty(mApiKey)) {
            throw new IllegalStateException("API key cannot be null or empty.");
        }
        return mApiKey;
    }

    public interface IBuilder<T> extends Serializable {
        IBuilder<T> apiKey(@NonNull String apiKey);

        IBuilder<T> gson(@NonNull Gson gson);

        /**
         * Set API protocol type
         * <p>
         * Call this on the onCreate() of your Application
         *
         * @param protocol {@link Protocol}
         */
        IBuilder<T> protocol(@Protocol String protocol);

        /**
         * Set API server
         * <p>
         * Call this on the onCreate() of your Application
         *
         * @param serverName server name, default value is "api"
         */
        IBuilder<T> server(@NonNull String serverName);

        /**
         * Set {@link Interceptor}
         *
         * @param interceptor the interceptor
         */
        IBuilder<T> interceptor(@NonNull Interceptor interceptor);

        /**
         * Set {@link List}<{@link Interceptor}>
         *
         * @param interceptors the list of interceptors
         */
        IBuilder<T> interceptors(@NonNull List<Interceptor> interceptors);

        /**
         * Network timeout
         *
         * @param timeout between 0 to 30 seconds
         */
        IBuilder<T> timeout(@IntRange(from = 0, to = 30) int timeout);

        /**
         * Set API endpoint directly; however, using {@link #protocol(String)} and
         * {@link #server(String)} are recommended over using this.
         */
        IBuilder<T> endpoint(@NonNull String endpoint);

        IApiService<T> build();
    }

    public static class Builder<T> implements IBuilder<T> {

        private static final long serialVersionUID = -3581428418516126896L;

        protected static final String API_ENDPOINT_FORMATTER = "%1$s://%2$s.tenor.com/v1/";
        protected static final String SERVER_NAME = "api";

        @Protocol
        private String protocol = Protocols.HTTPS;
        @NonNull
        private String serverName = SERVER_NAME;
        @NonNull
        private String endpoint = String.format(API_ENDPOINT_FORMATTER, protocol, serverName);
        @IntRange(from = 0, to = 30)
        private int timeout = 15;
        @NonNull
        private List<Interceptor> interceptors = new ArrayList<>();
        @NonNull
        private String apiKey = StringConstant.EMPTY;
        @NonNull
        private Gson gson = AbstractGsonUtils.getInstance();

        private final Context context;
        private final Class<T> cls;

        /**
         * Call this on the onCreate() of your subclass of {@link Application}
         */
        public Builder(@NonNull Context context, @NonNull Class<T> cls) {
            this.context = context;
            this.cls = cls;
        }

        @Override
        public IBuilder<T> apiKey(@NonNull String apiKey) {
            if (TextUtils.isEmpty(apiKey)) {
                throw new IllegalStateException("API key cannot be null or empty.");
            }
            this.apiKey = apiKey;
            return this;
        }

        @Override
        public IBuilder<T> gson(@NonNull Gson gson) {
            this.gson = gson;
            return this;
        }

        /**
         * Set API protocol type
         * <p>
         * Call this on the onCreate() of your Application
         *
         * @param protocol {@link Protocols}
         */
        @Override
        public IBuilder<T> protocol(@Protocol String protocol) {
            this.protocol = Protocols.getOrHttps(protocol);
            this.endpoint = String.format(API_ENDPOINT_FORMATTER, this.protocol, this.serverName);
            return this;
        }

        /**
         * Set API server
         * <p>
         * Call this on the onCreate() of your Application
         *
         * @param server server name, default value is "api"
         */
        @Override
        public IBuilder<T> server(@NonNull String server) {
            this.serverName = !TextUtils.isEmpty(server) ? server : SERVER_NAME;
            this.endpoint = String.format(API_ENDPOINT_FORMATTER, this.protocol, this.serverName);
            return this;
        }

        /**
         * Set {@link Interceptor}
         *
         * @param interceptor the interceptor
         */
        @Override
        public IBuilder<T> interceptor(@NonNull Interceptor interceptor) {
            this.interceptors.add(interceptor);
            return this;
        }

        /**
         * Set {@link List}<{@link Interceptor}>
         *
         * @param interceptors the list of interceptors
         */
        @Override
        public IBuilder<T> interceptors(@NonNull List<Interceptor> interceptors) {
            if (!AbstractListUtils.isEmpty(interceptors)) {
                this.interceptors.addAll(interceptors);
            }
            return this;
        }

        @Override
        public IBuilder<T> timeout(@IntRange(from = 0, to = 30) int timeout) {
            if (timeout >= 0 && timeout <= 30 && this.timeout != timeout) {
                this.timeout = timeout;
            }
            return this;
        }

        /**
         * Set API endpoint directly; however, using {@link #protocol(String)} and
         * {@link #server(String)} are recommended over using this.
         */
        @Override
        public IBuilder<T> endpoint(@NonNull String endpoint) {
            if (!TextUtils.isEmpty(endpoint)) {
                this.endpoint = endpoint;
            }
            return this;
        }

        @Override
        public IApiService<T> build() {
            return new ApiService<>(this);
        }
    }
}


<|start_filename|>src/main/java/com/tenor/android/core/network/ApiClient.java<|end_filename|>
package com.tenor.android.core.network;

import android.app.Application;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.util.ArrayMap;
import android.text.TextUtils;

import com.tenor.android.core.constant.ScreenDensity;
import com.tenor.android.core.constant.StringConstant;
import com.tenor.android.core.constant.ViewAction;
import com.tenor.android.core.measurable.MeasurableViewHolderEvent;
import com.tenor.android.core.model.impl.Result;
import com.tenor.android.core.response.WeakRefCallback;
import com.tenor.android.core.response.impl.AnonIdResponse;
import com.tenor.android.core.service.AaidService;
import com.tenor.android.core.util.AbstractGsonUtils;
import com.tenor.android.core.util.AbstractLocaleUtils;
import com.tenor.android.core.util.AbstractSessionUtils;

import java.util.Collections;
import java.util.List;
import java.util.Map;

import retrofit2.Call;

/**
 * API Client to make network calls to retrieve contents
 */
public class ApiClient {

    private static volatile IApiService<IApiClient> sApiService;

    private static synchronized void init(@NonNull final Context context) {
        init(context, new ApiService.Builder<>(context, IApiClient.class));
    }

    /**
     * Initialize the ApiClient instance with your custom interceptor
     *
     * @param context the context
     * @param builder {@link ApiService.IBuilder}
     */
    public static synchronized void init(@NonNull final Context context,
                                         @NonNull ApiService.IBuilder<IApiClient> builder) {
        init(context, builder, null);
    }

    public static synchronized void init(@NonNull final Context context,
                                         @NonNull ApiService.IBuilder<IApiClient> builder,
                                         @Nullable IAnonIdListener listener) {
        if (sApiService == null) {
            sApiService = builder.build();
        }

        if (!AbstractSessionUtils.hasAnonId(context)) {
            getAnonId(context, listener);
        }
    }

    public static String getApiKey() {
        if (sApiService == null) {
            throw new IllegalStateException("Api service cannot be null");
        }
        return sApiService.getApiKey();
    }

    /**
     * Retrieve instance of the {@link ApiClient}, and create instance if not already created
     *
     * @return the {@link ApiClient} instance
     */
    @NonNull
    public static synchronized IApiClient getInstance() {
        if (sApiService == null) {
            throw new IllegalStateException("Api service cannot be null");
        }
        return sApiService.get();
    }

    /**
     * Lazy initialize a {@link IApiClient} with default {@link IApiService} configuration
     */
    @NonNull
    public static synchronized IApiClient getInstance(@NonNull Context context) {
        if (sApiService == null) {
            init(context);
        }
        return sApiService.get();
    }

    /**
     * Get service ids that can delivery a more accurate and better experience
     *
     * @return a {@link Map} with {@code key} (API Key), {@code anon_id},
     * {@code aaid} (Android Advertise Id) and {@code locale } for authentication and better
     * content delivery experience
     */
    public static Map<String, String> getServiceIds(@NonNull final Context context) {
        final ArrayMap<String, String> map = new ArrayMap<>(4);

        // API Key
        map.put("key", sApiService.getApiKey());

        /*
         * The following fields work together to delivery a more accurate and better experience
         *
         * 1. `anon_id`, a non-id or its older version, keyboard is used to roughly identify a user;
         * 2. `aaid`, Android Advertise Id, is also used in case "keyboardid" or "anon_id" mutates
         * 3. `locale` is used to deliver curated language/regional specific contents to users
         * 4. `screen_density` is used to optimize the content size to the device
         */
        final String id = AbstractSessionUtils.getAnonId(context);
        map.put(id.length() <= 20 ? "keyboardid" : "anon_id", id);
        if (TextUtils.isEmpty(AbstractSessionUtils.getAndroidAdvertiseId(context))) {
            AaidService.requestAaid(context);
        }
        map.put("aaid", AbstractSessionUtils.getAndroidAdvertiseId(context));
        map.put("locale", AbstractLocaleUtils.getCurrentLocaleName(context));
        map.put("screen_density", ScreenDensity.get(context));
        return map;
    }

    /**
     * Get a non-id to roughly identify a user for a better content delivery experience
     *
     * @param context  the application context
     * @param listener the callback when the asynchronous keyboard id API request is done
     * @return {@link Call}<{@link AnonIdResponse}>
     */
    public static Call<AnonIdResponse> getAnonId(@NonNull Context context,
                                                 @Nullable final IAnonIdListener listener) {
        Context app;
        if (context instanceof Application) {
            app = context;
        } else {
            app = context.getApplicationContext();
        }

        // request for new keyboard id
        Call<AnonIdResponse> call = ApiClient.getInstance(app)
                .getAnonId(sApiService.getApiKey(), AbstractLocaleUtils.getCurrentLocaleName(context));

        call.enqueue(new WeakRefCallback<Context, AnonIdResponse>(app) {
            @Override
            public void success(@NonNull Context app, @Nullable AnonIdResponse response) {
                if (response != null && !TextUtils.isEmpty(response.getId())) {
                    AbstractSessionUtils.setAnonId(app, response.getId());

                    if (listener == null) {
                        return;
                    }

                    final String id = response.getId();
                    if (TextUtils.isEmpty(id)) {
                        listener.onReceiveAnonIdFailed(new IllegalStateException("keyboard id cannot be empty"));
                        return;
                    }
                    listener.onReceiveAnonIdSucceeded(id);
                }
            }

            @Override
            public void failure(@NonNull Context app, @Nullable Throwable throwable) {
                if (listener != null) {
                    listener.onReceiveAnonIdFailed(throwable);
                }
            }
        });

        // request for aaid
        AaidService.requestAaid(context);

        return call;
    }


    /**
     * Report shared GIF id for better search experience in the future
     *
     * @param context the application context
     * @param id      the gif id
     * @param query   the search query that led to this GIF, or {@code null} for trending GIFs
     * @return {@link Call}<{@link Void}>
     */
    public static Call<Void> registerShare(@NonNull Context context,
                                           @NonNull String id,
                                           @Nullable String query) {
        Call<Void> call = ApiClient.getInstance(context)
                .registerShare(getServiceIds(context), Integer.valueOf(id), StringConstant.getOrEmpty(query));
        call.enqueue(new VoidCallBack());
        return call;
    }

    /**
     * @param context  the application context
     * @param sourceId the source id of a {@link Result}
     * @param action   the action {view|share|tap}
     * @return {@link Call}<{@link Void}>
     */
    public static Call<Void> registerAction(@NonNull final Context context,
                                            @NonNull String sourceId,
                                            @NonNull String visualPosition,
                                            @ViewAction.Value final String action) {
        return registerAction(context, new MeasurableViewHolderEvent(sourceId, action, AbstractLocaleUtils.getUtcOffset(context), visualPosition));
    }

    /**
     * @param context the application context
     * @param event   the {@link MeasurableViewHolderEvent}
     * @return {@link Call}<{@link Void}>
     */
    public static Call<Void> registerAction(@NonNull final Context context,
                                            @NonNull MeasurableViewHolderEvent event) {
        List<MeasurableViewHolderEvent> actions = Collections.singletonList(event);
        return registerActions(context, actions);
    }

    /**
     * @param context the application context
     * @param events  the {@link MeasurableViewHolderEvent}
     * @return {@link Call}<{@link Void}>
     */
    public static Call<Void> registerActions(@NonNull final Context context,
                                             @NonNull List<MeasurableViewHolderEvent> events) {
        final String data = AbstractGsonUtils.getInstance().toJson(events);
        Call<Void> call = ApiClient.getInstance().registerActions(getServiceIds(context), data);
        call.enqueue(new VoidCallBack());
        return call;
    }
}


<|start_filename|>src/main/java/com/tenor/android/core/checker/ScriptDirectionChecker.java<|end_filename|>
package com.tenor.android.core.checker;

import android.content.Context;
import android.support.annotation.IntDef;
import android.support.annotation.Nullable;

import com.tenor.android.core.R;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;


public class ScriptDirectionChecker {

    @Retention(RetentionPolicy.CLASS)
    @IntDef({UNSPECIFIED, LEFT_TO_RIGHT, RIGHT_TO_LEFT})
    public @interface Value {
    }

    public static final int UNSPECIFIED = -1;
    public static final int LEFT_TO_RIGHT = 0;
    public static final int RIGHT_TO_LEFT = 1;

    @ScriptDirectionChecker.Value
    public static int checkSelfScriptDirection(@Nullable Context context) {
        if (context == null || context.getResources() == null) {
            return UNSPECIFIED;
        }

        if (context.getResources().getBoolean(R.bool.right_to_left)) {
            return RIGHT_TO_LEFT;
        } else {
            return LEFT_TO_RIGHT;
        }
    }
}


<|start_filename|>src/main/java/com/tenor/android/core/widget/WeakRefRecyclerView.java<|end_filename|>
package com.tenor.android.core.widget;

import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;

import com.tenor.android.core.measurable.IRecyclerView;
import com.tenor.android.core.util.AbstractWeakReferenceUtils;
import com.tenor.android.core.weakref.IWeakRefHandler;
import com.tenor.android.core.weakref.IWeakRefObject;
import com.tenor.android.core.weakref.WeakRefCurrentHandler;

import java.lang.ref.WeakReference;

/**
 * {@link WeakRefRecyclerView} is a subclass of {@link RecyclerView} for search suggestions
 * <p>
 * "AppCompatTextView" under "com.android.support:appcompat-v7" is not required, yet highly recommended
 * to ensure this widget is working properly on API 20-
 */
public class WeakRefRecyclerView extends RecyclerView implements IRecyclerView, IWeakRefObject<Context>,
        IWeakRefHandler<WeakRefRecyclerView> {

    private final WeakReference<Context> mWeakRef;
    private final WeakRefCurrentHandler<WeakRefRecyclerView> mHandler;

    public WeakRefRecyclerView(Context context) {
        this(context, null);
    }

    public WeakRefRecyclerView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public WeakRefRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        mWeakRef = new WeakReference<>(context);
        mHandler = new WeakRefCurrentHandler<>(this);
    }

    @NonNull
    public WeakRefCurrentHandler<WeakRefRecyclerView> getHandler() {
        return mHandler;
    }

    @NonNull
    @Override
    public WeakReference<Context> getWeakRef() {
        return mWeakRef;
    }

    @Override
    public boolean hasRef() {
        return AbstractWeakReferenceUtils.isAlive(mWeakRef);
    }
}


<|start_filename|>src/main/java/com/tenor/android/core/measurable/IMeasurableViewHolder.java<|end_filename|>
package com.tenor.android.core.measurable;

import android.support.annotation.FloatRange;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;

/**
 * Interface of a {@link RecyclerView.ViewHolder}, measure {@link MeasurableViewHolderData} of a {@link RecyclerView.ViewHolder}
 * <p>
 * (1) Implementations of methods in this {@link IMeasurableViewHolder} are recommended to be synchronized
 * <p>
 * (2) {@link MeasurableViewHolderData} must be initialized on constructor
 */
public interface IMeasurableViewHolder extends IViewHolder {

    /**
     * @return visible fraction of the item view
     */
    @FloatRange(from = 0f, to = 1f)
    float measure(@NonNull RecyclerView recyclerView);

    void attachMeasurer(@NonNull RecyclerView recyclerView);

    /**
     * Resume the measurer to collect {@link MeasurableViewHolderData} on referenced view resumed
     */
    void resumeMeasurer(@NonNull RecyclerView recyclerView);

    /**
     * Pause the measurer to collect {@link MeasurableViewHolderData} on referenced view paused
     */
    void pauseMeasurer(@NonNull RecyclerView recyclerView);

    /**
     * Destroy {@link MeasurableViewHolderData} on the itemView of a
     * {@link RecyclerView.ViewHolder} detached from window
     */
    void detachMeasurer();

    /**
     * Return true if the {@link RecyclerView.ViewHolder} has been attached from the {@link RecyclerView}
     */
    boolean isAttached();

    /**
     * Return true if the {@link RecyclerView.ViewHolder} has been detached from the {@link RecyclerView}
     */
    boolean isDetached();

    void flush();
}



<|start_filename|>src/main/java/com/tenor/android/core/widget/adapter/ListRVAdapter.java<|end_filename|>
package com.tenor.android.core.widget.adapter;

import android.support.annotation.CallSuper;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;

import com.tenor.android.core.view.IBaseView;

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
import java.util.Stack;

/**
 * The {@link RecyclerView} implementation of {@link android.widget.ListAdapter}
 *
 * @param <CTX> the referenced context
 * @param <T>   the list item type
 * @param <VH>  the view holder type extending from {@link RecyclerView.ViewHolder}
 */
public abstract class ListRVAdapter<CTX extends IBaseView, T, VH extends RecyclerView.ViewHolder>
        extends AbstractRVAdapter<CTX, VH> {

    private final List<T> mList;

    public ListRVAdapter(@Nullable final CTX ctx) {
        super(ctx);
        mList = new ArrayList<>();
    }

    public ListRVAdapter(@NonNull final WeakReference<CTX> weakRef) {
        super(weakRef);
        mList = new ArrayList<>();
    }

    @CallSuper
    @NonNull
    public List<T> getList() {
        return mList;
    }

    public void insert(@Nullable T item, boolean isAppend) {
        if (item == null) {
            return;
        }

        List<T> list = new ArrayList<>();
        list.add(item);
        insert(list, isAppend);
    }

    public void insert(@NonNull List<T> list) {
        insert(list, false);
    }

    public abstract void insert(@NonNull List<T> list, boolean isAppend);

    public void insert(int position, @NonNull List<T> list) {
        getList().addAll(position, list);
        notifyItemRangeInserted(position, list.size());
    }

    public void insert(int position, @NonNull T item) {
        getList().add(position, item);
        notifyItemInserted(position);
    }

    public void clearList() {
        getList().clear();
    }

    public synchronized void threadSafeRemove(@NonNull final IThreadSafeConditions<T> conditions) {

        if (conditions == null) {
            throw new IllegalArgumentException("conditions cannot be null");
        }

        /*
         * Remove everything except PivotsRV
         *
         * Use iterator to avoid ConcurrentModificationException
         */
        ListIterator<T> iterator = getList().listIterator();
        final Stack<Integer> positions = new Stack<>();
        int position;
        T item;
        while (iterator.hasNext()) {
            position = iterator.nextIndex();
            item = iterator.next();
            if (conditions.removeIf(item)) {
                iterator.remove();
                positions.push(position);
            }
        }
        conditions.onItemsRemoved(positions);
    }

    public interface IThreadSafeConditions<T> {
        /**
         * Condition for removing <T>
         */
        boolean removeIf(T item);

        void onItemsRemoved(Stack<Integer> positions);
    }
}


<|start_filename|>src/main/java/com/tenor/android/core/response/impl/SearchSuggestionResponse.java<|end_filename|>
package com.tenor.android.core.response.impl;

import android.support.annotation.NonNull;

import com.tenor.android.core.response.AbstractResponse;
import com.tenor.android.core.util.AbstractListUtils;

import java.util.Collections;
import java.util.List;

/**
 * The response of search suggestions
 */
public class SearchSuggestionResponse extends AbstractResponse {

    private static final long serialVersionUID = 8046525237691607393L;
    private List<String> results;

    @NonNull
    public List<String> getResults() {
        return !AbstractListUtils.isEmpty(results) ? results : Collections.<String>emptyList();
    }
}


<|start_filename|>src/main/java/com/tenor/android/core/widget/adapter/AbstractRVItem.java<|end_filename|>
package com.tenor.android.core.widget.adapter;

import android.support.annotation.NonNull;

/**
 * All recycler view items should inherit this abstract class
 */
public abstract class AbstractRVItem implements IRVItem {

    private final int mType;
    private final String mId;
    private int mRelativePosition = -1;

    public AbstractRVItem(int type) {
        this(type, String.valueOf(type));
    }

    public AbstractRVItem(int type, @NonNull final String id) {
        mType = type;
        mId = id;
    }

    /**
     * Get the type for this {@link AbstractRVItem}
     *
     * @return the type
     */
    @Override
    public int getType() {
        return mType;
    }

    /**
     * Get the unique identifier for this {@link AbstractRVItem}
     *
     * @return the id
     */
    @Override
    @NonNull
    public String getId() {
        return mId;
    }

    @Override
    public int getRelativePosition() {
        return mRelativePosition;
    }

    /**
     * Set the position relative to the same {@link #getType()},
     * this may not may not be the same as the adapter position
     *
     * @param relativePosition the relative position
     */
    public AbstractRVItem setRelativePosition(int relativePosition) {
        mRelativePosition = relativePosition;
        return this;
    }
}


<|start_filename|>src/main/java/com/tenor/android/core/response/ServiceReceiverWeakRefCallback.java<|end_filename|>
package com.tenor.android.core.response;

import android.os.ResultReceiver;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;

import com.tenor.android.core.util.AbstractWeakReferenceUtils;

import java.lang.ref.WeakReference;

/**
 * A Class extends {@link WeakRefCallback} with a weak reference on the given {@link ResultReceiver}
 *
 * @param <T> the type of response
 * @param <CTX> the type of calling service
 */
public abstract class ServiceReceiverWeakRefCallback<CTX, T> extends WeakRefCallback<CTX, T> {

    private final WeakReference<ResultReceiver> mWeakRefReceiver;

    public ServiceReceiverWeakRefCallback(@NonNull final CTX service,
                                          @NonNull final ResultReceiver receiver) {
        super(service);
        mWeakRefReceiver = new WeakReference<>(receiver);
    }

    @Override
    public final void success(@NonNull CTX service, T response) {
        if (AbstractWeakReferenceUtils.isAlive(mWeakRefReceiver)) {
            success(mWeakRefReceiver.get(), service, response);
        }
    }

    @Override
    public final void failure(@NonNull CTX service, Throwable throwable) {
        if (AbstractWeakReferenceUtils.isAlive(mWeakRefReceiver)) {
            failure(mWeakRefReceiver.get(), service, throwable);
        }
    }

    public abstract void success(@NonNull ResultReceiver receiver,
                                 @NonNull CTX service, @Nullable T response);

    public abstract void failure(@NonNull ResultReceiver receiver,
                                 @NonNull CTX service, @Nullable Throwable throwable);
}


<|start_filename|>src/main/java/com/tenor/android/core/service/AaidServiceApi26.java<|end_filename|>
package com.tenor.android.core.service;

import android.content.Context;
import android.content.Intent;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.JobIntentService;

import com.tenor.android.core.constant.StringConstant;

/**
 * Base class for {@code AaidService} to be use with v26 APIs.
 */
public final class AaidServiceApi26 extends JobIntentService {

    @Nullable
    private static IAaidListener sListener;

    @Override
    protected void onHandleWork(@Nullable Intent intent) {

        if (intent == null || intent.getAction() == null) {
            if (sListener != null) {
                sListener.failure(AaidInfo.AAID_FAILURE);
            }
            return;
        }

        String action = StringConstant.getOrEmpty(intent.getAction());
        switch (action) {
            case AaidService.ACTION_GET_AAID:
                AaidClient.init(getApplicationContext(), sListener);
                break;
            default:
                // do nothing
                break;
        }
    }

    public static void requestAaid(@NonNull Context context) {
        requestAaid(context, null);
    }

    public static void requestAaid(@NonNull Context context, @Nullable IAaidListener listener) {
        sListener = listener;
        enqueueWork(context, AaidServiceApi26.class, AaidService.JOB_ID_GET_AAID,
                new Intent(AaidService.ACTION_GET_AAID));
    }
}

<|start_filename|>src/main/java/com/tenor/android/core/measurable/IViewHolderDataManager.java<|end_filename|>
package com.tenor.android.core.measurable;

import android.content.Context;
import android.support.annotation.NonNull;

public interface IViewHolderDataManager {

    /**
     * Push a {@link MeasurableViewHolderEvent} to queue
     *
     * @param context the context
     * @param event   the serialized {@link MeasurableViewHolderEvent}
     */
    void push(@NonNull final Context context,
              @NonNull final MeasurableViewHolderEvent event);

    /**
     * Send feedback to improve future content delivery experience
     *
     * @param context   the context
     * @param batchSize the size of the analytic data batch
     */
    void send(@NonNull final Context context, int batchSize);
}


<|start_filename|>src/main/java/com/tenor/android/core/response/AbstractResponse.java<|end_filename|>
package com.tenor.android.core.response;

import android.support.annotation.NonNull;
import android.text.TextUtils;

import com.tenor.android.core.constant.StringConstant;

import java.io.Serializable;

/**
 * The abstract response that all other responses should extend from
 */
public abstract class AbstractResponse implements Serializable {

    private static final long serialVersionUID = 2769940807942589161L;
    private String error;

    @NonNull
    public String getError() {
        return StringConstant.getOrEmpty(error);
    }

    public boolean hasError() {
        return !TextUtils.isEmpty(error);
    }
}


<|start_filename|>src/main/java/com/tenor/android/core/measurable/MeasurableViewHolderEvent.java<|end_filename|>
package com.tenor.android.core.measurable;

import android.support.annotation.NonNull;

import com.google.gson.annotations.SerializedName;
import com.tenor.android.core.constant.ItemVisualPosition;
import com.tenor.android.core.constant.ViewAction;

import java.io.Serializable;
import java.util.Locale;

public class MeasurableViewHolderEvent implements Serializable {

    private static final long serialVersionUID = 2288577105348192434L;

    @SerializedName("source_id")
    private final String mSourceId;

    @SerializedName("timestamp")
    private final String mTimestamp;

    @SerializedName("action")
    private final String mAction;

    @SerializedName("timezone")
    private final String mUtcOffset;

    @ItemVisualPosition.Value
    @SerializedName("visual_pos")
    private final String mVisualPosition;

    @SerializedName("elapsed_ms")
    private String mDuration;

    @SerializedName("visible_fraction")
    private String mVisibleFraction;

    public MeasurableViewHolderEvent(@NonNull MeasurableViewHolderData data,
                                     @NonNull String utcOffset) {
        this(data.getId(), ViewAction.VIEW, utcOffset, data.getVisualPosition());
        mDuration = String.format(Locale.US, "%d", data.getAccumulatedVisibleDuration());
        mVisibleFraction = String.format(Locale.US, "%f", data.getVisibleFraction());
    }

    public MeasurableViewHolderEvent(@NonNull String sourceId,
                                     @ViewAction.Value String action,
                                     @NonNull String utcOffset,
                                     @ItemVisualPosition.Value String visualPosition) {
        mSourceId = sourceId;
        mTimestamp = String.format(Locale.US, "%d", System.currentTimeMillis());
        mAction = action;
        mUtcOffset = utcOffset;
        mVisualPosition = visualPosition;
    }
}


<|start_filename|>src/main/java/com/tenor/android/core/constant/MediaFormat.java<|end_filename|>
package com.tenor.android.core.constant;

import android.support.annotation.StringDef;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
 * The supported media formats
 */
public class MediaFormat {

    @Retention(RetentionPolicy.CLASS)
    @StringDef({ALL, GIF, JPEG, JPG, MP4, PNG})
    public @interface Value {
    }

    public static final String ALL = "all";
    public static final String GIF = "gif";
    public static final String MP4 = "mp4";
    public static final String PNG = "png";
    public static final String JPG = "jpg";
    public static final String JPEG = "jpeg";
}
