public final class

DefaultEmojiCompatConfig

extends java.lang.Object

 java.lang.Object

↳androidx.emoji2.text.DefaultEmojiCompatConfig

Gradle dependencies

compile group: 'androidx.emoji2', name: 'emoji2', version: '1.2.0-alpha04'

  • groupId: androidx.emoji2
  • artifactId: emoji2
  • version: 1.2.0-alpha04

Artifact androidx.emoji2:emoji2:1.2.0-alpha04 it located at Google repository (https://maven.google.com/)

Overview

The default config will use downloadable fonts to fetch the emoji compat font file.

It will automatically fetch the emoji compat font from a ContentProvider that is installed on the devices system image, if present.

You should use this if you want the default emoji font from a system installed downloadable fonts provider. This is the recommended behavior for all applications unless they install a custom emoji font.

You may need to specialize the configuration beyond this default config in some situations:

  • If you are trying to install a custom emoji font through downloadable fonts use FontRequestEmojiCompatConfig instead of this method.
  • If you're trying to bundle an emoji font with your APK use BundledEmojiCompatConfig in the emoji2-bundled artifact.
  • If you are building an APK that will be installed on devices that won't have a downloadable fonts provider, use BundledEmojiCompatConfig.

The downloadable font provider used by DefaultEmojiCompatConfig always satisfies the following contract:

  1. It MUST provide an intent filter for androidx.content.action.LOAD_EMOJI_FONT.
  2. It MUST respond to the query emojicompat-emoji-font with a valid emoji compat font file including metadata.
  3. It MUST provide fonts via the same contract as downloadable fonts.
  4. It MUST be installed in the system image.

Summary

Methods
public static FontRequestEmojiCompatConfigcreate(Context context)

Get the default emoji compat config for this device.

from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Methods

public static FontRequestEmojiCompatConfig create(Context context)

Get the default emoji compat config for this device. You may further configure the returned config before passing it to EmojiCompat.init(Context). Each call to this method will return a new EmojiCompat.Config, so changes to the returned object will not modify future return values.

Parameters:

context: context for lookup

Returns:

A valid config for downloading the emoji compat font, or null if no font provider could be found.

Source

/*
 * Copyright 2021 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 androidx.emoji2.text;

import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ProviderInfo;
import android.content.pm.ResolveInfo;
import android.content.pm.Signature;
import android.os.Build;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
import androidx.annotation.VisibleForTesting;
import androidx.core.provider.FontRequest;
import androidx.core.util.Preconditions;

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

/**
 * The default config will use downloadable fonts to fetch the emoji compat font file.
 *
 * <p>It will automatically fetch the emoji compat font from a {@code ContentProvider} that is
 * installed on the devices system image, if present.</p>
 *
 * <p>You should use this if you want the default emoji font from a system installed
 * downloadable fonts provider. This is the recommended behavior for all applications unless
 * they install a custom emoji font.</p>
 *
 * <p>You may need to specialize the configuration beyond this default config in some
 * situations:</p>
 * <ul>
 *  <li>If you are trying to install a custom emoji font through downloadable fonts use
 *  {@link FontRequestEmojiCompatConfig} instead of this method.</li>
 *  <li>If you're trying to bundle an emoji font with your APK use {@code
 *  BundledEmojiCompatConfig} in the {@code emoji2-bundled} artifact.</li>
 *  <li>If you are building an APK that will be installed on devices that won't have a
 *  downloadable fonts provider, use {@code BundledEmojiCompatConfig}.</li>
 * </ul>
 *
 * <p>The downloadable font provider used by {@code DefaultEmojiCompatConfig} always satisfies
 * the following contract:</p>
 * <ol>
 *  <li>It <i>MUST</i> provide an intent filter for {@code androidx.content.action.LOAD_EMOJI_FONT}.
 *  </li>
 *  <li>It <i>MUST</i> respond to the query {@code emojicompat-emoji-font} with a valid emoji compat
 *  font file including metadata.</li>
 *  <li>It <i>MUST</i> provide fonts via the same contract as downloadable fonts.</li>
 *  <li>It <i>MUST</i> be installed in the system image.</li>
 * </ol>
 */
public final class DefaultEmojiCompatConfig {
    /**
     * This class cannot be instantiated.
     *
     * @see DefaultEmojiCompatConfig#create
     */
    private DefaultEmojiCompatConfig() {
    }

    /**
     * Get the default emoji compat config for this device.
     *
     * You may further configure the returned config before passing it to {@link EmojiCompat#init}.
     *
     * Each call to this method will return a new EmojiCompat.Config, so changes to the returned
     * object will not modify future return values.
     *
     * @param context context for lookup
     * @return A valid config for downloading the emoji compat font, or null if no font provider
     * could be found.
     */
    @Nullable
    public static FontRequestEmojiCompatConfig create(@NonNull Context context) {
        return (FontRequestEmojiCompatConfig) new DefaultEmojiCompatConfigFactory(null)
                .create(context);
    }

    /**
     * Actual factory for generating default emoji configs, does service locator lookup internally.
     *
     * @see DefaultEmojiCompatConfig#create
     * @hide
     */
    @RestrictTo(RestrictTo.Scope.LIBRARY)
    public static class DefaultEmojiCompatConfigFactory {
        private static final @NonNull String TAG = "emoji2.text.DefaultEmojiConfig";
        private static final @NonNull String INTENT_LOAD_EMOJI_FONT =
                "androidx.content.action.LOAD_EMOJI_FONT";
        private static final @NonNull String DEFAULT_EMOJI_QUERY = "emojicompat-emoji-font";
        private final DefaultEmojiCompatConfigHelper mHelper;

        /**
         * @hide
         */
        @RestrictTo(RestrictTo.Scope.LIBRARY)
        public DefaultEmojiCompatConfigFactory(@Nullable DefaultEmojiCompatConfigHelper helper) {
            mHelper = helper != null ? helper : getHelperForApi();
        }

        /**
         * @see DefaultEmojiCompatConfig#create
         * @hide
         */
        @RestrictTo(RestrictTo.Scope.LIBRARY)
        @Nullable
        public EmojiCompat.Config create(@NonNull Context context) {
            return configOrNull(context, (FontRequest) queryForDefaultFontRequest(context));
        }

        /**
         * Create a new Config if fontRequest is not null
         * @param context context for the config
         * @param fontRequest optional font request
         * @return a new config if fontRequest is not null
         */
        @Nullable
        private EmojiCompat.Config configOrNull(@NonNull Context context,
                @Nullable FontRequest fontRequest) {
            if (fontRequest == null) {
                return null;
            } else {
                return new FontRequestEmojiCompatConfig(context, fontRequest);
            }
        }

        /**
         * Find the installed font provider and return a FontInfo that describes it.
         * @param context context for getting package manager
         * @return valid FontRequest, or null if no provider could be found
         * @hide
         */
        @RestrictTo(RestrictTo.Scope.LIBRARY)
        @Nullable
        @VisibleForTesting
        FontRequest queryForDefaultFontRequest(@NonNull Context context) {
            PackageManager packageManager = context.getPackageManager();
            // throw here since the developer has provided an atypical Context
            Preconditions.checkNotNull(packageManager,
                    "Package manager required to locate emoji font provider");
            ProviderInfo providerInfo = queryDefaultInstalledContentProvider(packageManager);
            if (providerInfo == null) return null;

            try {
                return generateFontRequestFrom(providerInfo, packageManager);
            } catch (PackageManager.NameNotFoundException e) {
                Log.wtf(TAG, e);
                return null;
            }
        }

        /**
         * Look up a ContentProvider that provides emoji fonts that's installed with the system.
         *
         * @param packageManager package manager from a Context
         * @return a ResolveInfo for a system installed content provider, or null if none found
         */
        @Nullable
        private ProviderInfo queryDefaultInstalledContentProvider(
                @NonNull PackageManager packageManager) {
            List<ResolveInfo> providers = mHelper.queryIntentContentProviders(packageManager,
                    new Intent(INTENT_LOAD_EMOJI_FONT), 0);

            for (ResolveInfo resolveInfo : providers) {
                ProviderInfo providerInfo = mHelper.getProviderInfo(resolveInfo);
                if (hasFlagSystem(providerInfo)) {
                    return providerInfo;
                }
            }
            return null;
        }

        /**
         * @param providerInfo optional ProviderInfo that describes a content provider
         * @return true if this provider info is from an application with
         * {@link ApplicationInfo#FLAG_SYSTEM}
         */
        private boolean hasFlagSystem(@Nullable ProviderInfo providerInfo) {
            return providerInfo != null
                    && providerInfo.applicationInfo != null
                    && (providerInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM)
                    == ApplicationInfo.FLAG_SYSTEM;
        }

        /**
         * Generate a full FontRequest from a ResolveInfo that describes a ContentProvider.
         *
         * @param providerInfo description of content provider to generate a FontRequest from
         * @return a valid font request
         * @throws NullPointerException if the passed resolveInfo has a null providerInfo.
         */
        @NonNull
        private FontRequest generateFontRequestFrom(
                @NonNull ProviderInfo providerInfo,
                @NonNull PackageManager packageManager
        ) throws PackageManager.NameNotFoundException {
            String providerAuthority = providerInfo.authority;
            String providerPackage = providerInfo.packageName;

            Signature[] signingSignatures = mHelper.getSigningSignatures(packageManager,
                    providerPackage);
            List<List<byte[]>> signatures = convertToByteArray(signingSignatures);
            return new FontRequest(providerAuthority, providerPackage, DEFAULT_EMOJI_QUERY,
                    signatures);
        }

        /**
         * Convert signatures into a form usable by a FontConfig
         */
        @NonNull
        private List<List<byte[]>> convertToByteArray(@NonNull Signature[] signatures) {
            List<byte[]> shaList = new ArrayList<>();
            for (Signature signature : signatures) {
                shaList.add(signature.toByteArray());
            }
            return Collections.singletonList(shaList);
        }

        /**
         * @return the right DefaultEmojiCompatConfigHelper for the device API
         */
        @NonNull
        private static DefaultEmojiCompatConfigHelper getHelperForApi() {
            if (Build.VERSION.SDK_INT >= 28) {
                return new DefaultEmojiCompatConfigHelper_API28();
            } else if (Build.VERSION.SDK_INT >= 19) {
                return new DefaultEmojiCompatConfigHelper_API19();
            } else {
                return new DefaultEmojiCompatConfigHelper();
            }
        }
    }

    /**
     * Helper to lookup signatures in package manager.
     *
     * @hide
     */
    @RestrictTo(RestrictTo.Scope.LIBRARY)
    public static class DefaultEmojiCompatConfigHelper {
        /**
         * Get the signing signatures for a package in package manager.
         */
        @SuppressWarnings("deprecation") // replaced in API 28
        @NonNull
        public Signature[] getSigningSignatures(@NonNull PackageManager packageManager,
                @NonNull String providerPackage) throws PackageManager.NameNotFoundException {
            PackageInfo packageInfoForSignatures = packageManager.getPackageInfo(providerPackage,
                    PackageManager.GET_SIGNATURES);
            return packageInfoForSignatures.signatures;
        }

        /**
         * Get the content provider by intent.
         */
        @NonNull
        public List<ResolveInfo> queryIntentContentProviders(@NonNull PackageManager packageManager,
                @NonNull Intent intent, int flags) {
            return Collections.emptyList();
        }

        /**
         * Get a ProviderInfo, if present, from a ResolveInfo
         * @param resolveInfo the subject
         * @return resolveInfo.providerInfo above API 19
         */
        @Nullable
        public ProviderInfo getProviderInfo(@NonNull ResolveInfo resolveInfo) {
            throw new IllegalStateException("Unable to get provider info prior to API 19");
        }
    }

    /**
     * Actually do lookups > API 19
     *
     * @hide
     */
    @RestrictTo(RestrictTo.Scope.LIBRARY)
    @RequiresApi(19)
    public static class DefaultEmojiCompatConfigHelper_API19
            extends DefaultEmojiCompatConfigHelper {
        @NonNull
        @Override
        @SuppressWarnings("deprecation")
        public List<ResolveInfo> queryIntentContentProviders(@NonNull PackageManager packageManager,
                @NonNull Intent intent, int flags) {
            return packageManager.queryIntentContentProviders(intent, flags);
        }

        @Nullable
        @Override
        public ProviderInfo getProviderInfo(@NonNull ResolveInfo resolveInfo) {
            return resolveInfo.providerInfo;
        }
    }

    /**
     * Helper to lookup signatures in package manager > API 28
     * @hide
     */
    @RestrictTo(RestrictTo.Scope.LIBRARY)
    @RequiresApi(28)
    public static class DefaultEmojiCompatConfigHelper_API28
            extends DefaultEmojiCompatConfigHelper_API19 {
        @SuppressWarnings("deprecation") // using deprecated API to match exact behavior in core
        @Override
        @NonNull
        public Signature[] getSigningSignatures(@NonNull PackageManager packageManager,
                @NonNull String providerPackage)
                throws PackageManager.NameNotFoundException {
            // This uses the deprecated GET_SIGNATURES currently to match the behavior in Core.
            // When that behavior changes, we will need to update this method.

            // Alternatively, you may at that time introduce a new config option that allows
            // skipping signature validations to avoid this code sync.
            PackageInfo packageInfoForSignatures = packageManager.getPackageInfo(providerPackage,
                    PackageManager.GET_SIGNATURES);
            return packageInfoForSignatures.signatures;
        }
    }
}