public class ChannelCreator {
    public static final String CHANNEL_ID = "io.whz.androidneuralnetwork.notification";
    private static final String CHANNEL_NAME = "Synapse Channel";

    @RequiresApi(api = Build.VERSION_CODES.O)
    public static void createChannel(@NonNull Context context) {
        Precondition.checkNotNull(context);

        final NotificationChannel channel = new NotificationChannel(CHANNEL_ID, CHANNEL_NAME,
                NotificationManager.IMPORTANCE_DEFAULT);
        channel.setLockscreenVisibility(NotificationCompat.VISIBILITY_PUBLIC);

        final NotificationManager manager = getManager(context);
        manager.deleteNotificationChannel(CHANNEL_ID);
        manager.createNotificationChannel(channel);
    }

    private static NotificationManager getManager(@NonNull Context context) {
        return (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
    }
} public class Dir {
    private static final String DOWNLOAD = "download";
    private static final String DECOMPRESS = "decompress";
    private static final String MNIST = "mnist";
    private static final String TRAIN = "train";
    private static final String TEST = "test";

    public final File root;
    public final File download;
    public final File decompress;
    public final File mnist;
    public final File train;
    public final File test;

    public Dir(@NonNull File root) {
        this.root = root;

        this.download = new File(root, DOWNLOAD);
        this.decompress = new File(root, DECOMPRESS);
        this.mnist = new File(root, MNIST);
        this.train = new File(this.mnist, TRAIN);
        this.test = new File(this.mnist, TEST);
    }
} public class Global {
    private final Uri mBaseDownloadUri;
    private final String[] mDataSet;

    private final Singleton<EventBus> mBus;
    private final Singleton<DaoSession> mSession;
    private final Singleton<Dir> mDirs;
    private final Singleton<SharedPreferences> mPreference;

    private Global() {
        mBaseDownloadUri = Uri.parse("http://yann.lecun.com/exdb/mnist");
        mDataSet = new String[]{
                "train-images-idx3-ubyte.gz",
                "train-labels-idx1-ubyte.gz",
                "t10k-images-idx3-ubyte.gz",
                "t10k-labels-idx1-ubyte.gz",
        };

        mBus = new Singleton<>();
        mSession = new Singleton<>();
        mDirs = new Singleton<>();
        mPreference = new Singleton<>();
    }

    public String[] getDataSet() {
        return mDataSet;
    }

    public Uri getBaseDownloadUri() {
        return mBaseDownloadUri;
    }

    public void setPreference(@NonNull SharedPreferences preference) {
        mPreference.setAndLock(Precondition.checkNotNull(preference));
    }

    public SharedPreferences getPreference() {
        return mPreference.get();
    }

    public void setBus(@NonNull EventBus bus) {
        mBus.setAndLock(Precondition.checkNotNull(bus));
    }

    public EventBus getBus() {
        return mBus.get();
    }

    public void setSession(@NonNull DaoSession session) {
        mSession.setAndLock(session);
    }

    public DaoSession getSession() {
        return mSession.get();
    }

    public void setRootDir(@NonNull File root) {
        final Dir dirs = new Dir(root);
        mDirs.setAndLock(dirs);
    }

    public Dir getDirs() {
        return mDirs.get();
    }

    public boolean isDirSet() {
        return mDirs.isSet();
    }

    public static Global getInstance() {
        return Holder.sInstance;
    }

    private interface Holder {
        Global sInstance = new Global();
    }
} public enum Scheduler implements IThreadExecutor {
    Main(new MainThread()), Secondary(new SecondaryThread());

    private final IThreadExecutor mExecutor;

    Scheduler(IThreadExecutor executor) {
        mExecutor = executor;
    }

    @Override
    public void execute(Runnable runnable) {
        mExecutor.execute(runnable);
    }

    private static class SecondaryThread implements IThreadExecutor {
        private static int sCoreThreadNum = Runtime.getRuntime().availableProcessors() + 1;
        private final ExecutorService mExecutorService = Executors.newFixedThreadPool(sCoreThreadNum);

        @Override
        public void execute(Runnable runnable) {
            mExecutorService.execute(runnable);
        }
    }

    private static class MainThread implements IThreadExecutor {
        private final Handler handler = new Handler(Looper.getMainLooper());

        @Override
        public void execute(Runnable runnable) {
            handler.post(runnable);
        }
    }

} public class DataSet {
    private static final String TAG = App.TAG + "-DataSet";
    private static final int DEFAULT_MIN_BATCH = 20;
    private static final int DIGIT_COUNT = 10;
    private static final int PIXEL_COUNT = 784;

    private final int mMiniBatch;
    private final List<File> mBatchFiles = new ArrayList<>();
    private final List<File> mCurFiles = new ArrayList<>();

    private Digit[] mCurDigits;
    private int mRemain;
    private int mIndex;

    public DataSet(@NonNull File... batchFiles) {
        mMiniBatch = DEFAULT_MIN_BATCH;
        mBatchFiles.addAll(Arrays.asList(batchFiles));

        reset();
    }

    public DataSet(int miniBatch, @NonNull File... batchFiles) {
        Precondition.checkArgument(miniBatch <= 0, "MiniBatch must be positive");

        mMiniBatch = miniBatch;
        mBatchFiles.addAll(Arrays.asList(batchFiles));

        reset();
    }

    private void reset() {
        mCurFiles.clear();
        mCurFiles.addAll(mBatchFiles);
        mCurDigits = null;
        mRemain = 0;
        mIndex = 0;
    }

    @Nullable
    public Batch nextBatch() {
        final List<Digit> batch = new ArrayList<>();
        int need = mMiniBatch;

        while (need > 0) {
            while (mRemain <= 0 && !mCurFiles.isEmpty()) {
                final File file = mCurFiles.remove(0);
                mCurDigits = nextDigits(file);

                if (mCurDigits != null && mCurDigits.length != 0) {
                    mRemain = mCurDigits.length;
                    mIndex = 0;
                }
            }

            if (mRemain <= 0) {
                break;
            }

            final int size = Math.min(need, mRemain);
            need -= size;
            mRemain -= size;

            final Digit[] window = new Digit[size];
            System.arraycopy(mCurDigits, mIndex, window, 0, size);
            mIndex += size;
            batch.addAll(Arrays.asList(window));
        }

        return convert2Matrix(batch);
    }

    private Batch convert2Matrix(@NonNull List<Digit> digits) {
        if (digits.isEmpty()) {
            return null;
        }

        final int len = digits.size();

        final Matrix[] inputs = new Matrix[len];
        final Matrix[] targets = new Matrix[len];
        Digit digit;

        for (int i = 0; i < len; ++i) {
            digit = digits.get(i);

            inputs[i] = Matrix.array(digit.colors, PIXEL_COUNT);
            targets[i] = oneHot(digit.label);
        }

        return new Batch(inputs, targets);
    }

    private Matrix oneHot(int actual) {
        final double[] doubles = new double[DIGIT_COUNT];
        doubles[actual] = 1D;

        return Matrix.array(doubles, DIGIT_COUNT);
    }

    @Nullable
    private Digit[] nextDigits(@NonNull File file) {
        final Digit[] res = MNISTUtil.readBatches(file);

        return res == null ? null : normalize(res);
    }

    private Digit[] normalize(@NonNull Digit[] digits) {
        final List<Digit> tmp = new ArrayList<>(Arrays.asList(digits));
        Collections.shuffle(tmp);
        tmp.toArray(digits);

        return digits;
    }

    public void shuffle() {
        Collections.shuffle(mBatchFiles);
        reset();
    }
} public class MNISTUtil {
    private static final int MASK = 0xFF;
    private static final int LABEL_MAGIC = 2049;
    private static final int IMAGE_MAGIC = 2051;
    private static final int BATCH_MAGIC = 2052;
    public static final int MAX_TRAINING_SIZE = 60000;
    public static final int PRE_FILE_SIZE = 2000;
    private static final String BATCH_FILE_SUFFIX = "batch";

    public static boolean gunzip(File sourceFile, File targetFile) {
        if (!sourceFile.exists()) {
            return false;
        }

        GZIPInputStream inputStream = null;
        FileOutputStream outputStream = null;

        boolean result;

        try {
            inputStream = new GZIPInputStream(new FileInputStream(sourceFile));
            outputStream = new FileOutputStream(targetFile);

            int len;
            final byte[] buffer = new byte[1024];

            while ((len = inputStream.read(buffer)) != -1) {
                outputStream.write(buffer, 0, len);
            }

            outputStream.flush();
            result = true;
        } catch (IOException e) {
            e.printStackTrace();

            result = false;
        } finally {
            try {
                if (inputStream != null) {
                    inputStream.close();
                }

                if (outputStream != null) {
                    outputStream.close();
                }
            } catch (IOException e1) {
                e1.printStackTrace();
            }
        }

        return result;
    }

    public static List<Integer> parseLabels(File sourceFile) {
        DataInputStream inputStream = null;
        final List<Integer> list = new ArrayList<>();

        try {
            inputStream = new DataInputStream(new FileInputStream(sourceFile));

            final int magic = inputStream.readInt();

            if (magic != LABEL_MAGIC) {
                throw new IllegalArgumentException();
            }

            final int len = inputStream.readInt();
            byte cur;

            for (int i = 0; i < len; ++i) {
                cur = inputStream.readByte();
                list.add((int) cur);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (inputStream != null) {
                    inputStream.close();
                }
            } catch (IOException e1) {
                e1.printStackTrace();
            }
        }

        return list;
    }

    public static Figure[] readFigures(@NonNull File file) {
        Precondition.checkNotNull(file);

        if (!file.exists()) {
            return null;
        }

        DataInputStream inputStream = null;

        try {
            inputStream = new DataInputStream(new FileInputStream(file));

            if (inputStream.readInt() != BATCH_MAGIC) {
                return null;
            }

            final int len = inputStream.readInt();
            final int rows = inputStream.readInt();
            final int cols = inputStream.readInt();

            final Figure[] figures = new Figure[len];
            final int size = rows * cols;
            final byte[] buffer = new byte[size];
            int curNum;

            for (int i = 0; i < len; ++i) {
                curNum = inputStream.readInt();
                inputStream.read(buffer);

                figures[i] = new Figure(curNum, buffer.clone());
            }

            return figures;
        } catch (IOException e) {
            e.printStackTrace();

            return null;
        } finally {
            try {
                if (inputStream != null) {
                    inputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static boolean parseImages(File sourceFile, File targetDir, List<Integer> labels) {
        if (!sourceFile.exists() || !targetDir.exists()) {
            return false;
        }

        DataInputStream inputStream = null;
        DataOutputStream outputStream = null;

        boolean result;

        try {
            inputStream = new DataInputStream(new FileInputStream(sourceFile));

            final int magic = inputStream.readInt();

            if (magic != IMAGE_MAGIC) {
                throw new IllegalArgumentException();
            }

            final int len = inputStream.readInt();

            if (len != labels.size()) {
                throw new IllegalStateException();
            }

            final int rows = inputStream.readInt();
            final int cols = inputStream.readInt();
            final byte[] buffer = new byte[rows * cols];
            int count = 1;

            for (int i = 1; i <= len; ++i) {
                if (outputStream == null) {
                    outputStream = new DataOutputStream(new FileOutputStream(new File(targetDir,
                            String.format("%s.%s", count++, BATCH_FILE_SUFFIX))));
                    outputStream.writeInt(BATCH_MAGIC);
                    outputStream.writeInt(PRE_FILE_SIZE);
                    outputStream.writeInt(rows);
                    outputStream.writeInt(cols);
                }

                inputStream.read(buffer);
                outputStream.writeInt(labels.get(i - 1));
                outputStream.write(buffer);

                if (i % PRE_FILE_SIZE == 0) {
                    outputStream.flush();
                    outputStream.close();
                    outputStream = null;
                }
            }

            result = true;
        } catch (IOException e) {
            e.printStackTrace();

            result = false;
        } finally {
            try {
                if (inputStream != null) {
                    inputStream.close();
                }

                if (outputStream != null) {
                    outputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        return result;
    }

    public static Digit[] readBatches(File file) {
        if (!file.exists()) {
            return null;
        }

        DataInputStream inputStream = null;

        try {
            inputStream = new DataInputStream(new FileInputStream(file));

            if (inputStream.readInt() != BATCH_MAGIC) {
                return null;
            }

            final int len = inputStream.readInt();
            final int rows = inputStream.readInt();
            final int cols = inputStream.readInt();

            final int size = rows * cols;
            final Digit[] digits = new Digit[len];
            final byte[] buffer = new byte[size];
            int curNum;

            for (int i = 0; i < len; ++i) {
                curNum = inputStream.readInt();
                inputStream.read(buffer);

                digits[i] = new Digit(curNum, convertMnistImage2Darkness(buffer));
            }

            return digits;
        } catch (IOException e) {
            e.printStackTrace();

            return null;
        } finally {
            try {
                if (inputStream != null) {
                    inputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * In Mnist, 0 represents the white color, and 255 represents the black color
     *
     */
    private static double[] convertMnistImage2Darkness(@NonNull byte[] bytes) {
        final double[] doubles = new double[bytes.length];

        for (int i = 0, len = bytes.length; i < len; ++i) {
            doubles[i] = (double) (MASK & bytes[i]) / MASK;
        }

        return doubles;
    }

    /**
     * In android bitmap, 0 represents the black color, and 255 represents the white color
     */
    public static double[] convertBitmap2Darkness(@NonNull int[] pixels) {
        final double[] res = new double[pixels.length];

        for (int i = 0, len = pixels.length; i < len; ++i) {
            res[i] = (double)(MASK - (MASK & pixels[i])) / MASK;
        }

        return res;
    }

    public static int[] convertByteArray2Bitmap(@NonNull byte[] bytes) {
        final int[] res = new int[bytes.length];
        int color;

        for (int i = 0, len = bytes.length; i < len; ++i) {
            color = MASK - (MASK & bytes[i]);
            res[i] = color << 16 | color << 8 | color | 0xFF000000;
        }

        return res;
    }
} public class NeuralNetwork {
    private static final String TAG = App.TAG + "-NeuralNetwork";
    
    public static final int INPUT_LAYER_NUMBER = 784;
    public static final int OUTPUT_LAYER_NUMBER = 10;

    private final Matrix[] mWeights;
    private final Matrix[] mBiases;

    public NeuralNetwork(@NonNull int... hiddenLayerSizes) {
        Precondition.checkNotNull(hiddenLayerSizes, "Hidden layers must not be null!");

        final int hiddenLen;
        Precondition.checkArgument((hiddenLen = hiddenLayerSizes.length) != 0, "Size of Hidden layers must not be zero!");

        final int[] totalSizes = new int[hiddenLen + 2];
        System.arraycopy(hiddenLayerSizes, 0, totalSizes, 1, hiddenLen);
        totalSizes[0] = INPUT_LAYER_NUMBER;
        totalSizes[totalSizes.length - 1] = OUTPUT_LAYER_NUMBER;

        mBiases = newBiasesMatrix(totalSizes);
        mWeights = newWeightsMatrices(totalSizes);
    }

    public NeuralNetwork(@NonNull Matrix[] weights, @NonNull Matrix[] biases) {
        Precondition.checkNotNull(weights);
        Precondition.checkNotNull(biases);

        mWeights = weights;
        mBiases = biases;
    }

    private Matrix[] newWeightsMatrices(int[] totalSize) {
        final int len = totalSize.length - 1;

        final int[] rows = new int[len];
        System.arraycopy(totalSize, 1, rows, 0, len);

        final int[] cols = new int[len];
        System.arraycopy(totalSize, 0, cols, 0, len);

        return MatrixUtil.randns(rows, cols);
    }

    private Matrix[] newBiasesMatrix(int[] totalSize) {
        final int len = totalSize.length - 1;

        final int[] rows = new int[len];
        System.arraycopy(totalSize, 1, rows, 0, rows.length);

        final int[] cols = new int[len];
        Arrays.fill(cols, 1);

        return MatrixUtil.randns(rows, cols);
    }

    public Matrix[] getBiases() {
        return mBiases;
    }

    public Matrix[] getWeights() {
        return mWeights;
    }

    public void train(int epochs, double learningRate,
                      @NonNull DataSet trainDataSet, @NotNull DataSet validateDataSet,
                      @NonNull DataSet testDataSet, @NonNull TrainCallback callback) {
        Precondition.checkArgument(epochs > 0, "Epochs must greater than 0");
        Precondition.checkArgument(learningRate > 0D, "Learning rate must greater than 0");
        Precondition.checkNotNull(trainDataSet);

        new TrainRunnable(epochs, learningRate, trainDataSet,
                validateDataSet, testDataSet, mBiases, mWeights, callback)
                .run();
    }

    private static class TrainRunnable implements Runnable {
        private final int mEpochs;
        private final double mLearningRate;
        private final DataSet mTraining;
        private final DataSet mValidation;
        private final DataSet mTest;
        private final Matrix[] mBiases;
        private final Matrix[] mWeights;
        private TrainCallback mCallback;

        private TrainRunnable(int epochs, double learningRate, DataSet training,
                              DataSet validation, DataSet test, Matrix[] biases, Matrix[] weights,
                              TrainCallback callback) {
            mEpochs = epochs;
            mLearningRate = learningRate;
            mTraining = training;
            mValidation = validation;
            mTest = test;
            mBiases = biases;
            mWeights = weights;
            mCallback = callback;
        }

        @Override
        public void run() {
            mCallback.onStart();

            for (int i = 1; i <= mEpochs; ++i) {
                mTraining.shuffle();
                sgd();

                final double rate = evaluate(mValidation);

                if (!mCallback.onUpdate(i, rate)) {
                    mCallback.onEvaluate();
                    return;
                }
            }

            mCallback.onEvaluate();

            final double rate = evaluate(mTest);
            mCallback.onComplete(rate);
        }

        private void sgd() {
            Batch batch;

            while ((batch = mTraining.nextBatch()) != null) {
                updateMiniBatch(batch.inputs, batch.targets);
            }
        }

        private void updateMiniBatch(Matrix[] inputs, Matrix[] targets) {
            final Matrix[] batchWeights = MatrixUtil.zerosLike(mWeights);
            final Matrix[] batchBiases = MatrixUtil.zerosLike(mBiases);
            final int len = inputs.length;

            for (int i = 0; i < len; ++i) {
                feed(inputs[i], targets[i], batchWeights, batchBiases);
            }

            update(batchWeights, batchBiases, len);
        }

        private void update(Matrix[] batchWeights, Matrix[] batchBiases, int count) {
            final int len = mWeights.length;
            final double tmp = mLearningRate / count;

            for (int i = 0; i < len; ++i) {
                mWeights[i].minusTo(batchWeights[i].times(tmp));
                mBiases[i].minusTo(batchBiases[i].times(tmp));
            }
        }

        private void feed(Matrix input, Matrix target, Matrix[] batchWeights, Matrix[] batchBiases) {
            final Matrix[] activations = forwardPropagation(input);
            final int aLen = activations.length;
            final int bLen = batchWeights.length;

            final Matrix error = activations[aLen - 1].minus(target);
            Matrix delta = error.times(ActivateFunction.sigmoidPrime(activations[aLen - 1]));

            batchBiases[bLen - 1].plusTo(delta);
            batchWeights[bLen - 1].plusTo(delta.dot(activations[aLen - 2].transpose()));

            for (int i = 2; i < aLen; ++i) {
                final Matrix prime = ActivateFunction.sigmoidPrime(activations[aLen - i]);
                delta = mWeights[bLen - i + 1].transpose()
                        .dot(delta)
                        .times(prime);

                batchBiases[bLen - i].plusTo(delta);
                batchWeights[bLen - i].plusTo(delta.dot(activations[aLen - i - 1].transpose()));
            }
        }

        private Matrix[] forwardPropagation(@NonNull Matrix input) {
            final int len = mWeights.length;
            final Matrix[] activations = new Matrix[len + 1];
            activations[0] = input;

            for (int i = 0; i < len; ++i) {
                final Matrix matrix = mWeights[i]
                        .dot(activations[i])
                        .plus(mBiases[i]);

                activations[i + 1] = ActivateFunction.sigmoid(matrix);
            }

            return activations;
        }

        private double evaluate(@NonNull DataSet dataSet) {
            Batch batch;
            int correct = 0;
            int total = 0;

            dataSet.shuffle();

            while ((batch = dataSet.nextBatch()) != null) {
                final Matrix[] inputs = batch.inputs;
                final Matrix[] targets = batch.targets;
                final int len = inputs.length;
                total += len;

                for (int i = 0; i < len; ++i) {
                    final Matrix output = feedForward(mWeights, mBiases, inputs[i]);

                    if (MatrixUtil.argmax(output) == MatrixUtil.index(targets[i])) {
                        ++correct;
                    }
                }
            }

            return total != 0 ? (double) correct / total : 0D;
        }
    }

    public Pair<Integer, Double> predict(@NonNull Matrix input) {
        return MatrixUtil.findMax(feedForward(mWeights, mBiases, input));
    }

    private static Matrix feedForward(@NonNull Matrix[] weights, @NonNull Matrix[] biases,
                                      @NonNull Matrix input) {
        final int len = weights.length;
        Matrix res = input;

        for (int i = 0; i < len; ++i) {
            res = ActivateFunction.sigmoid(weights[i].dot(res).plus(biases[i]));
        }

        return res;
    }
} public interface TrainCallback {
    void onStart();

    boolean onUpdate(int progress, double accurate);

    void onEvaluate();

    void onComplete(double evaluate);
} public interface PreferenceCons {
    String IS_DATA_SET_READY = "is_downloaded_data_set";
    String IS_FIST_PLAY = "is_fist_play";
    String SHOULD_ADD_DEMO_MODEL = "should_add_demo_model";
} public class TrackCons {
    private static final String SPLIT = "_";
    private static final String INDEX = BuildConfig.DEBUG ? "debug" : "";

    public interface Main {
        String INDEX = concat(TrackCons.INDEX, MainActivity.class.getSimpleName().toLowerCase());

        String CLICK_DOWNLOAD = concat(INDEX, "click_download");
        String CLICK_FAB = concat(INDEX, "click_fab");
        String CLICK_PLAY = concat(INDEX, "click_play");
        String CLICK_TRAINED = concat(INDEX, "click_trained");
        String CLICK_TRAINING = concat(INDEX, "click_training");
        String CLICK_ABOUT = concat(INDEX, "click_about");

        String SCROLL_BOTTOM = concat(INDEX, "scroll_bottom");
    }

    public interface Play {
        String INDEX = concat(TrackCons.INDEX, PlayActivity.class.getSimpleName().toLowerCase());

        String CLICK_MODEL_SELECTION = concat(INDEX, "click_model_selection");
        String CLICK_VIEW_DETAIL = concat(INDEX, "click_view_detail");

        String PLAY_MNIST = concat(INDEX, "play_mnist");
        String PLAY_HAND_WRITE = concat(INDEX, "play_hand_write");
    }

    public interface Model {
        String INDEX = concat(TrackCons.INDEX, NeuralModelActivity.class.getSimpleName().toLowerCase());

        String CLICK_ADD_NEW_LAYER = concat(INDEX, "click_add_new_layer");
        String CLICK_LAYER_DELETE = concat(INDEX, "click_layer_delete");
        String CLICK_TRAIN = concat(INDEX, "click_train");
    }

    public interface Detail {
        String INDEX = concat(TrackCons.INDEX, ModelDetailActivity.class.getSimpleName().toLowerCase());

        String CLICK_INTERRUPT = concat(INDEX, "click_interrupt");
        String CLICK_DELETE = concat(INDEX, "click_delete");
        String CLICK_PLAY = concat(INDEX, "click_play");
    }

    public interface About {
        String INDEX = concat(TrackCons.INDEX, AboutDialog.class.getSimpleName().toLowerCase());

        String CLICK_GITHUB = concat(INDEX, "click_github");
        String CLICK_RATE = concat(INDEX, "click_rate");
        String CLICK_SHARE = concat(INDEX, "click_share");
    }

    public interface Service {
        String INDEX = concat(TrackCons.INDEX, MainService.class.getSimpleName().toLowerCase());

        String DOWNLOAD = concat(INDEX, "download");
        String DECOMPRESS = concat(INDEX, "decompress");
        String TRAIN = concat(INDEX, "train");
        String INTERRUPT_REQ = concat(INDEX, "interrupt_req");
        String INTERRUPT = concat(INDEX, "interrupt");
    }

    public interface APP {
        String INDEX = concat(TrackCons.INDEX, App.class.getSimpleName().toLowerCase());

        String INITIALIZE = concat(INDEX, "initialize");
        String CAUGHT = concat(INDEX, "catch_exception");
    }

    public interface Key {
        String SUCCESS = "success";
        String MSG = "msg";
        String TIME_USED = "time_used";
    }

    public interface Lifecycle {
        String INDEX = concat(TrackCons.INDEX, "lifecycle");

        String ENTER = concat(INDEX, "enter");
        String LEAVE = concat(INDEX, "leave");
    }

    public static String concat(@NonNull String... array) {
        final StringBuilder builder = new StringBuilder();

        for (int i = 0, len = array.length; i < len; ++i) {
            if (builder.length() != 0) {
                builder.append(SPLIT);
            }

            builder.append(array[i]);
        }

        return builder.toString();
    }
} public class Model implements Serializable {
    public static final long serialVersionUID = 0xAAFF;

    private Long id;
    private String name;
    private long createdTime;
    private double learningRate;
    private int epochs;
    private int dataSize;
    private long timeUsed;
    private double evaluate;
    private int stepEpoch;
    private double[] accuracies;
    private int[] hiddenSizes;
    private Matrix[] biases;
    private Matrix[] weights;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public long getCreatedTime() {
        return createdTime;
    }

    public void setCreatedTime(long createdTime) {
        this.createdTime = createdTime;
    }

    public double getLearningRate() {
        return learningRate;
    }

    public void setLearningRate(double learningRate) {
        this.learningRate = learningRate;
    }

    public int getEpochs() {
        return epochs;
    }

    public void setEpochs(int epochs) {
        this.epochs = epochs;
    }

    public int getDataSize() {
        return dataSize;
    }

    public void setDataSize(int dataSize) {
        this.dataSize = dataSize;
    }

    public long getTimeUsed() {
        return timeUsed;
    }

    public void setTimeUsed(long timeUsed) {
        this.timeUsed = timeUsed;
    }

    public double getEvaluate() {
        return evaluate;
    }

    public void setEvaluate(double evaluate) {
        this.evaluate = evaluate;
    }

    public int getStepEpoch() {
        return stepEpoch;
    }

    public void setStepEpoch(int stepEpoch) {
        this.stepEpoch = stepEpoch;
    }

    public int[] getHiddenSizes() {
        return hiddenSizes;
    }

    public void setHiddenSizes(int[] hiddenSizes) {
        this.hiddenSizes = hiddenSizes;
    }

    public Matrix[] getBiases() {
        return biases;
    }

    public void setBiases(Matrix[] biases) {
        this.biases = biases;
    }

    public Matrix[] getWeights() {
        return weights;
    }

    public void setWeights(Matrix[] weights) {
        this.weights = weights;
    }

    public double[] getAccuracies() {
        return accuracies;
    }

    public void setAccuracies(double[] accuracies) {
        this.accuracies = accuracies;
    }
} public class MANEvent<T> extends TypeEvent<T> {
    public static final int CLICK_DOWNLOAD = 0x01;
    public static final int DOWNLOAD_COMPLETE = 0x01 << 1;
    public static final int DECOMPRESS_COMPLETE = 0x01 << 2;
    public static final int REJECT_MSG = 0x01 << 3;
    public static final int JUMP_TO_PLAY = 0x01 << 4;
    public static final int JUMP_TO_TRAINED = 0x01 << 5;
    public static final int JUMP_TO_TRAINING = 0x01 << 6;

    @IntDef({
            CLICK_DOWNLOAD, DOWNLOAD_COMPLETE, DECOMPRESS_COMPLETE,
            REJECT_MSG, JUMP_TO_PLAY, JUMP_TO_TRAINED, JUMP_TO_TRAINING
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface Event {
    }

    public MANEvent(int what, T obj) {
        super(what, obj);
    }

    public MANEvent(int what) {
        super(what);
    }
} public class MSNEvent<T> extends TypeEvent<T> {
    public static final int DOWNLOAD_COMPLETE = 0x01;
    public static final int DECOMPRESS_COMPLETE = 0x01 << 1;
    public static final int TRAIN_INTERRUPT = 0x01 << 2;

    public MSNEvent(int what, T obj) {
        super(what, obj);
    }

    public MSNEvent(int what) {
        super(what);
    }
} public class TrainEvent<T> extends TypeEvent<T> {
    public static final int START = 0x01;
    public static final int UPDATE = 0x01 << 1;
    public static final int EVALUATE = 0x01 << 2;
    public static final int COMPLETE = 0x01 << 3;
    public static final int ERROR = 0x01 << 4;
    public static final int INTERRUPTED = 0x01 << 5;

    @Retention(RetentionPolicy.SOURCE)
    @IntDef({START, UPDATE, EVALUATE, COMPLETE, ERROR, INTERRUPTED})
    public @interface Type {}

    public TrainEvent(@Type int what, T obj) {
        super(what, obj);
    }

    public TrainEvent(@Type int what) {
        super(what);
    }
} public class ExceptionHelper {
    private static final boolean sEnable = BuildConfig.TRACK_ENABLE;
    private final Tracker mTracker;

    private ExceptionHelper() {
        mTracker = Tracker.getInstance();
    }

    public void caught(@NotNull Throwable e) {
        if (sEnable) {
            mTracker.event(CAUGHT)
                    .put(MSG, toString(e))
                    .log();
        } else {
            e.printStackTrace();
        }
    }

    private String toString(@NotNull Throwable e) {
        final StackTraceElement[] elements = e.getStackTrace();

        if (elements == null
                || elements.length == 0) {
            return "";
        }

        final StringBuilder builder = new StringBuilder();

        for (StackTraceElement element : elements) {
            builder.append(element.toString())
                    .append("\n");
        }

        return builder.toString();
    }

    public static ExceptionHelper getInstance() {
        return Holder.sInstance;
    }

    private interface Holder {
        ExceptionHelper sInstance = new ExceptionHelper();
    }
} public class TimeHelper {
    private final Map<String, Long> mMap;

    private TimeHelper() {
        mMap = new ArrayMap<>();
    }

    public void start(@NonNull String id) {
        if (TextUtils.isEmpty(id) || mMap.containsKey(id)) {
            new IllegalArgumentException("Illegal id")
                    .printStackTrace();

            return;
        }

        mMap.put(id, System.currentTimeMillis());
    }

    public long stop(@NonNull String id) {
        if (TextUtils.isEmpty(id) || !mMap.containsKey(id)) {
            new IllegalArgumentException("Illegal id")
                    .printStackTrace();

            return 0L;
        }

        final long startTIme = mMap.remove(id);

        return System.currentTimeMillis() - startTIme;
    }

    public static TimeHelper getInstance() {
        return Holder.sInstance;
    }

    private interface Holder {
        TimeHelper sInstance = new TimeHelper();
    }
} public class Tracker implements ITracker {
    private final Set<AbsTrackHandler> mTracks = new HashSet<>();

    private EventBus mBus;

    public void initialize(@NonNull Context context, @NonNull EventBus bus) {
        mBus = Precondition.checkNotNull(bus);

        if (BuildConfig.TRACK_ENABLE) {
            mTracks.add(new FirebaseTrackHandler(context));
            mTracks.add(new AmplitudeTrackHandler(context));
        } else {
            mTracks.add(new DebugTrackHandler(context));
        }

        for (AbsTrackHandler track : mTracks) {
            track.register(bus);
        }

        if (context instanceof Application) {
            ((Application) context).registerActivityLifecycleCallbacks(new ActivityLifecycleTracker(this));
        }
    }

    @Override
    public void logEvent(@NonNull TrackEvent event) {
        if (mBus == null) {
            new NullPointerException("EvenBus is null, please initialize first")
                    .printStackTrace();
            return;
        }

        mBus.post(event);
    }

    public void logEvent(@NonNull String id) {
        Precondition.checkNotNull(id);

        logEvent(new TrackEvent(id, null));
    }

    public EventBuilder event(@NonNull String id) {
        Precondition.checkNotNull(id);

        return new EventBuilder(id);
    }

    public static Tracker getInstance() {
        return Holder.sInstance;
    }

    private interface Holder {
        Tracker sInstance = new Tracker();
    }

    public static class EventBuilder{
        private final Bundle bundle = new Bundle();
        private final String id;

        EventBuilder(@NonNull String id) {
            this.id = id;
        }

        public EventBuilder put(@NonNull String key, boolean value) {
            bundle.putBoolean(key, value);

            return this;
        }

        public EventBuilder put(@NonNull String key, int value) {
            bundle.putInt(key, value);

            return this;
        }

        public EventBuilder put(@NonNull String key, double value) {
            bundle.putDouble(key, value);
            return this;
        }

        public EventBuilder put(@NonNull String key, String value) {
            bundle.putString(key, value);

            return this;
        }

        public EventBuilder put(@NonNull String key, Serializable value) {
            bundle.putSerializable(key, value);

            return this;
        }

        public void log() {
            Holder.sInstance.logEvent(new TrackEvent(id, bundle));
        }
    }
} public class DbHelper {
    public static Model dbModel2Model(@NonNull DBModel dbModel) {
        Precondition.checkNotNull(dbModel);

        final Model model = new Model();

        model.setId(dbModel.getId());
        model.setName(dbModel.getName());
        model.setCreatedTime(dbModel.getCreatedTime());
        model.setLearningRate(dbModel.getLearningRate());
        model.setEpochs(dbModel.getEpochs());
        model.setStepEpoch(dbModel.getEpochs());
        model.setDataSize(dbModel.getDataSize());
        model.setTimeUsed(dbModel.getTimeUsed());
        model.setEvaluate(dbModel.getEvaluate());
        model.setHiddenSizes(byteArray2IntArray(dbModel.getHiddenSizeBytes()));
        model.setAccuracies(byteArray2DoubleArray(dbModel.getAccuracyBytes()));
        model.setBiases(byteArray2MatrixArray(dbModel.getBiasBytes()));
        model.setWeights(byteArray2MatrixArray(dbModel.getWeightBytes()));

        return model;
    }

    public static DBModel model2DBModel(@NonNull Model model) {
        Precondition.checkNotNull(model);

        final DBModel dbModel = new DBModel();

        dbModel.setId(model.getId());
        dbModel.setName(model.getName());
        dbModel.setCreatedTime(model.getCreatedTime());
        dbModel.setLearningRate(model.getLearningRate());
        dbModel.setEpochs(model.getEpochs());
        dbModel.setDataSize(model.getDataSize());
        dbModel.setTimeUsed(model.getTimeUsed());
        dbModel.setEvaluate(model.getEvaluate());
        dbModel.setHiddenSizeBytes(convert2ByteArray(model.getHiddenSizes()));
        dbModel.setAccuracyBytes(convert2ByteArray(model.getAccuracies()));
        dbModel.setBiasBytes(convert2ByteArray(model.getBiases()));
        dbModel.setWeightBytes(convert2ByteArray(model.getWeights()));

        return dbModel;
    }

    @Nullable
    private static byte[] convert2ByteArray(int... array) {
        if (array == null) {
            return null;
        }

        ByteBuffer buffer = null;

        try {
            buffer = ByteBuffer.allocate(array.length << 2);

            for (int i : array) {
                buffer.putInt(i);
            }
        } catch (Exception e) {
            e.printStackTrace();
            buffer = null;
        }

        return buffer == null ? null : buffer.array();
    }

    @Nullable
    private static byte[] convert2ByteArray(double... array) {
        if (array == null) {
            return  null;
        }

        ByteBuffer buffer = null;

        try {
            buffer = ByteBuffer.allocate(array.length << 3);

            for (double i : array) {
                buffer.putDouble(i);
            }
        } catch (Exception e) {
            e.printStackTrace();

            buffer = null;
        }

        return buffer == null ? null : buffer.array();
    }

    @Nullable
    private static byte[] convert2ByteArray(Matrix... matrices) {
        if (matrices == null) {
            return null;
        }

        ByteBuffer buffer = null;
        int sum = 0;

        sum += 4;

        for (Matrix matrix : matrices) {
            sum += calMatrixLen(matrix);
        }

        try {
            buffer = ByteBuffer.allocate(sum);

            buffer.putInt(matrices.length);

            for (Matrix matrix : matrices) {
                buffer.putInt(matrix.getRow());
                buffer.putInt(matrix.getCol());

                final double[] doubles = matrix.getArray();

                for (double d : doubles) {
                    buffer.putDouble(d);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return buffer == null ? null : buffer.array();
    }

    private static int calMatrixLen(@NonNull Matrix matrix) {
        int sum = 0;

        sum += 8;
        sum += (matrix.getArray().length << 3);

        return sum;
    }

    @Nullable
    private static Matrix[] byteArray2MatrixArray(byte... array) {
        if (array == null) {
            return null;
        }

        Matrix[] res = null;

        try {
            final ByteBuffer buffer = ByteBuffer.wrap(array);

            final int len = buffer.getInt();
            res = new Matrix[len];

            for (int i = 0; i < len; ++i) {
                final int row = buffer.getInt();
                final int col = buffer.getInt();
                final double[] doubles = new double[row * col];

                for (int j = 0, jLen = doubles.length; j < jLen; ++j) {
                    doubles[j] = buffer.getDouble();
                }

                res[i] = Matrix.array(doubles, row);
            }
        } catch (Exception e) {
            e.printStackTrace();

            res = null;
        }

        return res;
    }

    @Nullable
    private static int[] byteArray2IntArray(byte... array) {
        if (array == null) {
            return null;
        }

        final int[] res = new int[array.length >> 2];

        try {
            final IntBuffer buffer = ByteBuffer.wrap(array).asIntBuffer();

            for (int i = 0, iLen = res.length; i < iLen; ++i) {
                res[i] = buffer.get();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return res;
    }

    @Nullable
    private static double[] byteArray2DoubleArray(byte... array) {
        if (array == null) {
            return null;
        }

        final int len = array.length;
        final double[] res = new double[len >> 3];

        try {
            final DoubleBuffer buffer = ByteBuffer.wrap(array).asDoubleBuffer();

            for (int i = 0, iLen = len >> 3; i < iLen; ++i) {
                res[i] = buffer.get();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return res;
    }
} public class FileUtil {

    public static void clear(@NonNull File... files) {
        final StringBuilder builder = new StringBuilder();

        for (File file : files) {
            if (file.exists()) {
                builder.append(file.getAbsolutePath())
                        .append(' ');
            }
        }

        String res;

        if (!(res = builder.toString()).isEmpty()) {
            ProcessUtil.execCommand(String.format("rm -rf %s", res));
        }
    }

    public static void makeDirs(@NonNull File... dirs) {
        for (File dir : dirs) {
            if (!dir.exists()) {
                dir.mkdirs();
            }
        }
    }
} @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public class Precondition {
    public static void checkArgument(boolean expression) {
        if (!expression) {
            throw new IllegalArgumentException();
        }
    }

    /**
     * Ensures that an expression checking an argument is true.
     *
     * @param expression the expression to check
     * @param errorMessage the exception message to use if the check fails; will
     *     be converted to a string using {@link String#valueOf(Object)}
     * @throws IllegalArgumentException if {@code expression} is false
     */
    public static void checkArgument(boolean expression, final Object errorMessage) {
        if (!expression) {
            throw new IllegalArgumentException(String.valueOf(errorMessage));
        }
    }

    /**
     * Ensures that an string reference passed as a parameter to the calling
     * method is not empty.
     *
     * @param string an string reference
     * @return the string reference that was validated
     * @throws IllegalArgumentException if {@code string} is empty
     */
    public static @NonNull <T extends CharSequence> T checkStringNotEmpty(final T string) {
        if (TextUtils.isEmpty(string)) {
            throw new IllegalArgumentException();
        }
        return string;
    }

    /**
     * Ensures that an string reference passed as a parameter to the calling
     * method is not empty.
     *
     * @param string an string reference
     * @param errorMessage the exception message to use if the check fails; will
     *     be converted to a string using {@link String#valueOf(Object)}
     * @return the string reference that was validated
     * @throws IllegalArgumentException if {@code string} is empty
     */
    public static @NonNull <T extends CharSequence> T checkStringNotEmpty(final T string,
            final Object errorMessage) {
        if (TextUtils.isEmpty(string)) {
            throw new IllegalArgumentException(String.valueOf(errorMessage));
        }
        return string;
    }

    /**
     * Ensures that an object reference passed as a parameter to the calling
     * method is not null.
     *
     * @param reference an object reference
     * @return the non-null reference that was validated
     * @throws NullPointerException if {@code reference} is null
     */
    public static @NonNull <T> T checkNotNull(final T reference) {
        if (reference == null) {
            throw new NullPointerException();
        }
        return reference;
    }

    /**
     * Ensures that an object reference passed as a parameter to the calling
     * method is not null.
     *
     * @param reference an object reference
     * @param errorMessage the exception message to use if the check fails; will
     *     be converted to a string using {@link String#valueOf(Object)}
     * @return the non-null reference that was validated
     * @throws NullPointerException if {@code reference} is null
     */
    public static @NonNull <T> T checkNotNull(final T reference, final Object errorMessage) {
        if (reference == null) {
            throw new NullPointerException(String.valueOf(errorMessage));
        }
        return reference;
    }

    /**
     * Ensures the truth of an expression involving the state of the calling
     * instance, but not involving any parameters to the calling method.
     *
     * @param expression a boolean expression
     * @param message exception message
     * @throws IllegalStateException if {@code expression} is false
     */
    public static void checkState(final boolean expression, String message) {
        if (!expression) {
            throw new IllegalStateException(message);
        }
    }

    /**
     * Ensures the truth of an expression involving the state of the calling
     * instance, but not involving any parameters to the calling method.
     *
     * @param expression a boolean expression
     * @throws IllegalStateException if {@code expression} is false
     */
    public static void checkState(final boolean expression) {
        checkState(expression, null);
    }

    /**
     * Check the requested flags, throwing if any requested flags are outside
     * the allowed set.
     *
     * @return the validated requested flags.
     */
    public static int checkFlagsArgument(final int requestedFlags, final int allowedFlags) {
        if ((requestedFlags & allowedFlags) != requestedFlags) {
            throw new IllegalArgumentException("Requested flags 0x"
                    + Integer.toHexString(requestedFlags) + ", but only 0x"
                    + Integer.toHexString(allowedFlags) + " are allowed");
        }

        return requestedFlags;
    }

    /**
     * Ensures that that the argument numeric value is non-negative.
     *
     * @param value a numeric int value
     * @param errorMessage the exception message to use if the check fails
     * @return the validated numeric value
     * @throws IllegalArgumentException if {@code value} was negative
     */
    public static @IntRange(from = 0) int checkArgumentNonnegative(final int value,
            final String errorMessage) {
        if (value < 0) {
            throw new IllegalArgumentException(errorMessage);
        }

        return value;
    }

    /**
     * Ensures that that the argument numeric value is non-negative.
     *
     * @param value a numeric int value
     *
     * @return the validated numeric value
     * @throws IllegalArgumentException if {@code value} was negative
     */
    public static @IntRange(from = 0) int checkArgumentNonnegative(final int value) {
        if (value < 0) {
            throw new IllegalArgumentException();
        }

        return value;
    }

    /**
     * Ensures that that the argument numeric value is non-negative.
     *
     * @param value a numeric long value
     * @return the validated numeric value
     * @throws IllegalArgumentException if {@code value} was negative
     */
    public static long checkArgumentNonnegative(final long value) {
        if (value < 0) {
            throw new IllegalArgumentException();
        }

        return value;
    }

    /**
     * Ensures that that the argument numeric value is non-negative.
     *
     * @param value a numeric long value
     * @param errorMessage the exception message to use if the check fails
     * @return the validated numeric value
     * @throws IllegalArgumentException if {@code value} was negative
     */
    public static long checkArgumentNonnegative(final long value, final String errorMessage) {
        if (value < 0) {
            throw new IllegalArgumentException(errorMessage);
        }

        return value;
    }

    /**
     * Ensures that that the argument numeric value is positive.
     *
     * @param value a numeric int value
     * @param errorMessage the exception message to use if the check fails
     * @return the validated numeric value
     * @throws IllegalArgumentException if {@code value} was not positive
     */
    public static int checkArgumentPositive(final int value, final String errorMessage) {
        if (value <= 0) {
            throw new IllegalArgumentException(errorMessage);
        }

        return value;
    }

    /**
     * Ensures that the argument floating point value is a finite number.
     *
     * <p>A finite number is defined to be both representable (that is, not NaN) and
     * not infinite (that is neither positive or negative infinity).</p>
     *
     * @param value a floating point value
     * @param valueName the name of the argument to use if the check fails
     *
     * @return the validated floating point value
     *
     * @throws IllegalArgumentException if {@code value} was not finite
     */
    public static float checkArgumentFinite(final float value, final String valueName) {
        if (Float.isNaN(value)) {
            throw new IllegalArgumentException(valueName + " must not be NaN");
        } else if (Float.isInfinite(value)) {
            throw new IllegalArgumentException(valueName + " must not be infinite");
        }

        return value;
    }

    /**
     * Ensures that the argument floating point value is within the inclusive range.
     *
     * <p>While this can be used to range check against +/- infinity, note that all NaN numbers
     * will always be out of range.</p>
     *
     * @param value a floating point value
     * @param lower the lower endpoint of the inclusive range
     * @param upper the upper endpoint of the inclusive range
     * @param valueName the name of the argument to use if the check fails
     *
     * @return the validated floating point value
     *
     * @throws IllegalArgumentException if {@code value} was not within the range
     */
    public static float checkArgumentInRange(float value, float lower, float upper,
            String valueName) {
        if (Float.isNaN(value)) {
            throw new IllegalArgumentException(valueName + " must not be NaN");
        } else if (value < lower) {
            throw new IllegalArgumentException(
                    String.format(
                            "%s is out of range of [%f, %f] (too low)", valueName, lower, upper));
        } else if (value > upper) {
            throw new IllegalArgumentException(
                    String.format(
                            "%s is out of range of [%f, %f] (too high)", valueName, lower, upper));
        }

        return value;
    }

    /**
     * Ensures that the argument int value is within the inclusive range.
     *
     * @param value a int value
     * @param lower the lower endpoint of the inclusive range
     * @param upper the upper endpoint of the inclusive range
     * @param valueName the name of the argument to use if the check fails
     *
     * @return the validated int value
     *
     * @throws IllegalArgumentException if {@code value} was not within the range
     */
    public static int checkArgumentInRange(int value, int lower, int upper,
            String valueName) {
        if (value < lower) {
            throw new IllegalArgumentException(
                    String.format(
                            "%s is out of range of [%d, %d] (too low)", valueName, lower, upper));
        } else if (value > upper) {
            throw new IllegalArgumentException(
                    String.format(
                            "%s is out of range of [%d, %d] (too high)", valueName, lower, upper));
        }

        return value;
    }

    /**
     * Ensures that the argument long value is within the inclusive range.
     *
     * @param value a long value
     * @param lower the lower endpoint of the inclusive range
     * @param upper the upper endpoint of the inclusive range
     * @param valueName the name of the argument to use if the check fails
     *
     * @return the validated long value
     *
     * @throws IllegalArgumentException if {@code value} was not within the range
     */
    public static long checkArgumentInRange(long value, long lower, long upper,
            String valueName) {
        if (value < lower) {
            throw new IllegalArgumentException(
                    String.format(
                            "%s is out of range of [%d, %d] (too low)", valueName, lower, upper));
        } else if (value > upper) {
            throw new IllegalArgumentException(
                    String.format(
                            "%s is out of range of [%d, %d] (too high)", valueName, lower, upper));
        }

        return value;
    }

    /**
     * Ensures that the array is not {@code null}, and none of its elements are {@code null}.
     *
     * @param value an array of boxed objects
     * @param valueName the name of the argument to use if the check fails
     *
     * @return the validated array
     *
     * @throws NullPointerException if the {@code value} or any of its elements were {@code null}
     */
    public static <T> T[] checkArrayElementsNotNull(final T[] value, final String valueName) {
        if (value == null) {
            throw new NullPointerException(valueName + " must not be null");
        }

        for (int i = 0; i < value.length; ++i) {
            if (value[i] == null) {
                throw new NullPointerException(
                        String.format("%s[%d] must not be null", valueName, i));
            }
        }

        return value;
    }

    /**
     * Ensures that the {@link Collection} is not {@code null}, and none of its elements are
     * {@code null}.
     *
     * @param value a {@link Collection} of boxed objects
     * @param valueName the name of the argument to use if the check fails
     *
     * @return the validated {@link Collection}
     *
     * @throws NullPointerException if the {@code value} or any of its elements were {@code null}
     */
    public static @NonNull <C extends Collection<T>, T> C checkCollectionElementsNotNull(
            final C value, final String valueName) {
        if (value == null) {
            throw new NullPointerException(valueName + " must not be null");
        }

        long ctr = 0;
        for (T elem : value) {
            if (elem == null) {
                throw new NullPointerException(
                        String.format("%s[%d] must not be null", valueName, ctr));
            }
            ++ctr;
        }

        return value;
    }

    /**
     * Ensures that the {@link Collection} is not {@code null}, and contains at least one element.
     *
     * @param value a {@link Collection} of boxed elements.
     * @param valueName the name of the argument to use if the check fails.

     * @return the validated {@link Collection}
     *
     * @throws NullPointerException if the {@code value} was {@code null}
     * @throws IllegalArgumentException if the {@code value} was empty
     */
    public static <T> Collection<T> checkCollectionNotEmpty(final Collection<T> value,
            final String valueName) {
        if (value == null) {
            throw new NullPointerException(valueName + " must not be null");
        }
        if (value.isEmpty()) {
            throw new IllegalArgumentException(valueName + " is empty");
        }
        return value;
    }

    /**
     * Ensures that all elements in the argument floating point array are within the inclusive range
     *
     * <p>While this can be used to range check against +/- infinity, note that all NaN numbers
     * will always be out of range.</p>
     *
     * @param value a floating point array of values
     * @param lower the lower endpoint of the inclusive range
     * @param upper the upper endpoint of the inclusive range
     * @param valueName the name of the argument to use if the check fails
     *
     * @return the validated floating point value
     *
     * @throws IllegalArgumentException if any of the elements in {@code value} were out of range
     * @throws NullPointerException if the {@code value} was {@code null}
     */
    public static float[] checkArrayElementsInRange(float[] value, float lower, float upper,
            String valueName) {
        checkNotNull(value, valueName + " must not be null");

        for (int i = 0; i < value.length; ++i) {
            float v = value[i];

            if (Float.isNaN(v)) {
                throw new IllegalArgumentException(valueName + "[" + i + "] must not be NaN");
            } else if (v < lower) {
                throw new IllegalArgumentException(
                        String.format("%s[%d] is out of range of [%f, %f] (too low)",
                                valueName, i, lower, upper));
            } else if (v > upper) {
                throw new IllegalArgumentException(
                        String.format("%s[%d] is out of range of [%f, %f] (too high)",
                                valueName, i, lower, upper));
            }
        }

        return value;
    }
} public class StringFormatUtil {
    private static final String SPLIT_ITEM = ":";

    public static String formatTimeUsed(long timeUsed) {
        return String.format(Locale.getDefault(), "%02d:%02d:%02d",
                timeUsed / (3600000), timeUsed / (60000) % 60, timeUsed / 1000 % 60);
    }

    public static String formatPercent(double value) {
        return String.format(Locale.getDefault(),
                "%.2f%%", value * 100);
    }

    public static String formatLayerSizes(@NonNull int[] hiddenSizes) {
        final StringBuilder builder = new StringBuilder();
        final String spilt = " × ";

        builder.append(NeuralNetwork.INPUT_LAYER_NUMBER)
                .append(spilt);

        for (int size : hiddenSizes) {
            builder.append(size)
                    .append(spilt);
        }

        builder.append(NeuralNetwork.OUTPUT_LAYER_NUMBER);

        return builder.toString();
    }
} public class Versatile {
    private static final String DEMO_MODEL_FILE = "demo.model";

    /**
     * Solve TransitionManager leak problem
     */
    public static void removeActivityFromTransitionManager(Activity activity) {
        final Class transitionManagerClass = TransitionManager.class;

        try {
            final Field runningTransitionsField = transitionManagerClass.getDeclaredField("sRunningTransitions");

            if (runningTransitionsField == null) {
                return;
            }

            runningTransitionsField.setAccessible(true);

            //noinspection unchecked
            final ThreadLocal<WeakReference<ArrayMap<ViewGroup, ArrayList<Transition>>>> runningTransitions
                    = (ThreadLocal<WeakReference<ArrayMap<ViewGroup, ArrayList<Transition>>>>)
                    runningTransitionsField.get(transitionManagerClass);

            if (runningTransitions == null
                    || runningTransitions.get() == null
                    || runningTransitions.get().get() == null) {
                return;
            }

            final ArrayMap map = runningTransitions.get().get();
            final View decorView = activity.getWindow().getDecorView();

            if (map.containsKey(decorView)) {
                map.remove(decorView);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @SuppressWarnings("unused")
    public static void writeModel2File(@NonNull Model model) {
        ObjectOutputStream objectOutputStream = null;

        try {
            final File file = new File(Global.getInstance().getDirs().root, DEMO_MODEL_FILE);

            if (file.exists()) {
                file.delete();
            }

            final FileOutputStream outputStream = new FileOutputStream(file);
            objectOutputStream = new ObjectOutputStream(outputStream);

            objectOutputStream.writeObject(model);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (objectOutputStream != null) {
                try {
                    objectOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    @Nullable
    public static Model readModelFromAssert(@NonNull Context context) throws IOException, ClassNotFoundException {
        Precondition.checkNotNull(context);

        final AssetManager manager = context.getAssets();

        if (manager == null) {
            return  null;
        }

        ObjectInputStream objectInputStream = null;

        try {
            final InputStream inputStream = manager.open(DEMO_MODEL_FILE);
            objectInputStream = new ObjectInputStream(inputStream);

            return (Model) objectInputStream.readObject();
        } catch (IOException | ClassNotFoundException e) {
            throw e;
        } finally {
            if (objectInputStream != null) {
                try {
                    objectInputStream.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
import android.app.DownloadManager;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.database.Cursor;
import android.graphics.Color;
import android.net.Uri;
import android.os.IBinder;
import android.provider.Settings;
import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import android.support.v4.app.NotificationCompat;
import android.support.v4.content.ContextCompat;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import java.io.File;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import io.whz.synapse.R;
import io.whz.synapse.element.ChannelCreator;
import io.whz.synapse.element.Dir;
import io.whz.synapse.element.Global;
import io.whz.synapse.element.Scheduler;
import io.whz.synapse.neural.DataSet;
import io.whz.synapse.neural.MNISTUtil;
import io.whz.synapse.neural.NeuralNetwork;
import io.whz.synapse.neural.TrainCallback;
import io.whz.synapse.pojo.constant.PreferenceCons;
import io.whz.synapse.pojo.constant.TrackCons;
import io.whz.synapse.pojo.neural.Model;
import io.whz.synapse.pojo.event.MANEvent;
import io.whz.synapse.pojo.event.MSNEvent;
import io.whz.synapse.pojo.event.TrainEvent;
import io.whz.synapse.track.ExceptionHelper;
import io.whz.synapse.track.TimeHelper;
import io.whz.synapse.track.Tracker;
import io.whz.synapse.util.DbHelper;
import io.whz.synapse.util.FileUtil;
import io.whz.synapse.util.Precondition;
import io.whz.synapse.util.StringFormatUtil;
import io.whz.synapse.util.Versatile;
package io.whz.synapse.component;





public class MainService extends Service {
    private static final int RQC_DECOMPRESS = 0x01;
    private static final int RQC_TRAIN_START = 0x01 << 1;
    private static final int RQC_TRAIN_INTERRUPT = 0x01 << 2;

    private static final String TAG = App.TAG + "-MainService";

    public static final String EXTRAS_NEURAL_CONFIG = "extras_neural_config";
    public static final String ACTION_KEY = "action_key";

    private static final int IDLE = 0x00;
    public static final int ACTION_DOWNLOAD = 0x01;
    public static final int ACTION_TRAIN = 0x01 << 1;

    @Retention(RetentionPolicy.SOURCE)
    @IntDef({IDLE, ACTION_DOWNLOAD, ACTION_TRAIN})
    private @interface Action {}

    @Action
    private int mAction = IDLE;

    private BroadcastReceiver mDownloadReceiver;
    private AtomicBoolean mTrainInterruptSign;
    private NotificationCompat.Builder mTrainNotifyBuilder;
    private NotificationManager mNotifyManager;

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        @Action final int action = intent.getIntExtra(ACTION_KEY, -1);

        if (isIdle()) {
            switch (action) {
                case ACTION_DOWNLOAD:
                    downloadFiles();
                    checkAndAddDemoModel();
                    break;

                case ACTION_TRAIN:
                    handleTraining(intent);
                    break;

                case IDLE:
                default:
                    throw new UnsupportedOperationException();
            }

            mAction = action;
        }

        return super.onStartCommand(intent, flags, startId);
    }

    private void checkAndAddDemoModel() {
        final boolean shouldAdd = Global.getInstance()
                .getPreference().getBoolean(PreferenceCons.SHOULD_ADD_DEMO_MODEL, true);

        if (shouldAdd) {
            Scheduler.Secondary.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        final Model model = Versatile.readModelFromAssert(MainService.this);

                        if (model != null) {
                            saveModel(model);
                        }
                    } catch (Exception e) {
                        ExceptionHelper.getInstance()
                                .caught(e);
                    }
                }
            });

            Global.getInstance()
                    .getPreference()
                    .edit()
                    .putBoolean(PreferenceCons.SHOULD_ADD_DEMO_MODEL, false)
                    .apply();
        }
    }

    private void handleTraining(@NonNull Intent intent) {
        final Model model = (Model) intent.getSerializableExtra(EXTRAS_NEURAL_CONFIG);
        Precondition.checkNotNull(model);

        interruptTraining();

        mTrainInterruptSign = new AtomicBoolean(false);
        Scheduler.Secondary.execute(new TrainRunnable(model, mTrainInterruptSign));
    }

    private void reset() {
        stopForeground(true);
        mAction = IDLE;
        stopSelf();
    }

    private boolean isIdle() {
        final String rep;

        switch (mAction) {
            case IDLE:
                return true;
                
            case ACTION_DOWNLOAD:
                rep = getString(R.string.text_wait_for_download);
                break;

            case ACTION_TRAIN:
                rep = getString(R.string.text_wait_for_train);
                break;

            default:
                return false;
        }

        sendRejectMsg(rep);
        return false;
    }

    private void sendRejectMsg(@NonNull String msg) {
        Global.getInstance()
                .getBus()
                .post(new MANEvent<>(MANEvent.REJECT_MSG, msg));