Merge changes Iaf1ecb67,Ifdc84b17,I1480b734,I2857cd33 into udc-dev am: 03081ace47 am: 3ff29df4e0

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/22692503

Change-Id: Idf13bdebc4f87702098cc5804a36f14963cbd99c
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/AlwaysOnMagnificationFeatureFlag.java b/services/accessibility/java/com/android/server/accessibility/magnification/AlwaysOnMagnificationFeatureFlag.java
index 16d2e6b..93531dd 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/AlwaysOnMagnificationFeatureFlag.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/AlwaysOnMagnificationFeatureFlag.java
@@ -16,76 +16,31 @@
 
 package com.android.server.accessibility.magnification;
 
-import android.annotation.NonNull;
 import android.provider.DeviceConfig;
 
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.util.concurrent.Executor;
-
 /**
  * Encapsulates the feature flags for always on magnification. {@see DeviceConfig}
  *
  * @hide
  */
-public class AlwaysOnMagnificationFeatureFlag {
+public class AlwaysOnMagnificationFeatureFlag extends MagnificationFeatureFlagBase {
 
     private static final String NAMESPACE = DeviceConfig.NAMESPACE_WINDOW_MANAGER;
     private static final String FEATURE_NAME_ENABLE_ALWAYS_ON_MAGNIFICATION =
             "AlwaysOnMagnifier__enable_always_on_magnifier";
 
-    private AlwaysOnMagnificationFeatureFlag() {}
-
-    /** Returns true if the feature flag is enabled for always on magnification */
-    public static boolean isAlwaysOnMagnificationEnabled() {
-        return DeviceConfig.getBoolean(
-                NAMESPACE,
-                FEATURE_NAME_ENABLE_ALWAYS_ON_MAGNIFICATION,
-                /* defaultValue= */ false);
+    @Override
+    String getNamespace() {
+        return NAMESPACE;
     }
 
-    /** Sets the feature flag. Only used for testing; requires shell permissions. */
-    @VisibleForTesting
-    public static boolean setAlwaysOnMagnificationEnabled(boolean isEnabled) {
-        return DeviceConfig.setProperty(
-                NAMESPACE,
-                FEATURE_NAME_ENABLE_ALWAYS_ON_MAGNIFICATION,
-                Boolean.toString(isEnabled),
-                /* makeDefault= */ false);
+    @Override
+    String getFeatureName() {
+        return FEATURE_NAME_ENABLE_ALWAYS_ON_MAGNIFICATION;
     }
 
-    /**
-     * Adds a listener for when the feature flag changes.
-     *
-     * <p>{@see DeviceConfig#addOnPropertiesChangedListener(
-     * String, Executor, DeviceConfig.OnPropertiesChangedListener)}
-     */
-    @NonNull
-    public static DeviceConfig.OnPropertiesChangedListener addOnChangedListener(
-            @NonNull Executor executor, @NonNull Runnable listener) {
-        DeviceConfig.OnPropertiesChangedListener onChangedListener =
-                properties -> {
-                    if (properties.getKeyset().contains(
-                            FEATURE_NAME_ENABLE_ALWAYS_ON_MAGNIFICATION)) {
-                        listener.run();
-                    }
-                };
-        DeviceConfig.addOnPropertiesChangedListener(
-                NAMESPACE,
-                executor,
-                onChangedListener);
-
-        return onChangedListener;
-    }
-
-    /**
-     * Remove a listener for when the feature flag changes.
-     *
-     * <p>{@see DeviceConfig#addOnPropertiesChangedListener(String, Executor,
-     * DeviceConfig.OnPropertiesChangedListener)}
-     */
-    public static void removeOnChangedListener(
-            @NonNull DeviceConfig.OnPropertiesChangedListener onChangedListener) {
-        DeviceConfig.removeOnPropertiesChangedListener(onChangedListener);
+    @Override
+    boolean getDefaultValue() {
+        return false;
     }
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
index ed8a35f..fbc7b3c 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
@@ -38,7 +38,6 @@
 import android.hardware.display.DisplayManagerInternal;
 import android.os.Handler;
 import android.os.Message;
-import android.provider.DeviceConfig;
 import android.text.TextUtils;
 import android.util.DisplayMetrics;
 import android.util.MathUtils;
@@ -57,6 +56,7 @@
 import com.android.internal.accessibility.common.MagnificationConstants;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ConcurrentUtils;
 import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.server.LocalServices;
 import com.android.server.accessibility.AccessibilityManagerService;
@@ -110,6 +110,7 @@
     private boolean mAlwaysOnMagnificationEnabled = false;
     private final DisplayManagerInternal mDisplayManagerInternal;
 
+    private final MagnificationThumbnailFeatureFlag mMagnificationThumbnailFeatureFlag;
     @NonNull private final Supplier<MagnificationThumbnail> mThumbnailSupplier;
 
     /**
@@ -177,9 +178,7 @@
                     mDisplayId, mMagnificationRegion);
             mMagnificationRegion.getBounds(mMagnificationBounds);
 
-            if (mMagnificationThumbnail == null) {
-                mMagnificationThumbnail = mThumbnailSupplier.get();
-            }
+            createThumbnailIfSupported();
 
             return true;
         }
@@ -207,7 +206,7 @@
                 mRegistered = false;
                 unregisterCallbackLocked(mDisplayId, delete);
 
-                destroyThumbNail();
+                destroyThumbnail();
             }
             mUnregisterPending = false;
         }
@@ -345,7 +344,7 @@
                     mMagnificationRegion.set(magnified);
                     mMagnificationRegion.getBounds(mMagnificationBounds);
 
-                    refreshThumbNail(getScale(), getCenterX(), getCenterY());
+                    refreshThumbnail(getScale(), getCenterX(), getCenterY());
 
                     // It's possible that our magnification spec is invalid with the new bounds.
                     // Adjust the current spec's offsets if necessary.
@@ -405,9 +404,9 @@
             }
 
             if (isActivated()) {
-                updateThumbNail(scale, centerX, centerY);
+                updateThumbnail(scale, centerX, centerY);
             } else {
-                hideThumbNail();
+                hideThumbnail();
             }
         }
 
@@ -538,7 +537,7 @@
             mIdOfLastServiceToMagnify = INVALID_SERVICE_ID;
             sendSpecToAnimation(spec, animationCallback);
 
-            hideThumbNail();
+            hideThumbnail();
 
             return changed;
         }
@@ -596,16 +595,16 @@
         }
 
         @GuardedBy("mLock")
-        void updateThumbNail(float scale, float centerX, float centerY) {
+        void updateThumbnail(float scale, float centerX, float centerY) {
             if (mMagnificationThumbnail != null) {
-                mMagnificationThumbnail.updateThumbNail(scale, centerX, centerY);
+                mMagnificationThumbnail.updateThumbnail(scale, centerX, centerY);
             }
         }
 
         @GuardedBy("mLock")
-        void refreshThumbNail(float scale, float centerX, float centerY) {
+        void refreshThumbnail(float scale, float centerX, float centerY) {
             if (mMagnificationThumbnail != null) {
-                mMagnificationThumbnail.setThumbNailBounds(
+                mMagnificationThumbnail.setThumbnailBounds(
                         mMagnificationBounds,
                         scale,
                         centerX,
@@ -615,20 +614,38 @@
         }
 
         @GuardedBy("mLock")
-        void hideThumbNail() {
+        void hideThumbnail() {
             if (mMagnificationThumbnail != null) {
-                mMagnificationThumbnail.hideThumbNail();
+                mMagnificationThumbnail.hideThumbnail();
             }
         }
 
         @GuardedBy("mLock")
-        void destroyThumbNail() {
+        void createThumbnailIfSupported() {
+            if (mMagnificationThumbnail == null) {
+                mMagnificationThumbnail = mThumbnailSupplier.get();
+                // We call refreshThumbnail when the thumbnail is just created to set current
+                // magnification bounds to thumbnail. It to prevent the thumbnail size has not yet
+                // updated properly and thus shows with huge size. (b/276314641)
+                refreshThumbnail(getScale(), getCenterX(), getCenterY());
+            }
+        }
+
+        @GuardedBy("mLock")
+        void destroyThumbnail() {
             if (mMagnificationThumbnail != null) {
-                hideThumbNail();
+                hideThumbnail();
                 mMagnificationThumbnail = null;
             }
         }
 
+        void onThumbnailFeatureFlagChanged() {
+            synchronized (mLock) {
+                destroyThumbnail();
+                createThumbnailIfSupported();
+            }
+        }
+
         /**
          * Updates the current magnification spec.
          *
@@ -768,20 +785,7 @@
                 lock,
                 magnificationInfoChangedCallback,
                 scaleProvider,
-                () -> {
-                    if (DeviceConfig.getBoolean(
-                            DeviceConfig.NAMESPACE_ACCESSIBILITY,
-                            "enable_magnifier_thumbnail",
-                            /* defaultValue= */ false)) {
-                        return new MagnificationThumbnail(
-                            context,
-                            context.getSystemService(WindowManager.class),
-                            new Handler(context.getMainLooper())
-                        );
-                    }
-
-                    return null;
-                });
+                /* thumbnailSupplier= */ null);
     }
 
     /** Constructor for tests */
@@ -791,7 +795,7 @@
             @NonNull Object lock,
             @NonNull MagnificationInfoChangedCallback magnificationInfoChangedCallback,
             @NonNull MagnificationScaleProvider scaleProvider,
-            @NonNull Supplier<MagnificationThumbnail> thumbnailSupplier) {
+            Supplier<MagnificationThumbnail> thumbnailSupplier) {
         mControllerCtx = ctx;
         mLock = lock;
         mMainThreadId = mControllerCtx.getContext().getMainLooper().getThread().getId();
@@ -799,7 +803,41 @@
         addInfoChangedCallback(magnificationInfoChangedCallback);
         mScaleProvider = scaleProvider;
         mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
-        mThumbnailSupplier = thumbnailSupplier;
+        mMagnificationThumbnailFeatureFlag = new MagnificationThumbnailFeatureFlag();
+        mMagnificationThumbnailFeatureFlag.addOnChangedListener(
+                ConcurrentUtils.DIRECT_EXECUTOR, this::onMagnificationThumbnailFeatureFlagChanged);
+        if (thumbnailSupplier != null) {
+            mThumbnailSupplier = thumbnailSupplier;
+        } else {
+            mThumbnailSupplier = () -> {
+                if (mMagnificationThumbnailFeatureFlag.isFeatureFlagEnabled()) {
+                    return new MagnificationThumbnail(
+                            ctx.getContext(),
+                            ctx.getContext().getSystemService(WindowManager.class),
+                            new Handler(ctx.getContext().getMainLooper())
+                    );
+                }
+                return null;
+            };
+        }
+    }
+
+    private void onMagnificationThumbnailFeatureFlagChanged() {
+        synchronized (mLock) {
+            for (int i = 0; i < mDisplays.size(); i++) {
+                onMagnificationThumbnailFeatureFlagChanged(mDisplays.keyAt(i));
+            }
+        }
+    }
+
+    private void onMagnificationThumbnailFeatureFlagChanged(int displayId) {
+        synchronized (mLock) {
+            final DisplayMagnification display = mDisplays.get(displayId);
+            if (display == null) {
+                return;
+            }
+            display.onThumbnailFeatureFlagChanged();
+        }
     }
 
     /**
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
index c1c47f5..7ee72df 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
@@ -93,6 +93,7 @@
     private final SparseArray<DisableMagnificationCallback>
             mMagnificationEndRunnableSparseArray = new SparseArray();
 
+    private final AlwaysOnMagnificationFeatureFlag mAlwaysOnMagnificationFeatureFlag;
     private final MagnificationScaleProvider mScaleProvider;
     private FullScreenMagnificationController mFullScreenMagnificationController;
     private WindowMagnificationManager mWindowMagnificationMgr;
@@ -151,7 +152,8 @@
         mSupportWindowMagnification = context.getPackageManager().hasSystemFeature(
                 FEATURE_WINDOW_MAGNIFICATION);
 
-        AlwaysOnMagnificationFeatureFlag.addOnChangedListener(
+        mAlwaysOnMagnificationFeatureFlag = new AlwaysOnMagnificationFeatureFlag();
+        mAlwaysOnMagnificationFeatureFlag.addOnChangedListener(
                 ConcurrentUtils.DIRECT_EXECUTOR, mAms::updateAlwaysOnMagnification);
     }
 
@@ -710,7 +712,7 @@
     }
 
     public boolean isAlwaysOnMagnificationFeatureFlagEnabled() {
-        return AlwaysOnMagnificationFeatureFlag.isAlwaysOnMagnificationEnabled();
+        return mAlwaysOnMagnificationFeatureFlag.isFeatureFlagEnabled();
     }
 
     private DisableMagnificationCallback getDisableMagnificationEndRunnableLocked(
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationFeatureFlagBase.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationFeatureFlagBase.java
new file mode 100644
index 0000000..2965887
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationFeatureFlagBase.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2023 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.server.accessibility.magnification;
+
+import android.annotation.NonNull;
+import android.os.Binder;
+import android.provider.DeviceConfig;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Abstract base class to encapsulates the feature flags for magnification features.
+ * {@see DeviceConfig}
+ *
+ * @hide
+ */
+abstract class MagnificationFeatureFlagBase {
+
+    abstract String getNamespace();
+    abstract String getFeatureName();
+    abstract boolean getDefaultValue();
+
+    private void clearCallingIdentifyAndTryCatch(Runnable tryBlock, Runnable catchBlock) {
+        try {
+            Binder.withCleanCallingIdentity(() -> tryBlock.run());
+        } catch (Throwable throwable) {
+            catchBlock.run();
+        }
+    }
+
+    /** Returns true iff the feature flag is readable and enabled */
+    public boolean isFeatureFlagEnabled() {
+        AtomicBoolean isEnabled = new AtomicBoolean(getDefaultValue());
+
+        clearCallingIdentifyAndTryCatch(
+                () -> isEnabled.set(DeviceConfig.getBoolean(
+                        getNamespace(),
+                        getFeatureName(),
+                        getDefaultValue())),
+                () -> isEnabled.set(getDefaultValue()));
+
+        return isEnabled.get();
+    }
+
+    /** Sets the feature flag. Only used for testing; requires shell permissions. */
+    @VisibleForTesting
+    public boolean setFeatureFlagEnabled(boolean isEnabled) {
+        AtomicBoolean success = new AtomicBoolean(getDefaultValue());
+
+        clearCallingIdentifyAndTryCatch(
+                () -> success.set(DeviceConfig.setProperty(
+                        getNamespace(),
+                        getFeatureName(),
+                        Boolean.toString(isEnabled),
+                        /* makeDefault= */ false)),
+                () -> success.set(getDefaultValue()));
+
+        return success.get();
+    }
+
+    /**
+     * Adds a listener for when the feature flag changes.
+     *
+     * <p>{@see DeviceConfig#addOnPropertiesChangedListener(
+     * String, Executor, DeviceConfig.OnPropertiesChangedListener)}
+     */
+    @NonNull
+    public DeviceConfig.OnPropertiesChangedListener addOnChangedListener(
+            @NonNull Executor executor, @NonNull Runnable listener) {
+        DeviceConfig.OnPropertiesChangedListener onChangedListener =
+                properties -> {
+                    if (properties.getKeyset().contains(
+                            getFeatureName())) {
+                        listener.run();
+                    }
+                };
+
+        clearCallingIdentifyAndTryCatch(
+                () -> DeviceConfig.addOnPropertiesChangedListener(
+                        getNamespace(),
+                        executor,
+                        onChangedListener),
+                () -> {});
+
+        return onChangedListener;
+    }
+
+    /**
+     * Remove a listener for when the feature flag changes.
+     *
+     * <p>{@see DeviceConfig#addOnPropertiesChangedListener(String, Executor,
+     * DeviceConfig.OnPropertiesChangedListener)}
+     */
+    public void removeOnChangedListener(
+            @NonNull DeviceConfig.OnPropertiesChangedListener onChangedListener) {
+        DeviceConfig.removeOnPropertiesChangedListener(onChangedListener);
+    }
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationThumbnail.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationThumbnail.java
index 5a783f4..03fa93d 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationThumbnail.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationThumbnail.java
@@ -58,7 +58,9 @@
     @VisibleForTesting
     public final FrameLayout mThumbnailLayout;
 
-    private final View mThumbNailView;
+    private final View mThumbnailView;
+    private int mThumbnailWidth;
+    private int mThumbnailHeight;
 
     private final WindowManager.LayoutParams mBackgroundParams;
     private boolean mVisible = false;
@@ -66,7 +68,7 @@
     private static final float ASPECT_RATIO = 14f;
     private static final float BG_ASPECT_RATIO = ASPECT_RATIO / 2f;
 
-    private ObjectAnimator mThumbNailAnimator;
+    private ObjectAnimator mThumbnailAnimator;
     private boolean mIsFadingIn;
 
     /**
@@ -79,9 +81,11 @@
         mWindowBounds =  mWindowManager.getCurrentWindowMetrics().getBounds();
         mThumbnailLayout = (FrameLayout) LayoutInflater.from(mContext)
                 .inflate(R.layout.thumbnail_background_view, /* root: */ null);
-        mThumbNailView =
+        mThumbnailView =
                 mThumbnailLayout.findViewById(R.id.accessibility_magnification_thumbnail_view);
         mBackgroundParams = createLayoutParams();
+        mThumbnailWidth = 0;
+        mThumbnailHeight = 0;
     }
 
     /**
@@ -90,35 +94,35 @@
      * @param currentBounds the current magnification bounds
      */
     @AnyThread
-    public void setThumbNailBounds(Rect currentBounds, float scale, float centerX, float centerY) {
+    public void setThumbnailBounds(Rect currentBounds, float scale, float centerX, float centerY) {
         if (DEBUG) {
-            Log.d(LOG_TAG, "setThumbNailBounds " + currentBounds);
+            Log.d(LOG_TAG, "setThumbnailBounds " + currentBounds);
         }
         mHandler.post(() -> {
             mWindowBounds = currentBounds;
             setBackgroundBounds();
             if (mVisible) {
-                updateThumbNailMainThread(scale, centerX, centerY);
+                updateThumbnailMainThread(scale, centerX, centerY);
             }
         });
     }
 
     private void setBackgroundBounds() {
         Point magnificationBoundary = getMagnificationThumbnailPadding(mContext);
-        final int thumbNailWidth = (int) (mWindowBounds.width() / BG_ASPECT_RATIO);
-        final int thumbNailHeight = (int) (mWindowBounds.height() / BG_ASPECT_RATIO);
+        mThumbnailWidth = (int) (mWindowBounds.width() / BG_ASPECT_RATIO);
+        mThumbnailHeight = (int) (mWindowBounds.height() / BG_ASPECT_RATIO);
         int initX = magnificationBoundary.x;
         int initY = magnificationBoundary.y;
-        mBackgroundParams.width = thumbNailWidth;
-        mBackgroundParams.height = thumbNailHeight;
+        mBackgroundParams.width = mThumbnailWidth;
+        mBackgroundParams.height = mThumbnailHeight;
         mBackgroundParams.x = initX;
         mBackgroundParams.y = initY;
     }
 
     @MainThread
-    private void showThumbNail() {
+    private void showThumbnail() {
         if (DEBUG) {
-            Log.d(LOG_TAG, "showThumbNail " + mVisible);
+            Log.d(LOG_TAG, "showThumbnail " + mVisible);
         }
         animateThumbnail(true);
     }
@@ -127,14 +131,14 @@
      * Hides thumbnail and removes the view from the window when finished animating.
      */
     @AnyThread
-    public void hideThumbNail() {
-        mHandler.post(this::hideThumbNailMainThread);
+    public void hideThumbnail() {
+        mHandler.post(this::hideThumbnailMainThread);
     }
 
     @MainThread
-    private void hideThumbNailMainThread() {
+    private void hideThumbnailMainThread() {
         if (DEBUG) {
-            Log.d(LOG_TAG, "hideThumbNail " + mVisible);
+            Log.d(LOG_TAG, "hideThumbnail " + mVisible);
         }
         if (mVisible) {
             animateThumbnail(false);
@@ -155,14 +159,14 @@
                         + " fadeIn: " + fadeIn
                         + " mVisible: " + mVisible
                         + " isFadingIn: " + mIsFadingIn
-                        + " isRunning: " + mThumbNailAnimator
+                        + " isRunning: " + mThumbnailAnimator
             );
         }
 
         // Reset countdown to hide automatically
-        mHandler.removeCallbacks(this::hideThumbNailMainThread);
+        mHandler.removeCallbacks(this::hideThumbnailMainThread);
         if (fadeIn) {
-            mHandler.postDelayed(this::hideThumbNailMainThread, LINGER_DURATION_MS);
+            mHandler.postDelayed(this::hideThumbnailMainThread, LINGER_DURATION_MS);
         }
 
         if (fadeIn == mIsFadingIn) {
@@ -175,18 +179,18 @@
             mVisible = true;
         }
 
-        if (mThumbNailAnimator != null) {
-            mThumbNailAnimator.cancel();
+        if (mThumbnailAnimator != null) {
+            mThumbnailAnimator.cancel();
         }
-        mThumbNailAnimator = ObjectAnimator.ofFloat(
+        mThumbnailAnimator = ObjectAnimator.ofFloat(
                 mThumbnailLayout,
                 "alpha",
                 fadeIn ? 1f : 0f
         );
-        mThumbNailAnimator.setDuration(
+        mThumbnailAnimator.setDuration(
                 fadeIn ? FADE_IN_ANIMATION_DURATION_MS : FADE_OUT_ANIMATION_DURATION_MS
         );
-        mThumbNailAnimator.addListener(new Animator.AnimatorListener() {
+        mThumbnailAnimator.addListener(new Animator.AnimatorListener() {
             private boolean mIsCancelled;
 
             @Override
@@ -231,7 +235,7 @@
             }
         });
 
-        mThumbNailAnimator.start();
+        mThumbnailAnimator.start();
     }
 
     /**
@@ -246,38 +250,48 @@
      *                of the viewport, or {@link Float#NaN} to leave unchanged
      */
     @AnyThread
-    public void updateThumbNail(float scale, float centerX, float centerY) {
-        mHandler.post(() -> updateThumbNailMainThread(scale, centerX, centerY));
+    public void updateThumbnail(float scale, float centerX, float centerY) {
+        mHandler.post(() -> updateThumbnailMainThread(scale, centerX, centerY));
     }
 
     @MainThread
-    private void updateThumbNailMainThread(float scale, float centerX, float centerY) {
+    private void updateThumbnailMainThread(float scale, float centerX, float centerY) {
         // Restart the fadeout countdown (or show if it's hidden)
-        showThumbNail();
+        showThumbnail();
 
-        var scaleDown = Float.isNaN(scale) ? mThumbNailView.getScaleX() : 1f / scale;
+        var scaleDown = Float.isNaN(scale) ? mThumbnailView.getScaleX() : 1f / scale;
         if (!Float.isNaN(scale)) {
-            mThumbNailView.setScaleX(scaleDown);
-            mThumbNailView.setScaleY(scaleDown);
+            mThumbnailView.setScaleX(scaleDown);
+            mThumbnailView.setScaleY(scaleDown);
+        }
+        float thumbnailWidth;
+        float thumbnailHeight;
+        if (mThumbnailView.getWidth() == 0 || mThumbnailView.getHeight() == 0) {
+            // if the thumbnail view size is not updated correctly, we just use the cached values.
+            thumbnailWidth = mThumbnailWidth;
+            thumbnailHeight = mThumbnailHeight;
+        } else {
+            thumbnailWidth = mThumbnailView.getWidth();
+            thumbnailHeight = mThumbnailView.getHeight();
         }
         if (!Float.isNaN(centerX)) {
-            var padding = mThumbNailView.getPaddingTop();
+            var padding = mThumbnailView.getPaddingTop();
             var ratio = 1f / BG_ASPECT_RATIO;
-            var centerXScaled = centerX * ratio - (mThumbNailView.getWidth() / 2f + padding);
-            var centerYScaled = centerY * ratio - (mThumbNailView.getHeight() / 2f + padding);
+            var centerXScaled = centerX * ratio - (thumbnailWidth / 2f + padding);
+            var centerYScaled = centerY * ratio - (thumbnailHeight / 2f + padding);
 
             if (DEBUG) {
                 Log.d(
                         LOG_TAG,
-                        "updateThumbNail centerXScaled : " + centerXScaled
+                        "updateThumbnail centerXScaled : " + centerXScaled
                                 + " centerYScaled : " + centerYScaled
-                                + " getTranslationX : " + mThumbNailView.getTranslationX()
+                                + " getTranslationX : " + mThumbnailView.getTranslationX()
                                 + " ratio : " + ratio
                 );
             }
 
-            mThumbNailView.setTranslationX(centerXScaled);
-            mThumbNailView.setTranslationY(centerYScaled);
+            mThumbnailView.setTranslationX(centerXScaled);
+            mThumbnailView.setTranslationY(centerYScaled);
         }
     }
 
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationThumbnailFeatureFlag.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationThumbnailFeatureFlag.java
new file mode 100644
index 0000000..519f31b
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationThumbnailFeatureFlag.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2023 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.server.accessibility.magnification;
+
+import android.provider.DeviceConfig;
+
+/**
+ * Encapsulates the feature flags for magnification thumbnail. {@see DeviceConfig}
+ *
+ * @hide
+ */
+public class MagnificationThumbnailFeatureFlag extends MagnificationFeatureFlagBase {
+
+    private static final String NAMESPACE = DeviceConfig.NAMESPACE_ACCESSIBILITY;
+    private static final String FEATURE_NAME_ENABLE_MAGNIFIER_THUMBNAIL =
+            "enable_magnifier_thumbnail";
+
+    @Override
+    String getNamespace() {
+        return NAMESPACE;
+    }
+
+    @Override
+    String getFeatureName() {
+        return FEATURE_NAME_ENABLE_MAGNIFIER_THUMBNAIL;
+    }
+
+    @Override
+    boolean getDefaultValue() {
+        return false;
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
index f1ad577..a01c7bd 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
@@ -200,7 +200,7 @@
         assertFalse(mFullScreenMagnificationController.isRegistered(DISPLAY_0));
         assertFalse(mFullScreenMagnificationController.isRegistered(DISPLAY_1));
 
-        verify(mMockThumbnail, times(2)).hideThumbNail();
+        verify(mMockThumbnail, times(2)).hideThumbnail();
     }
 
     @Test
@@ -538,7 +538,10 @@
                 mConfigCaptor.capture());
         assertConfigEquals(config, mConfigCaptor.getValue());
 
-        verify(mMockThumbnail).setThumbNailBounds(any(), anyFloat(), anyFloat(), anyFloat());
+        // The first time is triggered when the thumbnail is just created.
+        // The second time is triggered when the magnification region changed.
+        verify(mMockThumbnail, times(2)).setThumbnailBounds(
+                any(), anyFloat(), anyFloat(), anyFloat());
     }
 
     @Test
@@ -909,7 +912,7 @@
         verifyNoMoreInteractions(mMockWindowManager);
 
         verify(mMockThumbnail)
-                .updateThumbNail(eq(scale), eq(startCenter.x), eq(startCenter.y));
+                .updateThumbnail(eq(scale), eq(startCenter.x), eq(startCenter.y));
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationThumbnailTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationThumbnailTest.java
index 60c8148..3baa102 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationThumbnailTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationThumbnailTest.java
@@ -66,14 +66,14 @@
 
     @Test
     public void updateThumbnailShows() {
-        runOnMainSync(() -> mMagnificationThumbnail.updateThumbNail(
+        runOnMainSync(() -> mMagnificationThumbnail.updateThumbnail(
                 /* scale=   */ 2f,
                 /* centerX= */ 5,
                 /* centerY= */ 10
         ));
         idle();
 
-        runOnMainSync(() -> mMagnificationThumbnail.updateThumbNail(
+        runOnMainSync(() -> mMagnificationThumbnail.updateThumbnail(
                 /* scale=   */ 2.2f,
                 /* centerX= */ 15,
                 /* centerY= */ 50
@@ -86,7 +86,7 @@
 
     @Test
     public void updateThumbnailLingersThenHidesAfterTimeout() throws InterruptedException {
-        runOnMainSync(() -> mMagnificationThumbnail.updateThumbNail(
+        runOnMainSync(() -> mMagnificationThumbnail.updateThumbnail(
                 /* scale=   */ 2f,
                 /* centerX= */ 5,
                 /* centerY= */ 10
@@ -103,14 +103,14 @@
 
     @Test
     public void hideThumbnailRemoves() throws InterruptedException {
-        runOnMainSync(() -> mMagnificationThumbnail.updateThumbNail(
+        runOnMainSync(() -> mMagnificationThumbnail.updateThumbnail(
                 /* scale=   */ 2f,
                 /* centerX= */ 5,
                 /* centerY= */ 10
         ));
         idle();
 
-        runOnMainSync(() -> mMagnificationThumbnail.hideThumbNail());
+        runOnMainSync(() -> mMagnificationThumbnail.hideThumbnail());
         idle();
 
         // Wait for the fade out animation
@@ -122,10 +122,10 @@
 
     @Test
     public void hideShowHideShowHideRemoves() throws InterruptedException {
-        runOnMainSync(() -> mMagnificationThumbnail.hideThumbNail());
+        runOnMainSync(() -> mMagnificationThumbnail.hideThumbnail());
         idle();
 
-        runOnMainSync(() -> mMagnificationThumbnail.updateThumbNail(
+        runOnMainSync(() -> mMagnificationThumbnail.updateThumbnail(
                 /* scale=   */ 2f,
                 /* centerX= */ 5,
                 /* centerY= */ 10
@@ -135,17 +135,17 @@
         // Wait for the fade in animation
         Thread.sleep(200L);
 
-        runOnMainSync(() -> mMagnificationThumbnail.hideThumbNail());
+        runOnMainSync(() -> mMagnificationThumbnail.hideThumbnail());
         idle();
 
-        runOnMainSync(() -> mMagnificationThumbnail.updateThumbNail(
+        runOnMainSync(() -> mMagnificationThumbnail.updateThumbnail(
                 /* scale=   */ 2f,
                 /* centerX= */ 5,
                 /* centerY= */ 10
         ));
         idle();
 
-        runOnMainSync(() -> mMagnificationThumbnail.hideThumbNail());
+        runOnMainSync(() -> mMagnificationThumbnail.hideThumbnail());
         idle();
 
 
@@ -158,7 +158,7 @@
 
     @Test
     public void hideWithoutShowDoesNothing() throws InterruptedException {
-        runOnMainSync(() -> mMagnificationThumbnail.hideThumbNail());
+        runOnMainSync(() -> mMagnificationThumbnail.hideThumbnail());
         idle();
 
         // Wait for the fade out animation
@@ -172,7 +172,7 @@
 
     @Test
     public void whenHidden_setBoundsDoesNotShow() throws InterruptedException {
-        runOnMainSync(() -> mMagnificationThumbnail.setThumbNailBounds(
+        runOnMainSync(() -> mMagnificationThumbnail.setThumbnailBounds(
                 new Rect(),
                 /* scale=   */ 2f,
                 /* centerX= */ 5,