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

Commit 7dca6913 authored by Becca Hughes's avatar Becca Hughes
Browse files

Make provider icon sizes consistent

Fixes an issue where the icons under "additional providers"
are not the same size. Also, fixes the unit tests for
CredentialManagerPreferenceController.

https://hsv.googleplex.com/4797855484084224

Test: unit tests and manual on device tests
Bug: 278772478
Change-Id: I88d8694189a7529ccc2d92bf1b74bb5bf2268f46
parent be50a8f6
Loading
Loading
Loading
Loading
+48 −13
Original line number Original line Diff line number Diff line
@@ -34,6 +34,7 @@ import android.content.res.Resources;
import android.credentials.CredentialManager;
import android.credentials.CredentialManager;
import android.credentials.CredentialProviderInfo;
import android.credentials.CredentialProviderInfo;
import android.credentials.SetEnabledProvidersException;
import android.credentials.SetEnabledProvidersException;
import android.credentials.flags.Flags;
import android.database.ContentObserver;
import android.database.ContentObserver;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.net.Uri;
@@ -108,6 +109,7 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl
    private final List<ServiceInfo> mPendingServiceInfos = new ArrayList<>();
    private final List<ServiceInfo> mPendingServiceInfos = new ArrayList<>();
    private final Handler mHandler = new Handler();
    private final Handler mHandler = new Handler();
    private final SettingContentObserver mSettingsContentObserver;
    private final SettingContentObserver mSettingsContentObserver;
    private final ImageUtils.IconResizer mIconResizer;


    private @Nullable FragmentManager mFragmentManager = null;
    private @Nullable FragmentManager mFragmentManager = null;
    private @Nullable Delegate mDelegate = null;
    private @Nullable Delegate mDelegate = null;
@@ -116,6 +118,7 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl


    private boolean mVisibility = false;
    private boolean mVisibility = false;
    private boolean mIsWorkProfile = false;
    private boolean mIsWorkProfile = false;
    private boolean mSimulateConnectedForTests = false;


    public CredentialManagerPreferenceController(Context context, String preferenceKey) {
    public CredentialManagerPreferenceController(Context context, String preferenceKey) {
        super(context, preferenceKey);
        super(context, preferenceKey);
@@ -129,6 +132,13 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl
                new SettingContentObserver(mHandler, context.getContentResolver());
                new SettingContentObserver(mHandler, context.getContentResolver());
        mSettingsContentObserver.register();
        mSettingsContentObserver.register();
        mSettingsPackageMonitor.register(context, context.getMainLooper(), false);
        mSettingsPackageMonitor.register(context, context.getMainLooper(), false);
        mIconResizer = getResizer(context);
    }

    private static ImageUtils.IconResizer getResizer(Context context) {
        final Resources resources = context.getResources();
        int size = (int) resources.getDimension(android.R.dimen.app_icon_size);
        return new ImageUtils.IconResizer(size, size, resources.getDisplayMetrics());
    }
    }


    private @Nullable CredentialManager getCredentialManager(Context context, boolean isTest) {
    private @Nullable CredentialManager getCredentialManager(Context context, boolean isTest) {
@@ -147,7 +157,7 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl


    @Override
    @Override
    public int getAvailabilityStatus() {
    public int getAvailabilityStatus() {
        if (mCredentialManager == null) {
        if (!isConnected()) {
            return UNSUPPORTED_ON_DEVICE;
            return UNSUPPORTED_ON_DEVICE;
        }
        }


@@ -174,7 +184,11 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl


    @VisibleForTesting
    @VisibleForTesting
    public boolean isConnected() {
    public boolean isConnected() {
        return mCredentialManager != null;
        return mCredentialManager != null || mSimulateConnectedForTests;
    }

    public void setSimulateConnectedForTests(boolean simulateConnectedForTests) {
        mSimulateConnectedForTests = simulateConnectedForTests;
    }
    }


    /**
    /**
@@ -293,7 +307,7 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl


        NewProviderConfirmationDialogFragment fragment =
        NewProviderConfirmationDialogFragment fragment =
                newNewProviderConfirmationDialogFragment(
                newNewProviderConfirmationDialogFragment(
                        serviceInfo.packageName, appName, /* setActivityResult= */ true);
                        serviceInfo.packageName, appName, /* shouldSetActivityResult= */ true);
        if (fragment == null || mFragmentManager == null) {
        if (fragment == null || mFragmentManager == null) {
            return;
            return;
        }
        }
@@ -365,7 +379,8 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl
        }
        }
    }
    }


    private void setVisibility(boolean newVisibility) {
    @VisibleForTesting
    public void setVisibility(boolean newVisibility) {
        if (newVisibility == mVisibility) {
        if (newVisibility == mVisibility) {
            return;
            return;
        }
        }
@@ -376,6 +391,11 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl
        }
        }
    }
    }


    @VisibleForTesting
    public boolean getVisibility() {
        return mVisibility;
    }

    @VisibleForTesting
    @VisibleForTesting
    void setAvailableServices(
    void setAvailableServices(
            List<CredentialProviderInfo> availableServices, String flagOverrideForTest) {
            List<CredentialProviderInfo> availableServices, String flagOverrideForTest) {
@@ -574,6 +594,17 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl
        return enabledServices;
        return enabledServices;
    }
    }


    @VisibleForTesting
    public @NonNull Drawable processIcon(@Nullable Drawable icon) {
        // If we didn't get an icon then we should use the default app icon.
        if (icon == null) {
            icon = mPm.getDefaultActivityIcon();
        }

        Drawable providerIcon = Utils.getSafeIcon(icon);
        return mIconResizer.createIconThumbnail(providerIcon);
    }

    private CombiPreference addProviderPreference(
    private CombiPreference addProviderPreference(
            @NonNull Context prefContext,
            @NonNull Context prefContext,
            @NonNull CharSequence title,
            @NonNull CharSequence title,
@@ -584,13 +615,14 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl
        final CombiPreference pref =
        final CombiPreference pref =
                new CombiPreference(prefContext, mEnabledPackageNames.contains(packageName));
                new CombiPreference(prefContext, mEnabledPackageNames.contains(packageName));
        pref.setTitle(title);
        pref.setTitle(title);
        pref.setLayoutResource(R.layout.preference_icon_credman);


        if (icon != null) {
        if (Flags.newSettingsUi()) {
            pref.setIcon(processIcon(icon));
        } else if (icon != null) {
            pref.setIcon(icon);
            pref.setIcon(icon);
        }
        }


        pref.setLayoutResource(R.layout.preference_icon_credman);

        if (subtitle != null) {
        if (subtitle != null) {
            pref.setSummary(subtitle);
            pref.setSummary(subtitle);
        }
        }
@@ -711,13 +743,13 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl
            newNewProviderConfirmationDialogFragment(
            newNewProviderConfirmationDialogFragment(
                    @NonNull String packageName,
                    @NonNull String packageName,
                    @NonNull CharSequence appName,
                    @NonNull CharSequence appName,
                    boolean setActivityResult) {
                    boolean shouldSetActivityResult) {
        DialogHost host =
        DialogHost host =
                new DialogHost() {
                new DialogHost() {
                    @Override
                    @Override
                    public void onDialogClick(int whichButton) {
                    public void onDialogClick(int whichButton) {
                        completeEnableProviderDialogBox(
                        completeEnableProviderDialogBox(
                                whichButton, packageName, setActivityResult);
                                whichButton, packageName, shouldSetActivityResult);
                    }
                    }


                    @Override
                    @Override
@@ -728,8 +760,8 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl
    }
    }


    @VisibleForTesting
    @VisibleForTesting
    void completeEnableProviderDialogBox(
    int completeEnableProviderDialogBox(
            int whichButton, String packageName, boolean setActivityResult) {
            int whichButton, String packageName, boolean shouldSetActivityResult) {
        int activityResult = -1;
        int activityResult = -1;
        if (whichButton == DialogInterface.BUTTON_POSITIVE) {
        if (whichButton == DialogInterface.BUTTON_POSITIVE) {
            if (togglePackageNameEnabled(packageName)) {
            if (togglePackageNameEnabled(packageName)) {
@@ -746,7 +778,7 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl
                final DialogFragment fragment = newErrorDialogFragment();
                final DialogFragment fragment = newErrorDialogFragment();


                if (fragment == null || mFragmentManager == null) {
                if (fragment == null || mFragmentManager == null) {
                    return;
                    return activityResult;
                }
                }


                fragment.show(mFragmentManager, ErrorDialogFragment.TAG);
                fragment.show(mFragmentManager, ErrorDialogFragment.TAG);
@@ -758,9 +790,11 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl


        // If the dialog is being shown because of the intent we should
        // If the dialog is being shown because of the intent we should
        // return a result.
        // return a result.
        if (activityResult == -1 || !setActivityResult) {
        if (activityResult == -1 || !shouldSetActivityResult) {
            setActivityResult(activityResult);
            setActivityResult(activityResult);
        }
        }

        return activityResult;
    }
    }


    private @Nullable ErrorDialogFragment newErrorDialogFragment() {
    private @Nullable ErrorDialogFragment newErrorDialogFragment() {
@@ -1002,6 +1036,7 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl
            }
            }
        }
        }


        @VisibleForTesting
        public boolean isChecked() {
        public boolean isChecked() {
            return mChecked;
            return mChecked;
        }
        }
+200 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2024 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 com.android.settings.applications.credentials;

import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.PaintFlagsDrawFilter;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.PaintDrawable;
import android.util.DisplayMetrics;

import androidx.annotation.NonNull;

/** Handles resizing of images for CredMan settings. */
public class ImageUtils {

    /**
     * Utility class to resize icons to match default icon size. Code is mostly borrowed from
     * Launcher and ActivityPicker.
     */
    public static class IconResizer {
        private final int mIconWidth;
        private final int mIconHeight;

        private final DisplayMetrics mMetrics;
        private final Rect mOldBounds = new Rect();
        private final Canvas mCanvas = new Canvas();

        public IconResizer(int width, int height, DisplayMetrics metrics) {
            mCanvas.setDrawFilter(
                    new PaintFlagsDrawFilter(Paint.DITHER_FLAG, Paint.FILTER_BITMAP_FLAG));

            mMetrics = metrics;
            mIconWidth = width;
            mIconHeight = height;
        }

        /**
         * Returns a Drawable representing the thumbnail of the specified Drawable. The size of the
         * thumbnail is defined by the dimension android.R.dimen.app_icon_size.
         *
         * <p>This method is not thread-safe and should be invoked on the UI thread only.
         *
         * @param icon The icon to get a thumbnail of.
         * @return A thumbnail for the specified icon or the icon itself if the thumbnail could not
         *     be created.
         */
        public Drawable createIconThumbnail(Drawable icon) {
            int width = mIconWidth;
            int height = mIconHeight;

            if (icon == null) {
                return new EmptyDrawable(width, height);
            }

            try {
                if (icon instanceof PaintDrawable) {
                    PaintDrawable painter = (PaintDrawable) icon;
                    painter.setIntrinsicWidth(width);
                    painter.setIntrinsicHeight(height);
                } else if (icon instanceof BitmapDrawable) {
                    // Ensure the bitmap has a density.
                    BitmapDrawable bitmapDrawable = (BitmapDrawable) icon;
                    Bitmap bitmap = bitmapDrawable.getBitmap();
                    if (bitmap.getDensity() == Bitmap.DENSITY_NONE) {
                        bitmapDrawable.setTargetDensity(mMetrics);
                    }
                }
                int iconWidth = icon.getIntrinsicWidth();
                int iconHeight = icon.getIntrinsicHeight();

                if (iconWidth > 0 && iconHeight > 0) {
                    if (width < iconWidth || height < iconHeight) {
                        final float ratio = (float) iconWidth / iconHeight;

                        if (iconWidth > iconHeight) {
                            height = (int) (width / ratio);
                        } else if (iconHeight > iconWidth) {
                            width = (int) (height * ratio);
                        }

                        final Bitmap.Config c =
                                icon.getOpacity() != PixelFormat.OPAQUE
                                        ? Bitmap.Config.ARGB_8888
                                        : Bitmap.Config.RGB_565;
                        final Bitmap thumb = Bitmap.createBitmap(mIconWidth, mIconHeight, c);
                        final Canvas canvas = mCanvas;
                        canvas.setBitmap(thumb);

                        // Copy the old bounds to restore them later
                        // If we were to do oldBounds = icon.getBounds(),
                        // the call to setBounds() that follows would
                        // change the same instance and we would lose the
                        // old bounds.
                        mOldBounds.set(icon.getBounds());
                        final int x = (mIconWidth - width) / 2;
                        final int y = (mIconHeight - height) / 2;
                        icon.setBounds(x, y, x + width, y + height);
                        icon.draw(canvas);
                        icon.setBounds(mOldBounds);

                        // Create the new resized drawable.
                        icon = createBitmapDrawable(thumb);
                    } else if (iconWidth < width && iconHeight < height) {
                        final Bitmap.Config c = Bitmap.Config.ARGB_8888;
                        final Bitmap thumb = Bitmap.createBitmap(mIconWidth, mIconHeight, c);
                        final Canvas canvas = mCanvas;
                        canvas.setBitmap(thumb);
                        mOldBounds.set(icon.getBounds());

                        // Set the bounds for the new icon.
                        final int x = (width - iconWidth) / 2;
                        final int y = (height - iconHeight) / 2;
                        icon.setBounds(x, y, x + iconWidth, y + iconHeight);
                        icon.draw(canvas);
                        icon.setBounds(mOldBounds);

                        // Create the new resized drawable.
                        icon = createBitmapDrawable(thumb);
                    }
                }

            } catch (Throwable t) {
                icon = new EmptyDrawable(width, height);
            }

            return icon;
        }

        private BitmapDrawable createBitmapDrawable(Bitmap thumb) {
            BitmapDrawable icon = new BitmapDrawable(thumb);
            icon.setTargetDensity(mMetrics);
            mCanvas.setBitmap(null);
            return icon;
        }
    }

    public static class EmptyDrawable extends Drawable {
        private final int mWidth;
        private final int mHeight;

        EmptyDrawable(int width, int height) {
            mWidth = width;
            mHeight = height;
        }

        @Override
        public int getIntrinsicWidth() {
            return mWidth;
        }

        @Override
        public int getIntrinsicHeight() {
            return mHeight;
        }

        @Override
        public int getMinimumWidth() {
            return mWidth;
        }

        @Override
        public int getMinimumHeight() {
            return mHeight;
        }

        @Override
        public void draw(@NonNull Canvas canvas) {}

        @Override
        public void setAlpha(int alpha) {}

        @Override
        public void setColorFilter(@NonNull ColorFilter cf) {}

        @Override
        public int getOpacity() {
            return PixelFormat.TRANSLUCENT;
        }
    }
}
+20 −0
Original line number Original line Diff line number Diff line
<!--
  ~ Copyright (C) 2024 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.
  -->

<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="#ff0000"/>
    <size android:width="1dp" android:height="1dp" />
</shape>
+20 −0
Original line number Original line Diff line number Diff line
<!--
  ~ Copyright (C) 2024 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.
  -->

<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="#ff0000"/>
    <size android:width="128dp" android:height="128dp" />
</shape>
+115 −38

File changed.

Preview size limit exceeded, changes collapsed.