Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2ec88c40 authored by android-build-team Robot's avatar android-build-team Robot Committed by Android (Google) Code Review
Browse files

Merge "Allow to influence how loadSafeLabel works"

parents 68589db7 a6f5c70b
Loading
Loading
Loading
Loading
+5 −1
Original line number Diff line number Diff line
@@ -1062,7 +1062,11 @@ package android.content.pm {
  }

  public class PackageItemInfo {
    method public java.lang.CharSequence loadSafeLabel(android.content.pm.PackageManager);
    method public deprecated java.lang.CharSequence loadSafeLabel(android.content.pm.PackageManager);
    method public java.lang.CharSequence loadSafeLabel(android.content.pm.PackageManager, float, int);
    field public static final int SAFE_LABEL_FLAG_FIRST_LINE = 4; // 0x4
    field public static final int SAFE_LABEL_FLAG_SINGLE_LINE = 2; // 0x2
    field public static final int SAFE_LABEL_FLAG_TRIM = 1; // 0x1
  }

  public abstract class PackageManager {
+261 −46
Original line number Diff line number Diff line
@@ -16,6 +16,10 @@

package android.content.pm;

import static java.lang.annotation.RetentionPolicy.SOURCE;

import android.annotation.FloatRange;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.content.res.XmlResourceParser;
@@ -29,7 +33,11 @@ import android.text.TextUtils;
import android.util.Printer;
import android.util.proto.ProtoOutputStream;

import com.android.internal.util.Preconditions;

import java.lang.annotation.Retention;
import java.text.Collator;
import java.util.BitSet;
import java.util.Comparator;

/**
@@ -42,10 +50,56 @@ import java.util.Comparator;
 * in the implementation of Parcelable in subclasses.
 */
public class PackageItemInfo {
    private static final float MAX_LABEL_SIZE_PX = 500f;
    private static final int LINE_FEED_CODE_POINT = 10;
    private static final int NBSP_CODE_POINT = 160;

    /** The maximum length of a safe label, in characters */
    private static final int MAX_SAFE_LABEL_LENGTH = 50000;

    /** @hide */
    public static final float DEFAULT_MAX_LABEL_SIZE_PX = 500f;

    /**
     * Flags for {@link #loadSafeLabel(PackageManager, float, int)}
     *
     * @hide
     */
    @Retention(SOURCE)
    @IntDef(flag = true, prefix = "SAFE_LABEL_FLAG_",
            value = {SAFE_LABEL_FLAG_TRIM, SAFE_LABEL_FLAG_SINGLE_LINE,
                    SAFE_LABEL_FLAG_FIRST_LINE})
    public @interface SafeLabelFlags {}

    /**
     * Remove {@link Character#isWhitespace(int) whitespace} and non-breaking spaces from the edges
     * of the label.
     *
     * @see #loadSafeLabel(PackageManager, float, int)
     * @hide
     */
    @SystemApi
    public static final int SAFE_LABEL_FLAG_TRIM = 0x1;

    /**
     * Force entire string into single line of text (no newlines). Cannot be set at the same time as
     * {@link #SAFE_LABEL_FLAG_FIRST_LINE}.
     *
     * @see #loadSafeLabel(PackageManager, float, int)
     * @hide
     */
    @SystemApi
    public static final int SAFE_LABEL_FLAG_SINGLE_LINE = 0x2;

    /**
     * Return only first line of text (truncate at first newline). Cannot be set at the same time as
     * {@link #SAFE_LABEL_FLAG_SINGLE_LINE}.
     *
     * @see #loadSafeLabel(PackageManager, float, int)
     * @hide
     */
    @SystemApi
    public static final int SAFE_LABEL_FLAG_FIRST_LINE = 0x4;

    private static volatile boolean sForceSafeLabels = false;

    /** {@hide} */
@@ -140,7 +194,8 @@ public class PackageItemInfo {
     */
    public @NonNull CharSequence loadLabel(@NonNull PackageManager pm) {
        if (sForceSafeLabels) {
            return loadSafeLabel(pm);
            return loadSafeLabel(pm, DEFAULT_MAX_LABEL_SIZE_PX, SAFE_LABEL_FLAG_TRIM
                    | SAFE_LABEL_FLAG_FIRST_LINE);
        } else {
            return loadUnsafeLabel(pm);
        }
@@ -163,67 +218,227 @@ public class PackageItemInfo {
        return packageName;
    }

    private static boolean isNewline(int codePoint) {
        int type = Character.getType(codePoint);
        return type == Character.PARAGRAPH_SEPARATOR || type == Character.LINE_SEPARATOR
                || codePoint == LINE_FEED_CODE_POINT;
    }

    private static boolean isWhiteSpace(int codePoint) {
        return Character.isWhitespace(codePoint) || codePoint == NBSP_CODE_POINT;
    }

    /**
     * @hide
     * @deprecated use loadSafeLabel(PackageManager, float, int) instead
     */
    @SystemApi
    @Deprecated
    public @NonNull CharSequence loadSafeLabel(@NonNull PackageManager pm) {
        return loadSafeLabel(pm, DEFAULT_MAX_LABEL_SIZE_PX, SAFE_LABEL_FLAG_TRIM
                | SAFE_LABEL_FLAG_FIRST_LINE);
    }

    /**
     * A special string manipulation class. Just records removals and executes the when onString()
     * is called.
     */
    private static class StringWithRemovedChars {
        /** The original string */
        private final String mOriginal;

        /**
         * One bit per char in string. If bit is set, character needs to be removed. If whole
         * bit field is not initialized nothing needs to be removed.
         */
        private BitSet mRemovedChars;

        StringWithRemovedChars(@NonNull String original) {
            mOriginal = original;
        }

        /**
         * Mark all chars in a range {@code [firstRemoved - firstNonRemoved[} (not including
         * firstNonRemoved) as removed.
         */
        void removeRange(int firstRemoved, int firstNonRemoved) {
            if (mRemovedChars == null) {
                mRemovedChars = new BitSet(mOriginal.length());
            }

            mRemovedChars.set(firstRemoved, firstNonRemoved);
        }

        /**
         * Remove all characters before {@code firstNonRemoved}.
         */
        void removeAllCharBefore(int firstNonRemoved) {
            if (mRemovedChars == null) {
                mRemovedChars = new BitSet(mOriginal.length());
            }

            mRemovedChars.set(0, firstNonRemoved);
        }

        /**
         * Remove all characters after and including {@code firstRemoved}.
         */
        void removeAllCharAfter(int firstRemoved) {
            if (mRemovedChars == null) {
                mRemovedChars = new BitSet(mOriginal.length());
            }

            mRemovedChars.set(firstRemoved, mOriginal.length());
        }

        @Override
        public String toString() {
            // Common case, no chars removed
            if (mRemovedChars == null) {
                return mOriginal;
            }

            StringBuilder sb = new StringBuilder(mOriginal.length());
            for (int i = 0; i < mOriginal.length(); i++) {
                if (!mRemovedChars.get(i)) {
                    sb.append(mOriginal.charAt(i));
                }
            }

            return sb.toString();
        }

        /**
         * Return length or the original string
         */
        int length() {
            return mOriginal.length();
        }

        /**
     * Same as {@link #loadLabel(PackageManager)} with the addition that
     * the returned label is safe for being presented in the UI since it
     * will not contain new lines and the length will be limited to a
     * reasonable amount. This prevents a malicious party to influence UI
     * layout via the app label misleading the user into performing a
     * detrimental for them action. If the label is too long it will be
     * truncated and ellipsized at the end.
         * Return if a certain {@code offset} of the original string is removed
         */
        boolean isRemoved(int offset) {
            return mRemovedChars != null && mRemovedChars.get(offset);
        }

        /**
         * Return codePoint of original string at a certain {@code offset}
         */
        int codePointAt(int offset) {
            return mOriginal.codePointAt(offset);
        }
    }

    /**
     * Load, clean up and truncate label before use.
     *
     * @param pm A PackageManager from which the label can be loaded; usually
     * the PackageManager from which you originally retrieved this item
     * @return Returns a CharSequence containing the item's label. If the
     * item does not have a label, its name is returned.
     * <p>This method is meant to remove common mistakes and nefarious formatting from strings that
     * are used in sensitive parts of the UI.
     *
     * <p>This method first treats the string like HTML and then ...
     * <ul>
     * <li>Removes new lines or truncates at first new line
     * <li>Trims the white-space off the end
     * <li>Truncates the string to a given length
     * </ul>
     * ... if specified.
     *
     * @param ellipsizeDip Assuming maximum length of the string (in dip), assuming font size 42.
     *                     This is roughly 50 characters for {@code ellipsizeDip == 1000}.<br />
     *                     Usually ellipsizing should be left to the view showing the string. If a
     *                     string is used as an input to another string, it might be useful to
     *                     control the length of the input string though. {@code 0} disables this
     *                     feature.
     * @return The safe label
     * @hide
     */
    @SystemApi
    public @NonNull CharSequence loadSafeLabel(@NonNull PackageManager pm) {
    public @NonNull CharSequence loadSafeLabel(@NonNull PackageManager pm,
            @FloatRange(from = 0) float ellipsizeDip, @SafeLabelFlags int flags) {
        boolean onlyKeepFirstLine = ((flags & SAFE_LABEL_FLAG_FIRST_LINE) != 0);
        boolean forceSingleLine = ((flags & SAFE_LABEL_FLAG_SINGLE_LINE) != 0);
        boolean trim = ((flags & SAFE_LABEL_FLAG_TRIM) != 0);

        Preconditions.checkNotNull(pm);
        Preconditions.checkArgument(ellipsizeDip >= 0);
        Preconditions.checkFlagsArgument(flags, SAFE_LABEL_FLAG_TRIM | SAFE_LABEL_FLAG_SINGLE_LINE
                | SAFE_LABEL_FLAG_FIRST_LINE);
        Preconditions.checkArgument(!(onlyKeepFirstLine && forceSingleLine),
                "Cannot set SAFE_LABEL_FLAG_SINGLE_LINE and SAFE_LABEL_FLAG_FIRST_LINE at the same "
                + "time");

        // loadLabel() always returns non-null
        String label = loadUnsafeLabel(pm).toString();
        // strip HTML tags to avoid <br> and other tags overwriting original message
        String labelStr = Html.fromHtml(label).toString();

        // If the label contains new line characters it may push the UI
        // down to hide a part of it. Labels shouldn't have new line
        // characters, so just truncate at the first time one is seen.
        final int labelLength = Math.min(labelStr.length(), MAX_SAFE_LABEL_LENGTH);
        final StringBuffer sb = new StringBuffer(labelLength);
        int offset = 0;
        while (offset < labelLength) {
            final int codePoint = labelStr.codePointAt(offset);
            final int type = Character.getType(codePoint);
            if (type == Character.LINE_SEPARATOR
                    || type == Character.CONTROL
                    || type == Character.PARAGRAPH_SEPARATOR) {
                labelStr = labelStr.substring(0, offset);

        // Treat string as HTML. This
        // - converts HTML symbols: e.g. &szlig; -> ß
        // - applies some HTML tags: e.g. <br> -> \n
        // - removes invalid characters such as \b
        // - removes html styling, such as <b>
        // - applies html formatting: e.g. a<p>b</p>c -> a\n\nb\n\nc
        // - replaces some html tags by "object replacement" markers: <img> -> \ufffc
        // - Removes leading white space
        // - Removes all trailing white space beside a single space
        // - Collapses double white space
        StringWithRemovedChars labelStr = new StringWithRemovedChars(
                Html.fromHtml(label).toString());

        int firstNonWhiteSpace = -1;
        int firstTrailingWhiteSpace = -1;

        // Remove new lines (if requested) and control characters.
        int labelLength = labelStr.length();
        for (int offset = 0; offset < labelLength; ) {
            int codePoint = labelStr.codePointAt(offset);
            int type = Character.getType(codePoint);
            int codePointLen = Character.charCount(codePoint);
            boolean isNewline = isNewline(codePoint);

            if (offset > MAX_SAFE_LABEL_LENGTH || onlyKeepFirstLine && isNewline) {
                labelStr.removeAllCharAfter(offset);
                break;
            } else if (forceSingleLine && isNewline) {
                labelStr.removeRange(offset, offset + codePointLen);
            } else if (type == Character.CONTROL && !isNewline) {
                labelStr.removeRange(offset, offset + codePointLen);
            } else if (trim && !isWhiteSpace(codePoint)) {
                // This is only executed if the code point is not removed
                if (firstNonWhiteSpace == -1) {
                    firstNonWhiteSpace = offset;
                }
                firstTrailingWhiteSpace = offset + codePointLen;
            }

            offset += codePointLen;
        }
            // replace all non-break space to " " in order to be trimmed
            final int charCount = Character.charCount(codePoint);
            if (type == Character.SPACE_SEPARATOR) {
                sb.append(' ');

        if (trim) {
            // Remove leading and trailing white space
            if (firstNonWhiteSpace == -1) {
                // No non whitespace found, remove all
                labelStr.removeAllCharAfter(0);
            } else {
                sb.append(labelStr.charAt(offset));
                if (charCount == 2) {
                    sb.append(labelStr.charAt(offset + 1));
                if (firstNonWhiteSpace > 0) {
                    labelStr.removeAllCharBefore(firstNonWhiteSpace);
                }
                if (firstTrailingWhiteSpace < labelLength) {
                    labelStr.removeAllCharAfter(firstTrailingWhiteSpace);
                }
            offset += charCount;
            }

        labelStr = sb.toString().trim();
        if (labelStr.isEmpty()) {
            return packageName;
        }
        TextPaint paint = new TextPaint();

        if (ellipsizeDip == 0) {
            return labelStr.toString();
        } else {
            // Truncate
            final TextPaint paint = new TextPaint();
            paint.setTextSize(42);

        return TextUtils.ellipsize(labelStr, paint, MAX_LABEL_SIZE_PX,
            return TextUtils.ellipsize(labelStr.toString(), paint, ellipsizeDip,
                    TextUtils.TruncateAt.END);
        }
    }

    /**
     * Retrieve the current graphical icon associated with this item.  This
+5 −1
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageItemInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.util.Log;
@@ -82,7 +83,10 @@ public class HarmfulAppWarningActivity extends AlertActivity implements
        final View view = getLayoutInflater().inflate(R.layout.harmful_app_warning_dialog,
                null /*root*/);
        ((TextView) view.findViewById(R.id.app_name_text))
                .setText(applicationInfo.loadSafeLabel(getPackageManager()));
                .setText(applicationInfo.loadSafeLabel(getPackageManager(),
                        PackageItemInfo.DEFAULT_MAX_LABEL_SIZE_PX,
                        PackageItemInfo.SAFE_LABEL_FLAG_FIRST_LINE
                                | PackageItemInfo.SAFE_LABEL_FLAG_TRIM));
        ((TextView) view.findViewById(R.id.message))
                .setText(mHarmfulAppWarning);
        return view;
+9 −4
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import android.app.slice.SliceProvider;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.DialogInterface.OnDismissListener;
import android.content.pm.PackageItemInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.net.Uri;
@@ -51,10 +52,14 @@ public class SlicePermissionActivity extends Activity implements OnClickListener

        try {
            PackageManager pm = getPackageManager();
            CharSequence app1 = BidiFormatter.getInstance().unicodeWrap(
                    pm.getApplicationInfo(mCallingPkg, 0).loadSafeLabel(pm).toString());
            CharSequence app2 = BidiFormatter.getInstance().unicodeWrap(
                    pm.getApplicationInfo(mProviderPkg, 0).loadSafeLabel(pm).toString());
            CharSequence app1 = BidiFormatter.getInstance().unicodeWrap(pm.getApplicationInfo(
                    mCallingPkg, 0).loadSafeLabel(pm, PackageItemInfo.DEFAULT_MAX_LABEL_SIZE_PX,
                    PackageItemInfo.SAFE_LABEL_FLAG_TRIM
                            | PackageItemInfo.SAFE_LABEL_FLAG_FIRST_LINE).toString());
            CharSequence app2 = BidiFormatter.getInstance().unicodeWrap(pm.getApplicationInfo(
                    mProviderPkg, 0).loadSafeLabel(pm, PackageItemInfo.DEFAULT_MAX_LABEL_SIZE_PX,
                    PackageItemInfo.SAFE_LABEL_FLAG_TRIM
                            | PackageItemInfo.SAFE_LABEL_FLAG_FIRST_LINE).toString());
            AlertDialog dialog = new AlertDialog.Builder(this)
                    .setTitle(getString(R.string.slice_permission_title, app1, app2))
                    .setView(R.layout.slice_permission_request)
+5 −2
Original line number Diff line number Diff line
@@ -23,7 +23,6 @@ import static com.android.internal.util.Preconditions.checkNotNull;
import static com.android.internal.util.Preconditions.checkState;
import static com.android.internal.util.function.pooled.PooledLambda.obtainRunnable;

import android.Manifest;
import android.annotation.CheckResult;
import android.annotation.Nullable;
import android.app.PendingIntent;
@@ -39,6 +38,7 @@ import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.FeatureInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageItemInfo;
import android.content.pm.PackageManager;
import android.net.NetworkPolicyManager;
import android.os.Binder;
@@ -289,7 +289,10 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
            String packageTitle = BidiFormatter.getInstance().unicodeWrap(
                    getPackageInfo(callingPackage, userId)
                            .applicationInfo
                            .loadSafeLabel(getContext().getPackageManager())
                            .loadSafeLabel(getContext().getPackageManager(),
                                    PackageItemInfo.DEFAULT_MAX_LABEL_SIZE_PX,
                                    PackageItemInfo.SAFE_LABEL_FLAG_TRIM
                                            | PackageItemInfo.SAFE_LABEL_FLAG_FIRST_LINE)
                            .toString());
            long identity = Binder.clearCallingIdentity();
            try {
Loading