chromium/ui/android/java/src/org/chromium/ui/base/MimeTypeUtils.java

// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.ui.base;

import android.Manifest;
import android.os.Build;
import android.webkit.MimeTypeMap;

import androidx.annotation.IntDef;
import androidx.annotation.Nullable;

import org.chromium.base.BuildInfo;
import org.chromium.url.GURL;

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

/** Utility methods for determining and working with mime types. */
public class MimeTypeUtils {
    /** The MIME type for a plain text objects dragged from Chrome. */
    public static final String CHROME_MIMETYPE_TEXT = "chrome/text";

    /** The MIME type for a link objects dragged from Chrome. */
    public static final String CHROME_MIMETYPE_LINK = "chrome/link";

    /** The MIME type for a tab object dragged from Chrome. */
    public static final String CHROME_MIMETYPE_TAB = "chrome/tab";

    /** The MIME type for text. */
    public static final String TEXT_MIME_TYPE = "text/plain";

    /** The MIME type for an image. */
    public static final String IMAGE_MIME_TYPE = "image/*";

    /** The MIME type for pdf. */
    public static final String PDF_MIME_TYPE = "application/pdf";

    /** A set of known mime types. */
    // Note: these values must match the AndroidUtilsMimeTypes enum in enums.xml.
    // Only add new values at the end, right before NUM_ENTRIES. We depend on these specific
    // values in UMA histograms.
    @IntDef({Type.UNKNOWN, Type.TEXT, Type.IMAGE, Type.AUDIO, Type.VIDEO, Type.PDF})
    @Retention(RetentionPolicy.SOURCE)
    public @interface Type {
        int UNKNOWN = 0;
        int TEXT = 1;
        int IMAGE = 2;
        int AUDIO = 3;
        int VIDEO = 4;
        int PDF = 5;
    }

    /** The number of entries in {@link Type}. */
    public static final int NUM_MIME_TYPE_ENTRIES = 6;

    /**
     * @param url A {@link GURL} for which to determine the mime type.
     * @return The mime type, based on the extension of the {@code url}.
     */
    public static @Type int getMimeTypeForUrl(GURL url) {
        String extension = MimeTypeMap.getFileExtensionFromUrl(url.getSpec());
        @Type int mimeType = Type.UNKNOWN;
        if (extension != null) {
            String type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
            if (type != null) {
                if (type.startsWith("text")) {
                    mimeType = Type.TEXT;
                } else if (type.startsWith("image")) {
                    mimeType = Type.IMAGE;
                } else if (type.startsWith("audio")) {
                    mimeType = Type.AUDIO;
                } else if (type.startsWith("video")) {
                    mimeType = Type.VIDEO;
                } else if (type.equals("application/pdf")) {
                    mimeType = Type.PDF;
                }
            }
        }

        return mimeType;
    }

    /**
     * @param mimeType The mime type associated with an operation that needs a permission.
     * @return The name of the Android permission to request. Returns null if no permission will
     *         allow access to the file, for example on Android T+ where READ_EXTERNAL_STORAGE has
     *         been replaced with a handful of READ_MEDIA_* permissions.
     */
    public @Nullable static String getPermissionNameForMimeType(@MimeTypeUtils.Type int mimeType) {
        if (useExternalStoragePermission()) {
            return Manifest.permission.READ_EXTERNAL_STORAGE;
        }

        switch (mimeType) {
            case MimeTypeUtils.Type.AUDIO:
                return Manifest.permission.READ_MEDIA_AUDIO;
            case MimeTypeUtils.Type.IMAGE:
                return Manifest.permission.READ_MEDIA_IMAGES;
            case MimeTypeUtils.Type.VIDEO:
                return Manifest.permission.READ_MEDIA_VIDEO;
            default:
                return null;
        }
    }

    static boolean useExternalStoragePermission() {
        // Extracted into a helper method for easy testing. Can be replaced with test annotations
        // once Robolectric recognizes SDK = T.
        return Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU || !BuildInfo.targetsAtLeastT();
    }
}