android.support.text.emoji.MetadataRepo.java Source code

Java tutorial

Introduction

Here is the source code for android.support.text.emoji.MetadataRepo.java

Source

/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package android.support.text.emoji;

import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;

import android.content.res.AssetManager;
import android.graphics.Typeface;
import android.support.annotation.AnyThread;
import android.support.annotation.NonNull;
import android.support.annotation.RequiresApi;
import android.support.annotation.RestrictTo;
import android.support.annotation.VisibleForTesting;
import android.support.text.emoji.flatbuffer.MetadataList;
import android.support.v4.util.Preconditions;
import android.util.SparseArray;

import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;

/**
 * Class to hold the emoji metadata required to process and draw emojis.
 */
@AnyThread
@RequiresApi(19)
public final class MetadataRepo {
    /**
     * The default children size of the root node.
     */
    private static final int DEFAULT_ROOT_SIZE = 1024;

    /**
     * MetadataList that contains the emoji metadata.
     */
    private final MetadataList mMetadataList;

    /**
     * char presentation of all EmojiMetadata's in a single array. All emojis we have are mapped to
     * Private Use Area A, in the range U+F0000..U+FFFFD. Therefore each emoji takes 2 chars.
     */
    private final char[] mEmojiCharArray;

    /**
     * Empty root node of the trie.
     */
    private final Node mRootNode;

    /**
     * Typeface to be used to render emojis.
     */
    private final Typeface mTypeface;

    /**
     * Constructor used for tests.
     *
     * @hide
     */
    @RestrictTo(LIBRARY_GROUP)
    MetadataRepo() {
        mTypeface = null;
        mMetadataList = null;
        mRootNode = new Node(DEFAULT_ROOT_SIZE);
        mEmojiCharArray = new char[0];
    }

    /**
     * Private constructor that is called by one of {@code create} methods.
     *
     * @param typeface Typeface to be used to render emojis
     * @param metadataList MetadataList that contains the emoji metadata
     */
    private MetadataRepo(@NonNull final Typeface typeface, @NonNull final MetadataList metadataList) {
        mTypeface = typeface;
        mMetadataList = metadataList;
        mRootNode = new Node(DEFAULT_ROOT_SIZE);
        mEmojiCharArray = new char[mMetadataList.listLength() * 2];
        constructIndex(mMetadataList);
    }

    /**
     * Construct MetadataRepo from an input stream. The library does not close the given
     * InputStream, therefore it is caller's responsibility to properly close the stream.
     *
     * @param typeface Typeface to be used to render emojis
     * @param inputStream InputStream to read emoji metadata from
     */
    public static MetadataRepo create(@NonNull final Typeface typeface, @NonNull final InputStream inputStream)
            throws IOException {
        return new MetadataRepo(typeface, MetadataListReader.read(inputStream));
    }

    /**
     * Construct MetadataRepo from a byte buffer. The position of the ByteBuffer will change, it is
     * caller's responsibility to reposition the buffer if required.
     *
     * @param typeface Typeface to be used to render emojis
     * @param byteBuffer ByteBuffer to read emoji metadata from
     */
    public static MetadataRepo create(@NonNull final Typeface typeface, @NonNull final ByteBuffer byteBuffer)
            throws IOException {
        return new MetadataRepo(typeface, MetadataListReader.read(byteBuffer));
    }

    /**
     * Construct MetadataRepo from an asset.
     *
     * @param assetManager AssetManager instance
     * @param assetPath asset manager path of the file that the Typeface and metadata will be
     *                  created from
     */
    public static MetadataRepo create(@NonNull final AssetManager assetManager, final String assetPath)
            throws IOException {
        final Typeface typeface = Typeface.createFromAsset(assetManager, assetPath);
        return new MetadataRepo(typeface, MetadataListReader.read(assetManager, assetPath));
    }

    /**
     * Read emoji metadata list and construct the trie.
     */
    private void constructIndex(final MetadataList metadataList) {
        int length = metadataList.listLength();
        for (int i = 0; i < length; i++) {
            final EmojiMetadata metadata = new EmojiMetadata(this, i);
            //since all emojis are mapped to a single codepoint in Private Use Area A they are 2
            //chars wide
            //noinspection ResultOfMethodCallIgnored
            Character.toChars(metadata.getId(), mEmojiCharArray, i * 2);
            put(metadata);
        }
    }

    /**
     * @hide
     */
    @RestrictTo(LIBRARY_GROUP)
    Typeface getTypeface() {
        return mTypeface;
    }

    /**
     * @hide
     */
    @RestrictTo(LIBRARY_GROUP)
    int getMetadataVersion() {
        return mMetadataList.version();
    }

    /**
     * @hide
     */
    @RestrictTo(LIBRARY_GROUP)
    Node getRootNode() {
        return mRootNode;
    }

    /**
     * @hide
     */
    @RestrictTo(LIBRARY_GROUP)
    public char[] getEmojiCharArray() {
        return mEmojiCharArray;
    }

    /**
     * @hide
     */
    @RestrictTo(LIBRARY_GROUP)
    public MetadataList getMetadataList() {
        return mMetadataList;
    }

    /**
     * Add an EmojiMetadata to the index.
     *
     * @hide
     */
    @RestrictTo(LIBRARY_GROUP)
    @VisibleForTesting
    void put(@NonNull final EmojiMetadata data) {
        Preconditions.checkNotNull(data, "emoji metadata cannot be null");
        Preconditions.checkArgument(data.getCodepointsLength() > 0, "invalid metadata codepoint length");

        mRootNode.put(data, 0, data.getCodepointsLength() - 1);
    }

    /**
     * Trie node that holds mapping from emoji codepoint(s) to EmojiMetadata. A single codepoint
     * emoji is represented by a child of the root node.
     *
     * @hide
     */
    @RestrictTo(LIBRARY_GROUP)
    static class Node {
        private final SparseArray<Node> mChildren;
        private EmojiMetadata mData;

        private Node() {
            this(1);
        }

        private Node(final int defaultChildrenSize) {
            mChildren = new SparseArray<>(defaultChildrenSize);
        }

        Node get(final int key) {
            return mChildren == null ? null : mChildren.get(key);
        }

        final EmojiMetadata getData() {
            return mData;
        }

        private void put(@NonNull final EmojiMetadata data, final int start, final int end) {
            Node node = get(data.getCodepointAt(start));
            if (node == null) {
                node = new Node();
                mChildren.put(data.getCodepointAt(start), node);
            }

            if (end > start) {
                node.put(data, start + 1, end);
            } else {
                node.mData = data;
            }
        }
    }
}