Upon expanding, expand just enough so the header shows.

This issue only happens when there is a lot of private space apps that scrolling to the bottom
will not sure the private space header.

Formula to calculate how many rows to scroll to =
	(appListHeight - privateHeaderHeight - headerProtectionHeight) / cellHeight.

bug: 299294792
Test:
manually - https://screenshot.googleplex.com/76UJPT2Jnpnp2Ab
before: it just scrolls all the way to the bottom
after: https://drive.google.com/file/d/1AbprxFm1RWTQKvpt7M4khbUfc1o6-XGF/view?usp=sharing
after PHONE WITH TABS:
2x2- https://drive.google.com/file/d/1SLPsWPHenCuZuisiS7HeEy5JwtNPeONs/view?usp=sharing
3x3- https://drive.google.com/file/d/1SK82jeNZMzFJK2odIuHnfTNLYfppne83/view?usp=sharing
4x4- https://drive.google.com/file/d/1T7EhFRq2tDv2zYIvs_FMsTKcFZXwGUaD/view?usp=sharing
4x5- https://drive.google.com/file/d/1SMUPuKjO1Yg36U6P6cDOb6dTkHn6Bh7D/view?usp=sharing
5x5- https://drive.google.com/file/d/1SJCQn1O_Yq5P7C__VUfZHc5I67CEdIpb/view?usp=sharing

AFTER PHONE NO TABS:
2x2: https://drive.google.com/file/d/1THU2xrAIt0hTmN5_GwBrgN9Lqj-W4Kfr/view?usp=sharing
3x3: https://drive.google.com/file/d/1TPTUx7PcHW3GsVwVAg_L5rCcn0QYoiY2/view?usp=sharing
4x4: https://drive.google.com/file/d/1TWVWpAX6bZp_JfFKtmXYO0askl4e5qKO/view?usp=sharing
4x5: https://drive.google.com/file/d/1TDJK-swmY3Y3C4ARH_2eljqUkBGEnD3e/view?usp=sharing
5x5- https://drive.google.com/file/d/1TBJtAynwvZrGyOc-29f637wyrJZpMXBJ/view?usp=sharing

Tablet:
landscape: https://drive.google.com/file/d/1SfyPdoUnCV7e7BWLnpxXWN2HiBOQkRo2/view?usp=sharing
portrait: https://drive.google.com/file/d/1SgZq0iE9WMvIFtc8mBb577nYlS9jBa_g/view?usp=sharing
Flag: ACONFIG com.android.launcher3.Flags.private_space_animation TRUNKFOOD

Change-Id: If70df1299572f8f2edc6376dd2a6df5d74287264
diff --git a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
index 6acfcd0..965e97c 100644
--- a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
@@ -189,6 +189,7 @@
     private float mBottomSheetAlpha = 1f;
     private boolean mForceBottomSheetVisible;
     private int mTabsProtectionAlpha;
+    private float mTotalHeaderProtectionHeight;
     @Nullable private AllAppsTransitionController mAllAppsTransitionController;
 
     private PrivateSpaceHeaderViewController mPrivateSpaceHeaderViewController;
@@ -1429,9 +1430,11 @@
                 mTmpPath.reset();
                 mTmpPath.addRoundRect(mTmpRectF, mBottomSheetCornerRadii, Direction.CW);
                 canvas.drawPath(mTmpPath, mHeaderPaint);
+                mTotalHeaderProtectionHeight = headerBottomWithScaleOnTablet;
             }
         } else {
             canvas.drawRect(0, 0, canvas.getWidth(), headerBottomWithScaleOnPhone, mHeaderPaint);
+            mTotalHeaderProtectionHeight = headerBottomWithScaleOnPhone;
         }
 
         // If tab exist (such as work profile), extend header with tab height
@@ -1461,10 +1464,19 @@
                     right,
                     tabBottomWithScale,
                     mHeaderPaint);
+            mTotalHeaderProtectionHeight = tabBottomWithScale;
         }
     }
 
     /**
+     * The height of the header protection is dynamically calculated during the time of drawing the
+     * header.
+     */
+    float getHeaderProtectionHeight() {
+        return mTotalHeaderProtectionHeight;
+    }
+
+    /**
      * redraws header protection
      */
     public void invalidateHeader() {
diff --git a/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewController.java b/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewController.java
index 6067454..b151b3a 100644
--- a/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewController.java
+++ b/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewController.java
@@ -41,15 +41,18 @@
 import android.widget.RelativeLayout;
 import android.widget.TextView;
 
+import androidx.annotation.VisibleForTesting;
 import androidx.recyclerview.widget.LinearSmoothScroller;
 import androidx.recyclerview.widget.RecyclerView;
 
 import com.android.app.animation.Interpolators;
+import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Flags;
 import com.android.launcher3.R;
 import com.android.launcher3.allapps.UserProfileManager.UserProfileState;
 import com.android.launcher3.anim.AnimatedPropertySetter;
 import com.android.launcher3.anim.PropertySetter;
+import com.android.launcher3.views.ActivityContext;
 import com.android.launcher3.views.RecyclerViewFastScroller;
 
 import java.util.List;
@@ -59,7 +62,6 @@
  * {@link UserProfileState}
  */
 public class PrivateSpaceHeaderViewController {
-    private static final int EXPAND_SCROLL_DURATION = 2000;
     private static final int EXPAND_COLLAPSE_DURATION = 800;
     private static final int SETTINGS_OPACITY_DURATION = 160;
     private final ActivityAllAppsContainerView mAllApps;
@@ -174,7 +176,12 @@
                 && mAllApps.getActiveRecyclerView() == mainAdapterHolder.mRecyclerView) {
             // Animate the text and settings icon.
             updatePrivateStateAnimator(true, header);
-            mAllApps.getActiveRecyclerView().scrollToBottomWithMotion(EXPAND_SCROLL_DURATION);
+            DeviceProfile deviceProfile =
+                    ActivityContext.lookupContext(mAllApps.getContext()).getDeviceProfile();
+            AllAppsRecyclerView allAppsRecyclerView = mAllApps.getActiveRecyclerView();
+            scrollForViewToBeVisibleInContainer(allAppsRecyclerView,
+                    allAppsRecyclerView.getApps().getAdapterItems(),
+                    header.getHeight(), deviceProfile.allAppsCellHeightPx);
         }
     }
 
@@ -212,6 +219,57 @@
         }
     }
 
+    /**
+     * Upon expanding, only scroll to the item position in the adapter that allows the header to be
+     * visible.
+     */
+    @VisibleForTesting
+    public int scrollForViewToBeVisibleInContainer(
+            AllAppsRecyclerView allAppsRecyclerView,
+            List<BaseAllAppsAdapter.AdapterItem> appListAdapterItems,
+            int psHeaderHeight,
+            int allAppsCellHeight) {
+        int rowToExpandToWithRespectToHeader = -1;
+        int itemToScrollTo = -1;
+        // Looks for the item in the app list to scroll to so that the header is visible.
+        for (int i = 0; i < appListAdapterItems.size(); i++) {
+            BaseAllAppsAdapter.AdapterItem currentItem = appListAdapterItems.get(i);
+            if (currentItem.viewType == VIEW_TYPE_PRIVATE_SPACE_HEADER) {
+                itemToScrollTo = i;
+                continue;
+            }
+            if (itemToScrollTo != -1) {
+                if (rowToExpandToWithRespectToHeader == -1) {
+                    rowToExpandToWithRespectToHeader = currentItem.rowIndex;
+                }
+                int rowToScrollTo =
+                        (int) Math.floor((double) (mAllApps.getHeight() - psHeaderHeight
+                                - mAllApps.getHeaderProtectionHeight()) / allAppsCellHeight);
+                int currentRowDistance = currentItem.rowIndex - rowToExpandToWithRespectToHeader;
+                // rowToScrollTo - 1 since the item to scroll to is 0 indexed.
+                if (currentRowDistance == rowToScrollTo - 1) {
+                    itemToScrollTo = i;
+                    break;
+                }
+            }
+        }
+        if (itemToScrollTo != -1) {
+            // Note: SmoothScroller is meant to be used once.
+            RecyclerView.SmoothScroller smoothScroller =
+                    new LinearSmoothScroller(mAllApps.getContext()) {
+                        @Override protected int getVerticalSnapPreference() {
+                            return LinearSmoothScroller.SNAP_TO_ANY;
+                        }
+                    };
+            smoothScroller.setTargetPosition(itemToScrollTo);
+            RecyclerView.LayoutManager layoutManager = allAppsRecyclerView.getLayoutManager();
+            if (layoutManager != null) {
+                layoutManager.startSmoothScroll(smoothScroller);
+            }
+        }
+        return itemToScrollTo;
+    }
+
     PrivateProfileManager getPrivateProfileManager() {
         return mPrivateProfileManager;
     }
diff --git a/tests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewControllerTest.java b/tests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewControllerTest.java
index 490cb47..043461d 100644
--- a/tests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewControllerTest.java
+++ b/tests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewControllerTest.java
@@ -18,6 +18,7 @@
 
 import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
 
+import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_PRIVATE_SPACE_HEADER;
 import static com.android.launcher3.allapps.UserProfileManager.STATE_DISABLED;
 import static com.android.launcher3.allapps.UserProfileManager.STATE_ENABLED;
 import static com.android.launcher3.allapps.UserProfileManager.STATE_TRANSITION;
@@ -25,13 +26,19 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.AdditionalAnswers.answer;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.when;
 
+import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
+import android.os.Process;
+import android.os.UserHandle;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.widget.ImageButton;
@@ -44,6 +51,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.launcher3.R;
+import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.util.ActivityContextWrapper;
 
 import org.junit.Before;
@@ -52,32 +60,53 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.ArrayList;
+import java.util.List;
+
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class PrivateSpaceHeaderViewControllerTest {
 
+    private static final UserHandle MAIN_HANDLE = Process.myUserHandle();
+    private static final UserHandle PRIVATE_HANDLE = new UserHandle(11);
     private static final int CONTAINER_HEADER_ELEMENT_COUNT = 1;
     private static final int LOCK_UNLOCK_BUTTON_COUNT = 1;
     private static final int PS_SETTINGS_BUTTON_COUNT_VISIBLE = 1;
     private static final int PS_SETTINGS_BUTTON_COUNT_INVISIBLE = 0;
     private static final int PS_TRANSITION_IMAGE_COUNT = 1;
+    private static final int NUM_APP_COLS = 4;
+    private static final int NUM_PRIVATE_SPACE_APPS = 50;
+    private static final int ALL_APPS_HEIGHT = 10;
+    private static final int ALL_APPS_CELL_HEIGHT = 1;
+    private static final int PS_HEADER_HEIGHT = 1;
+    private static final int BIGGER_PS_HEADER_HEIGHT = 2;
+    private static final int SCROLL_NO_WHERE = -1;
+    private static final float HEADER_PROTECTION_HEIGHT = 1F;
 
     private Context mContext;
     private PrivateSpaceHeaderViewController mPsHeaderViewController;
     private RelativeLayout mPsHeaderLayout;
+    private AlphabeticalAppsList<?> mAlphabeticalAppsList;
     @Mock
     private PrivateProfileManager mPrivateProfileManager;
     @Mock
     private ActivityAllAppsContainerView mAllApps;
+    @Mock
+    private AllAppsStore<?> mAllAppsStore;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         mContext = new ActivityContextWrapper(getApplicationContext());
+        when(mPrivateProfileManager.getItemInfoMatcher()).thenReturn(info ->
+                info != null && info.user.equals(PRIVATE_HANDLE));
         mPsHeaderViewController = new PrivateSpaceHeaderViewController(mAllApps,
                 mPrivateProfileManager);
         mPsHeaderLayout = (RelativeLayout) LayoutInflater.from(mContext).inflate(
                 R.layout.private_space_header, null);
+        mAlphabeticalAppsList = new AlphabeticalAppsList<>(mContext, mAllAppsStore,
+                null, mPrivateProfileManager);
+        mAlphabeticalAppsList.setNumAppsPerRowAllApps(NUM_APP_COLS);
     }
 
     @Test
@@ -223,6 +252,88 @@
         assertEquals(PS_TRANSITION_IMAGE_COUNT, totalLockUnlockButtonView);
     }
 
+    @Test
+    public void scrollForViewToBeVisibleInContainer_withHeader() {
+        when(mAllAppsStore.getApps()).thenReturn(createAppInfoList());
+        when(mPrivateProfileManager.addPrivateSpaceHeader(any()))
+                .thenAnswer(answer(this::addPrivateSpaceHeader));
+        when(mPrivateProfileManager.getCurrentState()).thenReturn(STATE_ENABLED);
+        when(mPrivateProfileManager.splitIntoUserInstalledAndSystemApps())
+                .thenReturn(iteminfo -> iteminfo.componentName == null
+                        || !iteminfo.componentName.getPackageName()
+                        .equals("com.android.launcher3.tests.camera"));
+        when(mAllApps.getContext()).thenReturn(mContext);
+        mAlphabeticalAppsList.updateItemFilter(info -> info != null
+                && info.user.equals(MAIN_HANDLE));
+        when(mAllApps.getHeight()).thenReturn(ALL_APPS_HEIGHT);
+        when(mAllApps.getHeaderProtectionHeight()).thenReturn(HEADER_PROTECTION_HEIGHT);
+        int rows = (int) (ALL_APPS_HEIGHT - PS_HEADER_HEIGHT - HEADER_PROTECTION_HEIGHT);
+        int position = rows * NUM_APP_COLS - (NUM_APP_COLS-1) + 1;
+
+        // The number of adapterItems should be the private space apps + one main app + header.
+        assertEquals(NUM_PRIVATE_SPACE_APPS + 1 + 1,
+                mAlphabeticalAppsList.getAdapterItems().size());
+        assertEquals(position,
+                mPsHeaderViewController.scrollForViewToBeVisibleInContainer(
+                        new AllAppsRecyclerView(mContext),
+                        mAlphabeticalAppsList.getAdapterItems(),
+                        PS_HEADER_HEIGHT,
+                        ALL_APPS_CELL_HEIGHT));
+    }
+
+    @Test
+    public void scrollForViewToBeVisibleInContainer_withHeaderAndLessAppRowSpace() {
+        when(mAllAppsStore.getApps()).thenReturn(createAppInfoList());
+        when(mPrivateProfileManager.addPrivateSpaceHeader(any()))
+                .thenAnswer(answer(this::addPrivateSpaceHeader));
+        when(mPrivateProfileManager.getCurrentState()).thenReturn(STATE_ENABLED);
+        when(mPrivateProfileManager.splitIntoUserInstalledAndSystemApps())
+                .thenReturn(iteminfo -> iteminfo.componentName == null
+                        || !iteminfo.componentName.getPackageName()
+                        .equals("com.android.launcher3.tests.camera"));
+        when(mAllApps.getContext()).thenReturn(mContext);
+        mAlphabeticalAppsList.updateItemFilter(info -> info != null
+                && info.user.equals(MAIN_HANDLE));
+        when(mAllApps.getHeight()).thenReturn(ALL_APPS_HEIGHT);
+        when(mAllApps.getHeaderProtectionHeight()).thenReturn(HEADER_PROTECTION_HEIGHT);
+        int rows = (int) (ALL_APPS_HEIGHT - BIGGER_PS_HEADER_HEIGHT - HEADER_PROTECTION_HEIGHT);
+        int position = rows * NUM_APP_COLS - (NUM_APP_COLS-1) + 1;
+
+        // The number of adapterItems should be the private space apps + one main app + header.
+        assertEquals(NUM_PRIVATE_SPACE_APPS + 1 + 1,
+                mAlphabeticalAppsList.getAdapterItems().size());
+        assertEquals(position,
+                mPsHeaderViewController.scrollForViewToBeVisibleInContainer(
+                        new AllAppsRecyclerView(mContext),
+                        mAlphabeticalAppsList.getAdapterItems(),
+                        BIGGER_PS_HEADER_HEIGHT,
+                        ALL_APPS_CELL_HEIGHT));
+    }
+
+    @Test
+    public void scrollForViewToBeVisibleInContainer_withNoHeader() {
+        when(mAllAppsStore.getApps()).thenReturn(createAppInfoList());
+        when(mPrivateProfileManager.getCurrentState()).thenReturn(STATE_ENABLED);
+        when(mPrivateProfileManager.splitIntoUserInstalledAndSystemApps())
+                .thenReturn(iteminfo -> iteminfo.componentName == null
+                        || !iteminfo.componentName.getPackageName()
+                        .equals("com.android.launcher3.tests.camera"));
+        when(mAllApps.getContext()).thenReturn(mContext);
+        mAlphabeticalAppsList.updateItemFilter(info -> info != null
+                && info.user.equals(MAIN_HANDLE));
+        when(mAllApps.getHeight()).thenReturn(ALL_APPS_HEIGHT);
+        when(mAllApps.getHeaderProtectionHeight()).thenReturn(HEADER_PROTECTION_HEIGHT);
+
+        // The number of adapterItems should be the private space apps + one main app.
+        assertEquals(NUM_PRIVATE_SPACE_APPS + 1,
+                mAlphabeticalAppsList.getAdapterItems().size());
+        assertEquals(SCROLL_NO_WHERE, mPsHeaderViewController.scrollForViewToBeVisibleInContainer(
+                new AllAppsRecyclerView(mContext),
+                mAlphabeticalAppsList.getAdapterItems(),
+                BIGGER_PS_HEADER_HEIGHT,
+                ALL_APPS_CELL_HEIGHT));
+    }
+
     private Bitmap getBitmap(Drawable drawable) {
         Bitmap result;
         if (drawable instanceof BitmapDrawable) {
@@ -249,4 +360,28 @@
     private static void awaitTasksCompleted() throws Exception {
         UI_HELPER_EXECUTOR.submit(() -> null).get();
     }
+
+    private int addPrivateSpaceHeader(List<BaseAllAppsAdapter.AdapterItem> adapterItemList) {
+        BaseAllAppsAdapter.AdapterItem privateSpaceHeader =
+                new BaseAllAppsAdapter.AdapterItem(VIEW_TYPE_PRIVATE_SPACE_HEADER);
+        adapterItemList.add(privateSpaceHeader);
+        return adapterItemList.size();
+    }
+
+    private AppInfo[] createAppInfoList() {
+        List<AppInfo> appInfos = new ArrayList<>();
+        ComponentName gmailComponentName = new ComponentName(mContext,
+                "com.android.launcher3.tests.Activity" + "Gmail");
+        AppInfo gmailAppInfo = new
+                AppInfo(gmailComponentName, "Gmail", MAIN_HANDLE, new Intent());
+        appInfos.add(gmailAppInfo);
+        ComponentName privateCameraComponentName = new ComponentName(
+                "com.android.launcher3.tests.camera", "CameraActivity");
+        for (int i = 0; i < NUM_PRIVATE_SPACE_APPS; i++) {
+            AppInfo privateCameraAppInfo = new AppInfo(privateCameraComponentName,
+                    "Private Camera " + i, PRIVATE_HANDLE, new Intent());
+            appInfos.add(privateCameraAppInfo);
+        }
+        return appInfos.toArray(AppInfo[]::new);
+    }
 }