chromium/components/browser_ui/util/android/java/src/org/chromium/components/browser_ui/util/DownloadUtils.java

// Copyright 2020 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.components.browser_ui.util;

import android.app.DownloadManager;
import android.content.Context;
import android.net.Uri;
import android.os.Build;
import android.text.TextUtils;

import androidx.core.app.NotificationManagerCompat;

import org.chromium.base.ContextUtils;
import org.chromium.base.ThreadUtils;
import org.chromium.components.embedder_support.util.UrlConstants;
import org.chromium.components.embedder_support.util.UrlUtilities;
import org.chromium.components.url_formatter.SchemeDisplay;
import org.chromium.components.url_formatter.UrlFormatter;
import org.chromium.url.GURL;

/** A class containing some utility static methods. */
public class DownloadUtils {
    public static final long INVALID_SYSTEM_DOWNLOAD_ID = -1;
    private static final int[] BYTES_STRINGS = {
        R.string.download_ui_kb, R.string.download_ui_mb, R.string.download_ui_gb
    };

    // Limit the origin length so that the eTLD+1 cannot be hidden. If the origin exceeds this
    // length the eTLD+1 is extracted and shown.
    public static final int MAX_ORIGIN_LENGTH_FOR_NOTIFICATION = 40;
    public static final int MAX_ORIGIN_LENGTH_FOR_DOWNLOAD_HOME_CAPTION = 25;

    /**
     * Format the number of bytes into KB, MB, or GB and return the corresponding generated string.
     * @param context Context to use.
     * @param bytes   Number of bytes needed to display.
     * @return        The formatted string to be displayed.
     */
    public static String getStringForBytes(Context context, long bytes) {
        return getStringForBytes(context, BYTES_STRINGS, bytes);
    }

    /**
     * Format the number of bytes into KB, or MB, or GB and return the corresponding string
     * resource.
     * @param context Context to use.
     * @param stringSet The string resources for displaying bytes in KB, MB and GB.
     * @param bytes Number of bytes.
     * @return A formatted string to be displayed.
     */
    public static String getStringForBytes(Context context, int[] stringSet, long bytes) {
        int resourceId;
        float bytesInCorrectUnits;

        if (ConversionUtils.bytesToMegabytes(bytes) < 1) {
            resourceId = stringSet[0];
            bytesInCorrectUnits = bytes / (float) ConversionUtils.BYTES_PER_KILOBYTE;
        } else if (ConversionUtils.bytesToGigabytes(bytes) < 1) {
            resourceId = stringSet[1];
            bytesInCorrectUnits = bytes / (float) ConversionUtils.BYTES_PER_MEGABYTE;
        } else {
            resourceId = stringSet[2];
            bytesInCorrectUnits = bytes / (float) ConversionUtils.BYTES_PER_GIGABYTE;
        }

        return context.getResources().getString(resourceId, bytesInCorrectUnits);
    }

    /**
     * Adds a download to the Android DownloadManager.
     * @see android.app.DownloadManager#addCompletedDownload(String, String, boolean, String,
     * String, long, boolean)
     */
    public static long addCompletedDownload(
            String fileName,
            String description,
            String mimeType,
            String filePath,
            long fileSizeBytes,
            GURL originalUrl,
            GURL referer) {
        assert !ThreadUtils.runningOnUiThread();
        assert Build.VERSION.SDK_INT < Build.VERSION_CODES.Q
                : "addCompletedDownload is deprecated in Q, may cause crash.";
        Context context = ContextUtils.getApplicationContext();
        DownloadManager manager =
                (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
        NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
        boolean useSystemNotification = !notificationManager.areNotificationsEnabled();
        try {
            // OriginalUri has to be null or non-empty http(s) scheme.
            Uri originalUri = parseOriginalUrl(originalUrl.getSpec());
            Uri refererUri = GURL.isEmptyOrInvalid(referer) ? null : Uri.parse(referer.getSpec());
            return manager.addCompletedDownload(
                    fileName,
                    description,
                    true,
                    mimeType,
                    filePath,
                    fileSizeBytes,
                    useSystemNotification,
                    originalUri,
                    refererUri);
        } catch (Exception e) {
            return INVALID_SYSTEM_DOWNLOAD_ID;
        }
    }

    /**
     * Parses an originating URL string and returns a valid Uri that can be inserted into
     * DownloadManager. The returned Uri has to be null or non-empty http(s) scheme.
     * @param originalUrl String representation of the originating URL.
     * @return A valid Uri that can be accepted by DownloadManager.
     */
    public static Uri parseOriginalUrl(String originalUrl) {
        Uri originalUri = TextUtils.isEmpty(originalUrl) ? null : Uri.parse(originalUrl);
        if (originalUri != null) {
            String scheme = originalUri.normalizeScheme().getScheme();
            if (scheme == null
                    || (!scheme.equals(UrlConstants.HTTPS_SCHEME)
                            && !scheme.equals(UrlConstants.HTTP_SCHEME))) {
                originalUri = null;
            }
        }
        return originalUri;
    }

    /**
     * Adjusts a URL for display to the user in a text view subject to char limits. Could elide
     * parts the URL if it is too long as per readability and security aspects.
     *
     * @param url The full URL.
     * @param limit Character limit.
     * @return The text to display, or null if the input was invalid.
     */
    public static String formatUrlForDisplayInNotification(GURL url, int limit) {
        if (GURL.isEmptyOrInvalid(url)) return null;

        String formattedUrl =
                UrlFormatter.formatUrlForSecurityDisplay(url, SchemeDisplay.OMIT_HTTP_AND_HTTPS);
        if (formattedUrl.length() <= limit) return formattedUrl;

        // The origin is too long. Strip down to eTLD+1.
        return UrlUtilities.getDomainAndRegistry(
                url.getSpec(), /* includePrivateRegistries= */ false);
    }
}