Merge "Fixed portrait orientation if fixed_rotation flag"
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java
index 700feef..cd001a1 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java
@@ -332,6 +332,8 @@
                     // Send a home intent to clear the task stack
                     mContext.startActivity(mGestureState.getHomeIntent());
                 } else {
+                    // Notify swipe-to-home (recents animation) is finished
+                    SystemUiProxy.INSTANCE.get(mContext).notifySwipeToHomeFinished();
                     mRecentsAnimationController.finish(true, () -> {
                         if (!mTouchedHomeDuringTransition) {
                             // If the user hasn't interacted with the screen during the transition,
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java
index 345a147..646d01f 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java
@@ -699,6 +699,8 @@
         switch (mGestureState.getEndTarget()) {
             case HOME:
                 mStateCallback.setState(STATE_SCALED_CONTROLLER_HOME | STATE_CAPTURE_SCREENSHOT);
+                // Notify swipe-to-home (recents animation) is finished
+                SystemUiProxy.INSTANCE.get(mContext).notifySwipeToHomeFinished();
                 break;
             case RECENTS:
                 mStateCallback.setState(STATE_SCALED_CONTROLLER_RECENTS | STATE_CAPTURE_SCREENSHOT
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
index a673ab6..a7968ca 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
@@ -17,6 +17,7 @@
 package com.android.quickstep.views;
 
 import static androidx.dynamicanimation.animation.DynamicAnimation.MIN_VISIBLE_CHANGE_PIXELS;
+
 import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS;
 import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_ICON_PARAMS;
 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
@@ -105,11 +106,13 @@
 import com.android.quickstep.RecentsAnimationTargets;
 import com.android.quickstep.RecentsModel;
 import com.android.quickstep.RecentsModel.TaskVisualsChangeListener;
+import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.TaskThumbnailCache;
 import com.android.quickstep.TaskUtils;
 import com.android.quickstep.ViewUtils;
 import com.android.quickstep.util.AppWindowAnimationHelper;
 import com.android.quickstep.util.LayoutUtils;
+import com.android.systemui.shared.recents.IPinnedStackAnimationListener;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -261,7 +264,10 @@
                 }
             });
         }
+    };
 
+    private final IPinnedStackAnimationListener mIPinnedStackAnimationListener =
+            new IPinnedStackAnimationListener.Stub() {
         @Override
         public void onPinnedStackAnimationStarted() {
             // Needed for activities that auto-enter PiP, which will not trigger a remote
@@ -443,6 +449,8 @@
         mSyncTransactionApplier = new SyncRtSurfaceTransactionApplierCompat(this);
         RecentsModel.INSTANCE.get(getContext()).addThumbnailChangeListener(this);
         mIdp.addOnChangeListener(this);
+        SystemUiProxy.INSTANCE.get(getContext()).setPinnedStackAnimationListener(
+                mIPinnedStackAnimationListener);
     }
 
     @Override
@@ -455,6 +463,7 @@
         mSyncTransactionApplier = null;
         RecentsModel.INSTANCE.get(getContext()).removeThumbnailChangeListener(this);
         mIdp.removeOnChangeListener(this);
+        SystemUiProxy.INSTANCE.get(getContext()).setPinnedStackAnimationListener(null);
     }
 
     @Override
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index 458d6a9..eb60601 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -29,6 +29,7 @@
 import android.view.MotionEvent;
 
 import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.systemui.shared.recents.IPinnedStackAnimationListener;
 import com.android.systemui.shared.recents.ISystemUiProxy;
 
 /**
@@ -268,9 +269,7 @@
         }
     }
 
-    /**
-     * See SharedApiCompat#setShelfHeight()
-     */
+    @Override
     public void setShelfHeight(boolean visible, int shelfHeight) {
         boolean changed = visible != mLastShelfVisible || shelfHeight != mLastShelfHeight;
         if (mSystemUiProxy != null && changed) {
@@ -306,4 +305,32 @@
             }
         }
     }
+
+    /**
+     * Notifies that swipe-to-home action is finished.
+     */
+    @Override
+    public void notifySwipeToHomeFinished() {
+        if (mSystemUiProxy != null) {
+            try {
+                mSystemUiProxy.notifySwipeToHomeFinished();
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed call setPinnedStackAnimationType", e);
+            }
+        }
+    }
+
+    /**
+     * Sets listener to get pinned stack animation callbacks.
+     */
+    @Override
+    public void setPinnedStackAnimationListener(IPinnedStackAnimationListener listener) {
+        if (mSystemUiProxy != null) {
+            try {
+                mSystemUiProxy.setPinnedStackAnimationListener(listener);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed call setPinnedStackAnimationListener", e);
+            }
+        }
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/TaskThumbnailCache.java b/quickstep/src/com/android/quickstep/TaskThumbnailCache.java
index e47df6c..ace6743 100644
--- a/quickstep/src/com/android/quickstep/TaskThumbnailCache.java
+++ b/quickstep/src/com/android/quickstep/TaskThumbnailCache.java
@@ -17,7 +17,6 @@
 
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 
-import android.app.ActivityManager;
 import android.content.Context;
 import android.content.res.Resources;
 import android.os.Handler;
@@ -43,9 +42,10 @@
     private final int mCacheSize;
     private final TaskKeyLruCache<ThumbnailData> mCache;
     private final HighResLoadingState mHighResLoadingState;
+    private final boolean mEnableTaskSnapshotPreloading;
 
     public static class HighResLoadingState {
-        private boolean mIsLowRamDevice;
+        private boolean mForceHighResThumbnails;
         private boolean mVisible;
         private boolean mFlingingFast;
         private boolean mHighResLoadingEnabled;
@@ -56,9 +56,9 @@
         }
 
         private HighResLoadingState(Context context) {
-            ActivityManager activityManager =
-                    (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
-            mIsLowRamDevice = activityManager.isLowRamDevice();
+            // If the device does not support low-res thumbnails, only attempt to load high-res
+            // thumbnails
+            mForceHighResThumbnails = !supportsLowResThumbnails();
         }
 
         public void addCallback(HighResLoadingStateChangedCallback callback) {
@@ -85,7 +85,7 @@
 
         private void updateState() {
             boolean prevState = mHighResLoadingEnabled;
-            mHighResLoadingEnabled = !mIsLowRamDevice && mVisible && !mFlingingFast;
+            mHighResLoadingEnabled = mForceHighResThumbnails || (mVisible && !mFlingingFast);
             if (prevState != mHighResLoadingEnabled) {
                 for (int i = mCallbacks.size() - 1; i >= 0; i--) {
                     mCallbacks.get(i).onHighResLoadingStateChanged(mHighResLoadingEnabled);
@@ -100,6 +100,7 @@
 
         Resources res = context.getResources();
         mCacheSize = res.getInteger(R.integer.recentsThumbnailCacheSize);
+        mEnableTaskSnapshotPreloading = res.getBoolean(R.bool.config_enableTaskSnapshotPreloading);
         mCache = new TaskKeyLruCache<>(mCacheSize);
     }
 
@@ -110,7 +111,7 @@
         Preconditions.assertUIThread();
         // Fetch the thumbnail for this task and put it in the cache
         if (task.thumbnail == null) {
-            updateThumbnailInBackground(task.key, true /* reducedResolution */,
+            updateThumbnailInBackground(task.key, true /* lowResolution */,
                     t -> task.thumbnail = t);
         }
     }
@@ -133,8 +134,8 @@
             Task task, Consumer<ThumbnailData> callback) {
         Preconditions.assertUIThread();
 
-        boolean reducedResolution = !mHighResLoadingState.isEnabled();
-        if (task.thumbnail != null && (!task.thumbnail.reducedResolution || reducedResolution)) {
+        boolean lowResolution = !mHighResLoadingState.isEnabled();
+        if (task.thumbnail != null && (!task.thumbnail.reducedResolution || lowResolution)) {
             // Nothing to load, the thumbnail is already high-resolution or matches what the
             // request, so just callback
             callback.accept(task.thumbnail);
@@ -148,23 +149,23 @@
         });
     }
 
-    private ThumbnailLoadRequest updateThumbnailInBackground(TaskKey key, boolean reducedResolution,
+    private ThumbnailLoadRequest updateThumbnailInBackground(TaskKey key, boolean lowResolution,
             Consumer<ThumbnailData> callback) {
         Preconditions.assertUIThread();
 
         ThumbnailData cachedThumbnail = mCache.getAndInvalidateIfModified(key);
-        if (cachedThumbnail != null && (!cachedThumbnail.reducedResolution || reducedResolution)) {
+        if (cachedThumbnail != null && (!cachedThumbnail.reducedResolution || lowResolution)) {
             // Already cached, lets use that thumbnail
             callback.accept(cachedThumbnail);
             return null;
         }
 
         ThumbnailLoadRequest request = new ThumbnailLoadRequest(mBackgroundHandler,
-                reducedResolution) {
+                lowResolution) {
             @Override
             public void run() {
                 ThumbnailData thumbnail = ActivityManagerWrapper.getInstance().getTaskThumbnail(
-                        key.id, reducedResolution);
+                        key.id, lowResolution);
                 if (isCanceled()) {
                     // We don't call back to the provided callback in this case
                     return;
@@ -212,15 +213,31 @@
      * @return Whether to enable background preloading of task thumbnails.
      */
     public boolean isPreloadingEnabled() {
-        return !mHighResLoadingState.mIsLowRamDevice && mHighResLoadingState.mVisible;
+        return mEnableTaskSnapshotPreloading && mHighResLoadingState.mVisible;
     }
 
     public static abstract class ThumbnailLoadRequest extends HandlerRunnable {
-        public final boolean reducedResolution;
+        public final boolean mLowResolution;
 
-        ThumbnailLoadRequest(Handler handler, boolean reducedResolution) {
+        ThumbnailLoadRequest(Handler handler, boolean lowResolution) {
             super(handler, null);
-            this.reducedResolution = reducedResolution;
+            mLowResolution = lowResolution;
         }
     }
+
+    /**
+     * @return Whether device supports low-res thumbnails. Low-res files are an optimization
+     * for faster load times of snapshots. Devices can optionally disable low-res files so that
+     * they only store snapshots at high-res scale. The actual scale can be configured in
+     * frameworks/base config overlay.
+     */
+    private static boolean supportsLowResThumbnails() {
+        Resources res = Resources.getSystem();
+        int resId = res.getIdentifier("config_lowResTaskSnapshotScale", "dimen", "android");
+        if (resId != 0) {
+            return 0 < res.getFloat(resId);
+        }
+        return true;
+    }
+
 }
diff --git a/res/values/config.xml b/res/values/config.xml
index ef34dcd..8aff6da 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -117,6 +117,9 @@
     <!-- Recents -->
     <item type="id" name="overview_panel"/>
 
+    <!-- Whether to enable background preloading of task thumbnails. -->
+    <bool name="config_enableTaskSnapshotPreloading">true</bool>
+
     <!-- Configuration resources -->
     <array name="dynamic_resources"> </array>
 </resources>