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

Commit a7bd5cf6 authored by Brandon Dayauon's avatar Brandon Dayauon Committed by Android (Google) Code Review
Browse files

Merge changes I209e3ec7,If70df129 into main

* changes:
  Change collapse to use adapterItems instead of getting the childCount()
  Upon expanding, expand just enough so the header shows.
parents 920d333e 5b5110a7
Loading
Loading
Loading
Loading
+12 −0
Original line number Diff line number Diff line
@@ -189,6 +189,7 @@ public class ActivityAllAppsContainerView<T extends Context & ActivityContext>
    private float mBottomSheetAlpha = 1f;
    private boolean mForceBottomSheetVisible;
    private int mTabsProtectionAlpha;
    private float mTotalHeaderProtectionHeight;
    @Nullable private AllAppsTransitionController mAllAppsTransitionController;

    private PrivateSpaceHeaderViewController mPrivateSpaceHeaderViewController;
@@ -1431,9 +1432,11 @@ public class ActivityAllAppsContainerView<T extends Context & ActivityContext>
                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
@@ -1463,9 +1466,18 @@ public class ActivityAllAppsContainerView<T extends Context & ActivityContext>
                    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
     */
+72 −14
Original line number Diff line number Diff line
@@ -41,15 +41,18 @@ import android.widget.ImageView;
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 @@ import java.util.List;
 * {@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,23 +176,24 @@ public class PrivateSpaceHeaderViewController {
                && 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);
        }
    }

    /** Finds the private space header to scroll to and set the private space icons to GONE. */
    private void collapse() {
        AllAppsRecyclerView allAppsRecyclerView = mAllApps.getActiveRecyclerView();
        for (int i = allAppsRecyclerView.getChildCount() - 1; i > 0; i--) {
            int adapterPosition = allAppsRecyclerView.getChildAdapterPosition(
                    allAppsRecyclerView.getChildAt(i));
            List<BaseAllAppsAdapter.AdapterItem> allAppsAdapters = allAppsRecyclerView.getApps()
                    .getAdapterItems();
            if (adapterPosition < 0 || adapterPosition >= allAppsAdapters.size()) {
                continue;
            }
        List<BaseAllAppsAdapter.AdapterItem> appListAdapterItems =
                allAppsRecyclerView.getApps().getAdapterItems();
        for (int i = appListAdapterItems.size() - 1; i > 0; i--) {
            BaseAllAppsAdapter.AdapterItem currentItem = appListAdapterItems.get(i);
            // Scroll to the private space header.
            if (allAppsAdapters.get(adapterPosition).viewType == VIEW_TYPE_PRIVATE_SPACE_HEADER) {
            if (currentItem.viewType == VIEW_TYPE_PRIVATE_SPACE_HEADER) {
                // Note: SmoothScroller is meant to be used once.
                RecyclerView.SmoothScroller smoothScroller =
                        new LinearSmoothScroller(mAllApps.getContext()) {
@@ -198,7 +201,7 @@ public class PrivateSpaceHeaderViewController {
                                return LinearSmoothScroller.SNAP_TO_END;
                            }
                        };
                smoothScroller.setTargetPosition(adapterPosition);
                smoothScroller.setTargetPosition(i);
                RecyclerView.LayoutManager layoutManager = allAppsRecyclerView.getLayoutManager();
                if (layoutManager != null) {
                    layoutManager.startSmoothScroll(smoothScroller);
@@ -206,10 +209,65 @@ public class PrivateSpaceHeaderViewController {
                break;
            }
            // Make the private space apps gone to "collapse".
            if (allAppsAdapters.get(adapterPosition).decorationInfo != null) {
                allAppsRecyclerView.getChildAt(i).setVisibility(GONE);
            if (currentItem.decorationInfo != null) {
                RecyclerView.ViewHolder viewHolder =
                        allAppsRecyclerView.findViewHolderForAdapterPosition(i);
                if (viewHolder != null) {
                    viewHolder.itemView.setVisibility(GONE);
                }
            }
        }
    }

    /**
     * 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() {
+135 −0
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package com.android.launcher3.allapps;

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 com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;

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.filters.SmallTest;
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.junit.runner.RunWith;
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 @@ public class PrivateSpaceHeaderViewControllerTest {
        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 @@ public class PrivateSpaceHeaderViewControllerTest {
    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);
    }
}