Snap for 10594510 from a1877d2888 to udc-qpr1-release

Change-Id: Ibc38c32e450769dba2403327071a3c394c342146
This commit is contained in:
Android Build Coastguard Worker
2023-08-01 23:19:10 +00:00
36 changed files with 473 additions and 173 deletions
@@ -183,7 +183,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
private static final String CONTROL_REMOTE_APP_TRANSITION_PERMISSION =
"android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS";
private static final long APP_LAUNCH_DURATION = 500;
public static final long APP_LAUNCH_DURATION = 500;
private static final long APP_LAUNCH_ALPHA_DURATION = 50;
private static final long APP_LAUNCH_ALPHA_START_DELAY = 25;
@@ -19,7 +19,6 @@ import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCH
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOTSEAT_EDU_DENY;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOTSEAT_EDU_SEEN;
import android.animation.PropertyValuesHolder;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Rect;
@@ -30,7 +29,6 @@ import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.android.app.animation.Interpolators;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.CellLayout;
import com.android.launcher3.DeviceProfile;
@@ -164,10 +162,7 @@ public class HotseatEduDialog extends AbstractSlideInView<Launcher> implements I
return;
}
mIsOpen = true;
mOpenCloseAnimator.setValues(
PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED));
mOpenCloseAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
mOpenCloseAnimator.start();
setUpDefaultOpenAnimator().start();
}
@Override
@@ -27,6 +27,7 @@ import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.uioverrides.QuickstepLauncher;
import com.android.quickstep.GestureState;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.views.DesktopAppSelectView;
import com.android.wm.shell.desktopmode.IDesktopTaskListener;
@@ -39,7 +40,8 @@ public class DesktopVisibilityController {
private static final String TAG = "DesktopVisController";
private static final boolean DEBUG = false;
private static final boolean IS_STASHING_ENABLED = SystemProperties.getBoolean(
"persist.wm.debug.desktop_stashing", false);
private final Launcher mLauncher;
private boolean mFreeformTasksVisible;
@@ -73,6 +75,9 @@ public class DesktopVisibilityController {
@Override
public void onStashedChanged(int displayId, boolean stashed) {
if (!IS_STASHING_ENABLED) {
return;
}
MAIN_EXECUTOR.execute(() -> {
if (displayId == mLauncher.getDisplayId()) {
if (DEBUG) {
@@ -166,20 +171,40 @@ public class DesktopVisibilityController {
/**
* Whether recents gesture is currently in progress.
*/
public boolean isGestureInProgress() {
public boolean isRecentsGestureInProgress() {
return mGestureInProgress;
}
/**
* Sets whether recents gesture is in progress.
* Notify controller that recents gesture has started.
*/
public void setGestureInProgress(boolean gestureInProgress) {
if (DEBUG) {
Log.d(TAG, "setGestureInProgress: inProgress=" + gestureInProgress);
}
public void setRecentsGestureStart() {
if (!isDesktopModeSupported()) {
return;
}
setRecentsGestureInProgress(true);
}
/**
* Notify controller that recents gesture finished with the given
* {@link com.android.quickstep.GestureState.GestureEndTarget}
*/
public void setRecentsGestureEnd(@Nullable GestureState.GestureEndTarget endTarget) {
if (!isDesktopModeSupported()) {
return;
}
setRecentsGestureInProgress(false);
if (endTarget == null) {
// Gesture did not result in a new end target. Ensure launchers gets paused again.
markLauncherPaused();
}
}
private void setRecentsGestureInProgress(boolean gestureInProgress) {
if (DEBUG) {
Log.d(TAG, "setGestureInProgress: inProgress=" + gestureInProgress);
}
if (gestureInProgress != mGestureInProgress) {
mGestureInProgress = gestureInProgress;
}
@@ -189,7 +214,7 @@ public class DesktopVisibilityController {
* Handle launcher moving to home due to home gesture or home button press.
*/
public void onHomeActionTriggered() {
if (areFreeformTasksVisible()) {
if (IS_STASHING_ENABLED && areFreeformTasksVisible()) {
SystemUiProxy.INSTANCE.get(mLauncher).stashDesktopApps(mLauncher.getDisplayId());
}
}
@@ -35,6 +35,8 @@ import android.view.ContextThemeWrapper;
import android.view.MotionEvent;
import android.view.View;
import androidx.annotation.VisibleForTesting;
import com.android.app.animation.Interpolators;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.BubbleTextView;
@@ -49,7 +51,7 @@ import com.android.launcher3.views.ArrowTipView;
*/
public class TaskbarHoverToolTipController implements View.OnHoverListener {
private static final int HOVER_TOOL_TIP_REVEAL_START_DELAY = 400;
@VisibleForTesting protected static final int HOVER_TOOL_TIP_REVEAL_START_DELAY = 400;
private static final int HOVER_TOOL_TIP_REVEAL_DURATION = 300;
private static final int HOVER_TOOL_TIP_EXIT_DURATION = 150;
@@ -145,7 +147,6 @@ public class TaskbarHoverToolTipController implements View.OnHoverListener {
}
private void startRevealHoverToolTip() {
mActivity.setTaskbarWindowFullscreen(true);
mHoverToolTipHandler.postDelayed(mRevealHoverToolTipRunnable,
HOVER_TOOL_TIP_REVEAL_START_DELAY);
}
@@ -157,6 +158,7 @@ public class TaskbarHoverToolTipController implements View.OnHoverListener {
if (mHoverView instanceof FolderIcon && !((FolderIcon) mHoverView).getIconVisible()) {
return;
}
mActivity.setTaskbarWindowFullscreen(true);
Rect iconViewBounds = Utilities.getViewBounds(mHoverView);
mHoverToolTipView.showAtLocation(mToolTipText, iconViewBounds.centerX(),
mTaskbarView.getTop(), /* shouldAutoClose= */ false);
@@ -17,9 +17,6 @@ package com.android.launcher3.taskbar.allapps;
import static com.android.app.animation.Interpolators.EMPHASIZED;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.PropertyValuesHolder;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Rect;
@@ -31,6 +28,7 @@ import android.window.OnBackInvokedDispatcher;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Insettable;
import com.android.launcher3.R;
import com.android.launcher3.anim.AnimatorListeners;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.taskbar.allapps.TaskbarAllAppsViewController.TaskbarAllAppsCallbacks;
import com.android.launcher3.taskbar.overlay.TaskbarOverlayContext;
@@ -68,16 +66,9 @@ public class TaskbarAllAppsSlideInView extends AbstractSlideInView<TaskbarOverla
mAllAppsCallbacks.onAllAppsTransitionStart(true);
if (animate) {
mOpenCloseAnimator.setValues(
PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED));
mOpenCloseAnimator.setInterpolator(EMPHASIZED);
mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mOpenCloseAnimator.removeListener(this);
mAllAppsCallbacks.onAllAppsTransitionEnd(true);
}
});
setUpOpenCloseAnimator(TRANSLATION_SHIFT_OPENED, EMPHASIZED);
mOpenCloseAnimator.addListener(AnimatorListeners.forEndCallback(
() -> mAllAppsCallbacks.onAllAppsTransitionEnd(true)));
mOpenCloseAnimator.setDuration(mAllAppsCallbacks.getOpenDuration()).start();
} else {
mTranslationShift = TRANSLATION_SHIFT_OPENED;
@@ -664,6 +664,8 @@ public class QuickstepLauncher extends Launcher {
@Override
public void onAnimationCancel(Animator animation) {
getDragLayer().removeView(floatingTaskView);
mSplitSelectStateController.getSplitAnimationController()
.removeSplitInstructionsView(QuickstepLauncher.this);
mSplitSelectStateController.resetState();
}
});
@@ -865,7 +867,7 @@ public class QuickstepLauncher extends Launcher {
if (DesktopTaskView.DESKTOP_MODE_SUPPORTED) {
DesktopVisibilityController controller = mDesktopVisibilityController;
if (controller != null && controller.areFreeformTasksVisible()
&& !controller.isGestureInProgress()) {
&& !controller.isRecentsGestureInProgress()) {
// Return early to skip setting activity to appear as resumed
// TODO(b/255649902): shouldn't be needed when we have a separate launcher state
// for desktop that we can use to control other parts of launcher
@@ -159,6 +159,10 @@ public class TaskOverlayFactory implements ResourceBasedOverride {
return mActionsView;
}
public TaskThumbnailView getThumbnailView() {
return mThumbnailView;
}
/**
* Called when the current task is interactive for the user
*/
@@ -265,6 +265,11 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC
return anim
}
/** Removes the split instructions view from [launcher] drag layer. */
fun removeSplitInstructionsView(launcher: StatefulActivity<*>) {
safeRemoveViewFromDragLayer(launcher, splitInstructionsView)
}
private fun safeRemoveViewFromDragLayer(launcher: StatefulActivity<*>, view: View?) {
if (view != null) {
launcher.dragLayer.removeView(view)
@@ -169,6 +169,7 @@ public class SplitToWorkspaceController {
private void cleanUp() {
mLauncher.getDragLayer().removeView(firstFloatingTaskView);
mLauncher.getDragLayer().removeView(secondFloatingTaskView);
mController.getSplitAnimationController().removeSplitInstructionsView(mLauncher);
mController.resetState();
}
});
@@ -249,7 +249,7 @@ public class LauncherRecentsView extends RecentsView<QuickstepLauncher, Launcher
DesktopVisibilityController desktopVisibilityController =
mActivity.getDesktopVisibilityController();
if (desktopVisibilityController != null) {
desktopVisibilityController.setGestureInProgress(true);
desktopVisibilityController.setRecentsGestureStart();
}
}
@@ -257,9 +257,11 @@ public class LauncherRecentsView extends RecentsView<QuickstepLauncher, Launcher
public void onGestureAnimationEnd() {
DesktopVisibilityController desktopVisibilityController = null;
boolean showDesktopApps = false;
GestureState.GestureEndTarget endTarget = null;
if (DesktopTaskView.DESKTOP_MODE_SUPPORTED) {
desktopVisibilityController = mActivity.getDesktopVisibilityController();
if (mCurrentGestureEndTarget == GestureState.GestureEndTarget.LAST_TASK
endTarget = mCurrentGestureEndTarget;
if (endTarget == GestureState.GestureEndTarget.LAST_TASK
&& desktopVisibilityController.areFreeformTasksVisible()) {
// Recents gesture was cancelled and we are returning to the previous task.
// After super class has handled clean up, show desktop apps on top again
@@ -268,7 +270,7 @@ public class LauncherRecentsView extends RecentsView<QuickstepLauncher, Launcher
}
super.onGestureAnimationEnd();
if (desktopVisibilityController != null) {
desktopVisibilityController.setGestureInProgress(false);
desktopVisibilityController.setRecentsGestureEnd(endTarget);
}
if (showDesktopApps) {
SystemUiProxy.INSTANCE.get(mActivity).showDesktopApps(mActivity.getDisplayId());
@@ -262,6 +262,13 @@ public class OverviewActionsView<T extends OverlayUICallbacks> extends FrameLayo
return mMultiValueAlpha.get(INDEX_SCROLL_ALPHA);
}
/**
* Returns the visibility of the overview actions buttons.
*/
public @Visibility int getActionsButtonVisibility() {
return findViewById(R.id.action_buttons).getVisibility();
}
/**
* Offsets OverviewActionsView horizontal position based on 3 button nav container in taskbar.
*/
@@ -4823,6 +4823,8 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
mSecondFloatingTaskView = null;
mSplitInstructionsView = null;
mSplitSelectSource = null;
mSplitSelectStateController.getSplitAnimationController()
.removeSplitInstructionsView(mActivity);
}
if (mSecondSplitHiddenView != null) {
@@ -18,6 +18,7 @@ package com.android.launcher3.taskbar;
import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
import static com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS;
import static com.android.launcher3.taskbar.TaskbarHoverToolTipController.HOVER_TOOL_TIP_REVEAL_START_DELAY;
import static com.google.common.truth.Truth.assertThat;
@@ -26,6 +27,7 @@ import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -56,7 +58,7 @@ import org.mockito.stubbing.Answer;
*/
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class TaskbarHoverToolTipControllerTest extends TaskbarBaseTestCase {
private TaskbarHoverToolTipController mTaskbarHoverToolTipController;
@@ -126,8 +128,10 @@ public class TaskbarHoverToolTipControllerTest extends TaskbarBaseTestCase {
boolean hoverHandled =
mTaskbarHoverToolTipController.onHover(mHoverBubbleTextView, mMotionEvent);
waitForIdleSync();
// Verify fullscreen is not set until the delayed runnable to reveal the tooltip has run
verify(taskbarActivityContext, never()).setTaskbarWindowFullscreen(true);
waitForIdleSync();
assertThat(hoverHandled).isTrue();
verify(taskbarActivityContext).setAutohideSuspendFlag(FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS,
true);
@@ -155,8 +159,10 @@ public class TaskbarHoverToolTipControllerTest extends TaskbarBaseTestCase {
boolean hoverHandled =
mTaskbarHoverToolTipController.onHover(mHoverFolderIcon, mMotionEvent);
waitForIdleSync();
// Verify fullscreen is not set until the delayed runnable to reveal the tooltip has run
verify(taskbarActivityContext, never()).setTaskbarWindowFullscreen(true);
waitForIdleSync();
assertThat(hoverHandled).isTrue();
verify(taskbarActivityContext).setAutohideSuspendFlag(FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS,
true);
@@ -216,6 +222,7 @@ public class TaskbarHoverToolTipControllerTest extends TaskbarBaseTestCase {
}
private void waitForIdleSync() {
mTestableLooper.moveTimeForward(HOVER_TOOL_TIP_REVEAL_START_DELAY + 1);
mTestableLooper.processAllMessages();
}
}
@@ -886,6 +886,10 @@ public class DeviceProfile {
updateIconSize(1f, res);
updateWorkspacePadding();
if (mIsResponsiveGrid) {
return 0;
}
// Check to see if the icons fit within the available height.
float usedHeight = getCellLayoutHeightSpecification();
final int maxHeight = getCellLayoutHeight();
@@ -18,6 +18,7 @@ package com.android.launcher3.allapps;
import static com.android.launcher3.allapps.ActivityAllAppsContainerView.AdapterHolder.SEARCH;
import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_WORK_DISABLED_CARD;
import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_WORK_EDU_CARD;
import static com.android.launcher3.config.FeatureFlags.ALL_APPS_GONE_VISIBILITY;
import static com.android.launcher3.config.FeatureFlags.ENABLE_ALL_APPS_RV_PREINFLATION;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_COUNT;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_TAP_ON_PERSONAL_TAB;
@@ -552,17 +553,14 @@ public class ActivityAllAppsContainerView<T extends Context & ActivityContext>
mAllAppsStore.unregisterIconContainer(mAH.get(AdapterHolder.WORK).mRecyclerView);
mAllAppsStore.unregisterIconContainer(mAH.get(AdapterHolder.SEARCH).mRecyclerView);
final AllAppsRecyclerView mainRecyclerView;
final AllAppsRecyclerView workRecyclerView;
if (mUsingTabs) {
mAH.get(AdapterHolder.MAIN).setup(mViewPager.getChildAt(0), mPersonalMatcher);
mAH.get(AdapterHolder.WORK).setup(mViewPager.getChildAt(1), mWorkManager.getMatcher());
mAH.get(AdapterHolder.WORK).mRecyclerView.setId(R.id.apps_list_view_work);
if (ENABLE_ALL_APPS_RV_PREINFLATION.get()) {
// Let main and work rv share same view pool.
((RecyclerView) mViewPager.getChildAt(0))
.setRecycledViewPool(mAllAppsStore.getRecyclerViewPool());
((RecyclerView) mViewPager.getChildAt(1))
.setRecycledViewPool(mAllAppsStore.getRecyclerViewPool());
}
mainRecyclerView = (AllAppsRecyclerView) mViewPager.getChildAt(0);
workRecyclerView = (AllAppsRecyclerView) mViewPager.getChildAt(1);
mAH.get(AdapterHolder.MAIN).setup(mainRecyclerView, mPersonalMatcher);
mAH.get(AdapterHolder.WORK).setup(workRecyclerView, mWorkManager.getMatcher());
workRecyclerView.setId(R.id.apps_list_view_work);
if (FeatureFlags.ENABLE_EXPANDING_PAUSE_WORK_BUTTON.get()) {
mAH.get(AdapterHolder.WORK).mRecyclerView.addOnScrollListener(
mWorkManager.newScrollListener());
@@ -587,13 +585,15 @@ public class ActivityAllAppsContainerView<T extends Context & ActivityContext>
onActivePageChanged(mViewPager.getNextPage());
}
} else {
mAH.get(AdapterHolder.MAIN).setup(findViewById(R.id.apps_list_view), null);
mainRecyclerView = findViewById(R.id.apps_list_view);
workRecyclerView = null;
mAH.get(AdapterHolder.MAIN).setup(mainRecyclerView, null);
mAH.get(AdapterHolder.WORK).mRecyclerView = null;
if (ENABLE_ALL_APPS_RV_PREINFLATION.get()) {
mAH.get(AdapterHolder.MAIN).mRecyclerView
.setRecycledViewPool(mAllAppsStore.getRecyclerViewPool());
}
}
setUpCustomRecyclerViewPool(
mainRecyclerView,
workRecyclerView,
mAllAppsStore.getRecyclerViewPool());
setupHeader();
if (isSearchBarFloating()) {
@@ -610,6 +610,30 @@ public class ActivityAllAppsContainerView<T extends Context & ActivityContext>
mAllAppsStore.registerIconContainer(mAH.get(AdapterHolder.SEARCH).mRecyclerView);
}
/**
* If {@link ENABLE_ALL_APPS_RV_PREINFLATION} is enabled, wire custom
* {@link RecyclerView.RecycledViewPool} to main and work {@link AllAppsRecyclerView}.
*
* Then if {@link ALL_APPS_GONE_VISIBILITY} is enabled, update max pool size. This is because
* all apps rv's hidden visibility is changed to {@link View#GONE} from {@link View#INVISIBLE),
* thus we cannot rely on layout pass to update pool size.
*/
private static void setUpCustomRecyclerViewPool(
@NonNull AllAppsRecyclerView mainRecyclerView,
@Nullable AllAppsRecyclerView workRecyclerView,
@NonNull RecyclerView.RecycledViewPool recycledViewPool) {
if (!ENABLE_ALL_APPS_RV_PREINFLATION.get()) {
return;
}
mainRecyclerView.setRecycledViewPool(recycledViewPool);
if (workRecyclerView != null) {
workRecyclerView.setRecycledViewPool(recycledViewPool);
}
if (ALL_APPS_GONE_VISIBILITY.get()) {
mainRecyclerView.updatePoolSize();
}
}
private void replaceAppsRVContainer(boolean showTabs) {
for (int i = AdapterHolder.MAIN; i <= AdapterHolder.WORK; i++) {
AdapterHolder adapterHolder = mAH.get(i);
@@ -15,6 +15,7 @@
*/
package com.android.launcher3.allapps;
import static com.android.launcher3.config.FeatureFlags.ALL_APPS_GONE_VISIBILITY;
import static com.android.launcher3.logger.LauncherAtom.ContainerInfo;
import static com.android.launcher3.logger.LauncherAtom.SearchResultContainer;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_PERSONAL_SCROLLED_DOWN;
@@ -26,6 +27,8 @@ import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCH
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_VERTICAL_SWIPE_END;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WORK_FAB_BUTTON_COLLAPSE;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WORK_FAB_BUTTON_EXTEND;
import static com.android.launcher3.recyclerview.AllAppsRecyclerViewPoolKt.EXTRA_ICONS_COUNT;
import static com.android.launcher3.recyclerview.AllAppsRecyclerViewPoolKt.PREINFLATE_ICONS_ROW_COUNT;
import static com.android.launcher3.util.LogConfig.SEARCH_LOGGING;
import android.content.Context;
@@ -96,8 +99,18 @@ public class AllAppsRecyclerView extends FastScrollRecyclerView {
int approxRows = (int) Math.ceil(grid.availableHeightPx / grid.allAppsIconSizePx);
pool.setMaxRecycledViews(AllAppsGridAdapter.VIEW_TYPE_EMPTY_SEARCH, 1);
pool.setMaxRecycledViews(AllAppsGridAdapter.VIEW_TYPE_ALL_APPS_DIVIDER, 1);
// If all apps' hidden visibility is INVISIBLE, we will need to preinflate one page of
// all apps icons for smooth scrolling.
int maxPoolSizeForAppIcons = (approxRows + 1) * grid.numShownAllAppsColumns;
if (ALL_APPS_GONE_VISIBILITY.get()) {
// If all apps' hidden visibility is GONE, we need to increase prefinated icons number
// by [PREINFLATE_ICONS_ROW_COUNT] rows + [EXTRA_ICONS_COUNT] for fast opening all apps.
maxPoolSizeForAppIcons +=
PREINFLATE_ICONS_ROW_COUNT * grid.numShownAllAppsColumns + EXTRA_ICONS_COUNT;
}
pool.setMaxRecycledViews(
AllAppsGridAdapter.VIEW_TYPE_ICON, (approxRows + 1) * grid.numShownAllAppsColumns);
AllAppsGridAdapter.VIEW_TYPE_ICON, maxPoolSizeForAppIcons);
}
@Override
@@ -438,7 +438,8 @@ public class AllAppsTransitionController
mAppsView = appsView;
mAppsView.setScrimView(scrimView);
mAppsViewAlpha = new MultiValueAlpha(mAppsView, APPS_VIEW_INDEX_COUNT);
mAppsViewAlpha = new MultiValueAlpha(mAppsView, APPS_VIEW_INDEX_COUNT,
FeatureFlags.ALL_APPS_GONE_VISIBILITY.get() ? View.GONE : View.INVISIBLE);
mAppsViewAlpha.setUpdateVisibility(true);
mAppsViewTranslationY = new MultiPropertyFactory<>(
mAppsView, VIEW_TRANSLATE_Y, APPS_VIEW_INDEX_COUNT, Float::sum);
@@ -53,8 +53,18 @@ public class AlphaUpdateListener extends AnimatorListenerAdapter
}
public static void updateVisibility(View view) {
if (view.getAlpha() < ALPHA_CUTOFF_THRESHOLD && view.getVisibility() != View.INVISIBLE) {
view.setVisibility(View.INVISIBLE);
updateVisibility(view, View.INVISIBLE);
}
/**
* Update view's visibility.
*
* @param view View that needs to update visibility.
* @param hiddenVisibility {@link View#GONE} or {@link View#INVISIBLE}
*/
public static void updateVisibility(View view, int hiddenVisibility) {
if (view.getAlpha() < ALPHA_CUTOFF_THRESHOLD && view.getVisibility() != hiddenVisibility) {
view.setVisibility(hiddenVisibility);
} else if (view.getAlpha() > ALPHA_CUTOFF_THRESHOLD
&& view.getVisibility() != View.VISIBLE) {
if (view instanceof ViewGroup) {
@@ -83,11 +83,11 @@ public final class FeatureFlags {
*/
// TODO(Block 1): Clean up flags
public static final BooleanFlag ENABLE_SEARCH_RESULT_BACKGROUND_DRAWABLES = getReleaseFlag(
270394041, "ENABLE_SEARCH_RESULT_BACKGROUND_DRAWABLES", DISABLED,
270394041, "ENABLE_SEARCH_RESULT_BACKGROUND_DRAWABLES", TEAMFOOD,
"Enable option to replace decorator-based search result backgrounds with drawables");
public static final BooleanFlag ENABLE_SEARCH_RESULT_LAUNCH_TRANSITION = getReleaseFlag(
270394392, "ENABLE_SEARCH_RESULT_LAUNCH_TRANSITION", DISABLED,
270394392, "ENABLE_SEARCH_RESULT_LAUNCH_TRANSITION", TEAMFOOD,
"Enable option to launch search results using the new view container transitions");
// TODO(Block 2): Clean up flags
@@ -395,10 +395,15 @@ public final class FeatureFlags {
// TODO(Block 33): Clean up flags
public static final BooleanFlag ENABLE_ALL_APPS_RV_PREINFLATION = getDebugFlag(288161355,
"ENABLE_ALL_APPS_RV_PREINFLATION", DISABLED,
"ENABLE_ALL_APPS_RV_PREINFLATION", ENABLED,
"Enables preinflating all apps icons to avoid scrolling jank.");
// TODO(Block 34): Empty block
// TODO(Block 34): Clean up flags
public static final BooleanFlag ALL_APPS_GONE_VISIBILITY = getDebugFlag(291651514,
"ALL_APPS_GONE_VISIBILITY", ENABLED,
"Set all apps container view's hidden visibility to GONE instead of INVISIBLE.");
// TODO(Block 35): Empty block
public static class BooleanFlag {
@@ -49,7 +49,6 @@ import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LauncherSettings.Animation;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.Workspace;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.logger.LauncherAtom;
import com.android.launcher3.logger.LauncherAtom.AllAppsContainer;
import com.android.launcher3.logger.LauncherAtom.ContainerInfo;
@@ -323,9 +322,7 @@ public class ItemInfo {
* Returns whether this item should use the background animation.
*/
public boolean shouldUseBackgroundAnimation() {
return animationType == LauncherSettings.Animation.VIEW_BACKGROUND
&& FeatureFlags.ENABLE_SEARCH_RESULT_BACKGROUND_DRAWABLES.get()
&& FeatureFlags.ENABLE_SEARCH_RESULT_LAUNCH_TRANSITION.get();
return animationType == LauncherSettings.Animation.VIEW_BACKGROUND;
}
/**
@@ -22,13 +22,14 @@ import androidx.recyclerview.widget.RecyclerView.RecycledViewPool
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.android.launcher3.BubbleTextView
import com.android.launcher3.allapps.BaseAllAppsAdapter
import com.android.launcher3.config.FeatureFlags
import com.android.launcher3.util.Executors.MAIN_EXECUTOR
import com.android.launcher3.util.Executors.VIEW_PREINFLATION_EXECUTOR
import com.android.launcher3.views.ActivityContext
import java.util.concurrent.Future
private const val PREINFLATE_ICONS_ROW_COUNT = 4
private const val EXTRA_ICONS_COUNT = 2
const val PREINFLATE_ICONS_ROW_COUNT = 4
const val EXTRA_ICONS_COUNT = 2
/**
* An [RecycledViewPool] that preinflates app icons ([ViewHolder] of [BubbleTextView]) of all apps
@@ -81,11 +82,21 @@ class AllAppsRecyclerViewPool<T> : RecycledViewPool() {
* After testing on phone, foldable and tablet, we found [PREINFLATE_ICONS_ROW_COUNT] rows of
* app icons plus [EXTRA_ICONS_COUNT] is the magic minimal count of app icons to preinflate to
* suffice fast scrolling.
*
* Note that if [FeatureFlags.ALL_APPS_GONE_VISIBILITY] is enabled, we need to preinfate extra
* app icons in size of one all apps pages, so that opening all apps don't need to inflate app
* icons.
*/
fun <T> getPreinflateCount(context: T): Int where T : Context, T : ActivityContext {
val targetPreinflateCount =
var targetPreinflateCount =
PREINFLATE_ICONS_ROW_COUNT * context.deviceProfile.numShownAllAppsColumns +
EXTRA_ICONS_COUNT
if (FeatureFlags.ALL_APPS_GONE_VISIBILITY.get()) {
val grid = ActivityContext.lookupContext<T>(context).deviceProfile
val approxRows =
Math.ceil((grid.availableHeightPx / grid.allAppsIconSizePx).toDouble()).toInt()
targetPreinflateCount += (approxRows + 1) * grid.numShownAllAppsColumns
}
val existingPreinflateCount = getRecycledViewCount(BaseAllAppsAdapter.VIEW_TYPE_ICON)
return targetPreinflateCount - existingPreinflateCount
}
@@ -32,8 +32,15 @@ public class MultiValueAlpha extends MultiPropertyFactory<View> {
// Whether we should change from INVISIBLE to VISIBLE and vice versa at low alpha values.
private boolean mUpdateVisibility;
private final int mHiddenVisibility;
public MultiValueAlpha(View view, int size) {
this(view, size, View.INVISIBLE);
}
public MultiValueAlpha(View view, int size, int hiddenVisibility) {
super(view, VIEW_ALPHA, size, ALPHA_AGGREGATOR, 1f);
this.mHiddenVisibility = hiddenVisibility;
}
/** Sets whether we should update between INVISIBLE and VISIBLE based on alpha. */
@@ -45,7 +52,7 @@ public class MultiValueAlpha extends MultiPropertyFactory<View> {
protected void apply(float value) {
super.apply(value);
if (mUpdateVisibility) {
AlphaUpdateListener.updateVisibility(mTarget);
AlphaUpdateListener.updateVisibility(mTarget, mHiddenVisibility);
}
}
}
@@ -24,8 +24,7 @@ import static com.android.launcher3.LauncherAnimUtils.TABLET_BOTTOM_SHEET_SUCCES
import static com.android.launcher3.allapps.AllAppsTransitionController.REVERT_SWIPE_ALL_APPS_TO_HOME_ANIMATION_DURATION_MS;
import static com.android.launcher3.util.ScrollableLayoutManager.PREDICTIVE_BACK_MIN_SCALE;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.content.Context;
@@ -51,6 +50,7 @@ import com.android.app.animation.Interpolators;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatedFloat;
import com.android.launcher3.anim.AnimatorListeners;
import com.android.launcher3.touch.BaseSwipeDetector;
import com.android.launcher3.touch.SingleAxisSwipeDetector;
@@ -82,15 +82,19 @@ public abstract class AbstractSlideInView<T extends Context & ActivityContext>
protected static final float TRANSLATION_SHIFT_CLOSED = 1f;
protected static final float TRANSLATION_SHIFT_OPENED = 0f;
private static final float VIEW_NO_SCALE = 1f;
private static final int NO_DURATION = -1;
protected final T mActivityContext;
protected final SingleAxisSwipeDetector mSwipeDetector;
protected final ObjectAnimator mOpenCloseAnimator;
protected @NonNull AnimatorSet mOpenCloseAnimator;
private final ObjectAnimator mTranslationShiftAnimator;
protected ViewGroup mContent;
protected final View mColorScrim;
protected Interpolator mScrollInterpolator;
private Interpolator mScrollInterpolator;
private long mScrollDuration;
// range [0, 1], 0=> completely open, 1=> completely closed
protected float mTranslationShift = TRANSLATION_SHIFT_CLOSED;
@@ -104,8 +108,8 @@ public abstract class AbstractSlideInView<T extends Context & ActivityContext>
protected final AnimatedFloat mSlideInViewScale =
new AnimatedFloat(this::onScaleProgressChanged, VIEW_NO_SCALE);
protected boolean mIsBackProgressing;
@Nullable private Drawable mContentBackground;
@Nullable private View mContentBackgroundParentView;
private @Nullable Drawable mContentBackground;
private @Nullable View mContentBackgroundParentView;
protected final ViewOutlineProvider mViewOutlineProvider = new ViewOutlineProvider() {
@Override
@@ -124,21 +128,52 @@ public abstract class AbstractSlideInView<T extends Context & ActivityContext>
mActivityContext = ActivityContext.lookupContext(context);
mScrollInterpolator = Interpolators.SCROLL_CUBIC;
mScrollDuration = NO_DURATION;
mSwipeDetector = new SingleAxisSwipeDetector(context, this,
SingleAxisSwipeDetector.VERTICAL);
mOpenCloseAnimator = ObjectAnimator.ofPropertyValuesHolder(this);
mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mSwipeDetector.finishedScrolling();
announceAccessibilityChanges();
}
});
mOpenCloseAnimator = new AnimatorSet();
mTranslationShiftAnimator = ObjectAnimator.ofPropertyValuesHolder(this);
int scrimColor = getScrimColor(context);
mColorScrim = scrimColor != -1 ? createColorScrim(context, scrimColor) : null;
}
/**
* Sets up a {@link #mOpenCloseAnimator} for opening with default parameters.
*
* @see #setUpOpenCloseAnimator(float, Interpolator)
*/
protected final AnimatorSet setUpDefaultOpenAnimator() {
return setUpOpenCloseAnimator(TRANSLATION_SHIFT_OPENED, Interpolators.FAST_OUT_SLOW_IN);
}
/**
* Initializes a new {@link #mOpenCloseAnimator}.
* <p>
* Subclasses should override this method if they want to add more {@code Animator} instances
* to the set.
*
* @param translationShift translation shift to animate to.
* @param translationShiftInterpolator interpolator for {@link #mTranslationShiftAnimator}.
* @return {@link #mOpenCloseAnimator}
*/
protected AnimatorSet setUpOpenCloseAnimator(
float translationShift, Interpolator translationShiftInterpolator) {
mOpenCloseAnimator = new AnimatorSet();
mOpenCloseAnimator.addListener(AnimatorListeners.forEndCallback(() -> {
mSwipeDetector.finishedScrolling();
announceAccessibilityChanges();
}));
mTranslationShiftAnimator.setValues(PropertyValuesHolder.ofFloat(
TRANSLATION_SHIFT, translationShift));
mTranslationShiftAnimator.setInterpolator(translationShiftInterpolator);
mOpenCloseAnimator.play(mTranslationShiftAnimator);
return mOpenCloseAnimator;
}
protected void attachToContainer() {
if (mColorScrim != null) {
getPopupContainer().addView(mColorScrim);
@@ -309,16 +344,13 @@ public abstract class AbstractSlideInView<T extends Context & ActivityContext>
if ((mSwipeDetector.isFling(velocity) && velocity > 0)
|| mTranslationShift > successfulShiftThreshold) {
mScrollInterpolator = scrollInterpolatorForVelocity(velocity);
mOpenCloseAnimator.setDuration(BaseSwipeDetector.calculateDuration(
velocity, TRANSLATION_SHIFT_CLOSED - mTranslationShift));
mScrollDuration = BaseSwipeDetector.calculateDuration(
velocity, TRANSLATION_SHIFT_CLOSED - mTranslationShift);
close(true);
} else {
mOpenCloseAnimator.setValues(PropertyValuesHolder.ofFloat(
TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED));
mOpenCloseAnimator.setDuration(
BaseSwipeDetector.calculateDuration(velocity, mTranslationShift))
.setInterpolator(Interpolators.DECELERATE);
mOpenCloseAnimator.start();
setUpOpenCloseAnimator(TRANSLATION_SHIFT_OPENED, Interpolators.DECELERATE)
.setDuration(BaseSwipeDetector.calculateDuration(velocity, mTranslationShift))
.start();
}
}
@@ -344,23 +376,19 @@ public abstract class AbstractSlideInView<T extends Context & ActivityContext>
onCloseComplete();
return;
}
mOpenCloseAnimator.setValues(
PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_CLOSED));
mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mOpenCloseAnimator.removeListener(this);
onCloseComplete();
}
});
final Interpolator interpolator;
final long duration;
if (mSwipeDetector.isIdleState()) {
mOpenCloseAnimator
.setDuration(defaultDuration)
.setInterpolator(getIdleInterpolator());
interpolator = getIdleInterpolator();
duration = defaultDuration;
} else {
mOpenCloseAnimator.setInterpolator(mScrollInterpolator);
interpolator = mScrollInterpolator;
duration = mScrollDuration > NO_DURATION ? mScrollDuration : defaultDuration;
}
mOpenCloseAnimator.start();
setUpOpenCloseAnimator(TRANSLATION_SHIFT_CLOSED, interpolator)
.addListener(AnimatorListeners.forEndCallback(this::onCloseComplete));
mOpenCloseAnimator.setDuration(duration).start();
}
protected Interpolator getIdleInterpolator() {
@@ -15,9 +15,6 @@
*/
package com.android.launcher3.views;
import static com.android.app.animation.Interpolators.FAST_OUT_SLOW_IN;
import android.animation.PropertyValuesHolder;
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
@@ -123,10 +120,7 @@ public class WidgetsEduView extends AbstractSlideInView<Launcher> implements Ins
return;
}
mIsOpen = true;
mOpenCloseAnimator.setValues(
PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED));
mOpenCloseAnimator.setInterpolator(FAST_OUT_SLOW_IN);
mOpenCloseAnimator.start();
setUpDefaultOpenAnimator().start();
}
/** Shows widget education dialog. */
@@ -16,10 +16,8 @@
package com.android.launcher3.widget;
import static com.android.app.animation.Interpolators.FAST_OUT_SLOW_IN;
import static com.android.launcher3.Utilities.ATLEAST_R;
import android.animation.PropertyValuesHolder;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Insets;
@@ -134,10 +132,7 @@ public class AddItemWidgetsBottomSheet extends AbstractSlideInView<AddItemActivi
return;
}
mIsOpen = true;
mOpenCloseAnimator.setValues(
PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED));
mOpenCloseAnimator.setInterpolator(FAST_OUT_SLOW_IN);
mOpenCloseAnimator.start();
setUpDefaultOpenAnimator().start();
}
@Override
@@ -16,10 +16,8 @@
package com.android.launcher3.widget;
import static com.android.app.animation.Interpolators.FAST_OUT_SLOW_IN;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_BOTTOM_WIDGETS_TRAY;
import android.animation.PropertyValuesHolder;
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
@@ -231,10 +229,7 @@ public class WidgetsBottomSheet extends BaseWidgetSheet {
}
mIsOpen = true;
setupNavBarColor();
mOpenCloseAnimator.setValues(
PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED));
mOpenCloseAnimator.setInterpolator(FAST_OUT_SLOW_IN);
mOpenCloseAnimator.start();
setUpDefaultOpenAnimator().start();
}
@Override
@@ -22,9 +22,6 @@ import static com.android.launcher3.config.FeatureFlags.LARGE_SCREEN_WIDGET_PICK
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WIDGETSTRAY_SEARCHED;
import static com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.PropertyValuesHolder;
import android.content.Context;
import android.content.pm.LauncherApps;
import android.content.res.Configuration;
@@ -627,20 +624,14 @@ public class WidgetsFullSheet extends BaseWidgetSheet
mContent.setAlpha(0);
setTranslationShift(VERTICAL_START_POSITION);
}
mOpenCloseAnimator.setValues(
PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED));
mOpenCloseAnimator
.setDuration(mActivityContext.getDeviceProfile().bottomSheetOpenDuration)
.setInterpolator(AnimationUtils.loadInterpolator(
setUpOpenCloseAnimator(
TRANSLATION_SHIFT_OPENED,
AnimationUtils.loadInterpolator(
getContext(), android.R.interpolator.linear_out_slow_in));
mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mOpenCloseAnimator.removeListener(this);
}
});
post(() -> {
mOpenCloseAnimator.start();
mOpenCloseAnimator
.setDuration(mActivityContext.getDeviceProfile().bottomSheetOpenDuration)
.start();
mContent.animate().alpha(1).setDuration(FADE_IN_DURATION);
});
} else {
+4
View File
@@ -32,6 +32,7 @@
<receiver
android:name="com.android.launcher3.testcomponent.AppWidgetNoConfig"
android:exported="true"
android:icon="@drawable/test_widget_no_config_icon"
android:label="No Config">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
@@ -65,6 +66,7 @@
<receiver
android:name="com.android.launcher3.testcomponent.AppWidgetWithConfig"
android:exported="true"
android:icon="@drawable/test_widget_with_config_icon"
android:label="With Config">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
@@ -76,6 +78,7 @@
<receiver
android:name="com.android.launcher3.testcomponent.AppWidgetWithDialog"
android:exported="true"
android:icon="@drawable/test_widget_with_dialog_icon"
android:label="With Dialog">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
@@ -87,6 +90,7 @@
<receiver
android:name="com.android.launcher3.testcomponent.AppWidgetDynamicColors"
android:exported="true"
android:icon="@drawable/test_widget_dynamic_colors_icon"
android:label="Dynamic Colors">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
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.
-->
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@android:color/white"/>
<foreground>
<color android:color="#964B00"/>
</foreground>
<monochrome>
<vector android:width="48dp" android:height="48dp" android:viewportWidth="48.0" android:viewportHeight="48.0">
<path
android:fillColor="#FF000000"
android:pathData="M0,24L48,24 48,48, 0,48 Z"/>
</vector>
</monochrome>
</adaptive-icon>
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
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.
-->
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@android:color/white"/>
<foreground>
<color android:color="#00FFFF"/>
</foreground>
<monochrome>
<vector android:width="48dp" android:height="48dp" android:viewportWidth="48.0" android:viewportHeight="48.0">
<path
android:fillColor="#FF000000"
android:pathData="M0,24L48,24 48,48, 0,48 Z"/>
</vector>
</monochrome>
</adaptive-icon>
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
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.
-->
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@android:color/white"/>
<foreground>
<color android:color="#008000" />
</foreground>
<monochrome>
<vector android:width="48dp" android:height="48dp" android:viewportWidth="48.0" android:viewportHeight="48.0">
<path
android:fillColor="#FF000000"
android:pathData="M0,24L48,24 48,48, 0,48 Z"/>
</vector>
</monochrome>
</adaptive-icon>
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
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.
-->
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@android:color/white"/>
<foreground>
<color android:color="#800080"/>
</foreground>
<monochrome>
<vector android:width="48dp" android:height="48dp" android:viewportWidth="48.0" android:viewportHeight="48.0">
<path
android:fillColor="#FF000000"
android:pathData="M0,24L48,24 48,48, 0,48 Z"/>
</vector>
</monochrome>
</adaptive-icon>
@@ -318,6 +318,7 @@ public class TaplTestsLauncher3 extends AbstractLauncherUiTest {
}
@Test
@Ignore // b/293191790
@PortraitLandscape
public void testWidgets() throws Exception {
// Test opening widgets.
@@ -26,8 +26,13 @@ import com.android.app.viewcapture.data.ExportedData
import com.android.launcher3.tapl.TestHelpers
import com.android.launcher3.util.ActivityLifecycleCallbacksAdapter
import com.android.launcher3.util.viewcapture_analysis.ViewCaptureAnalyzer
import org.junit.Assert.assertTrue
import java.io.BufferedOutputStream
import java.io.FileOutputStream
import java.io.IOException
import java.io.OutputStreamWriter
import java.util.function.Supplier
import org.junit.Assert.assertTrue
import org.junit.Assert.fail
import org.junit.rules.TestRule
import org.junit.runner.Description
import org.junit.runners.model.Statement
@@ -81,7 +86,7 @@ class ViewCaptureRule(var alreadyOpenActivitySupplier: Supplier<Activity?>) : Te
MAIN_EXECUTOR.execute { windowListenerCloseables.onEach(SafeCloseable::close) }
}
analyzeViewCapture()
analyzeViewCapture(description)
}
private fun startCapturingExistingActivity(
@@ -107,16 +112,39 @@ class ViewCaptureRule(var alreadyOpenActivitySupplier: Supplier<Activity?>) : Te
}
}
private fun analyzeViewCapture() {
private fun analyzeViewCapture(description: Description) {
// OOP tests don't produce ViewCapture data
if (!TestHelpers.isInLauncherProcess()) return
ViewCaptureAnalyzer.assertNoAnomalies(viewCaptureData)
var frameCount = 0
for (i in 0 until viewCaptureData!!.windowDataCount) {
frameCount += viewCaptureData!!.getWindowData(i).frameDataCount
}
assertTrue("Empty ViewCapture data", frameCount > 0)
val anomalies: Map<String, String> = ViewCaptureAnalyzer.getAnomalies(viewCaptureData)
if (!anomalies.isEmpty()) {
val diagFile = FailureWatcher.diagFile(description, "ViewAnomalies", "txt")
try {
OutputStreamWriter(BufferedOutputStream(FileOutputStream(diagFile))).use { writer ->
writer.write("View animation anomalies detected.\r\n")
writer.write(
"To suppress an anomaly for a view, add its full path to the PATHS_TO_IGNORE list in the corresponding AnomalyDetector.\r\n"
)
writer.write("List of views with animation anomalies:\r\n")
for ((viewPath, message) in anomalies) {
writer.write("View: $viewPath\r\n $message\r\n")
}
}
} catch (ex: IOException) {
throw RuntimeException(ex)
}
val (viewPath, message) = anomalies.entries.first()
fail(
"${anomalies.size} view(s) had animation anomalies during the test, including view: $viewPath: $message\r\nSee ${diagFile.name} for details."
)
}
}
}
@@ -113,7 +113,7 @@ final class AlphaJumpDetector extends AnomalyDetector {
DRAG_LAYER + "WidgetsFullSheet|SpringRelativeLayout:id/container",
DRAG_LAYER + "WidgetsTwoPaneSheet|SpringRelativeLayout:id/container",
CONTENT + "LauncherRootView:id/launcher|FloatingIconView",
RECENTS_DRAG_LAYER + "ArrowTipView|View:id/arrow",
RECENTS_DRAG_LAYER + "ArrowTipView",
DRAG_LAYER + "FallbackRecentsView:id/overview_panel",
RECENTS_DRAG_LAYER + "FallbackRecentsView:id/overview_panel",
DRAG_LAYER
@@ -212,24 +212,26 @@ final class AlphaJumpDetector extends AnomalyDetector {
}
@Override
void detectAnomalies(AnalysisNode oldInfo, AnalysisNode newInfo, int frameN) {
String detectAnomalies(AnalysisNode oldInfo, AnalysisNode newInfo, int frameN) {
// If the view was previously seen, proceed with analysis only if it was present in the
// view hierarchy in the previous frame.
if (oldInfo != null && oldInfo.frameN != frameN) return;
if (oldInfo != null && oldInfo.frameN != frameN) return null;
final AnalysisNode latestInfo = newInfo != null ? newInfo : oldInfo;
if (getNodeData(latestInfo).ignoreAlphaJumps) return;
final NodeData nodeData = getNodeData(latestInfo);
if (nodeData.ignoreAlphaJumps) return null;
final float oldAlpha = oldInfo != null ? oldInfo.alpha : 0;
final float newAlpha = newInfo != null ? newInfo.alpha : 0;
final float alphaDeltaAbs = Math.abs(newAlpha - oldAlpha);
if (alphaDeltaAbs >= ALPHA_JUMP_THRESHOLD) {
throw new AssertionError(
String.format(
"Alpha jump detected in ViewCapture data: alpha change: %s (%s -> %s)"
+ ", threshold: %s, view: %s",
alphaDeltaAbs, oldAlpha, newAlpha, ALPHA_JUMP_THRESHOLD, latestInfo));
nodeData.ignoreAlphaJumps = true; // No need to report alpha jump in children.
return String.format(
"Alpha jump detected in ViewCapture data: alpha change: %s (%s -> %s)"
+ ", threshold: %s, %s", // ----------- no need to include view?
alphaDeltaAbs, oldAlpha, newAlpha, ALPHA_JUMP_THRESHOLD, latestInfo);
}
return null;
}
}
@@ -61,8 +61,9 @@ public class ViewCaptureAnalyzer {
* the view is not present in the 'currentFrame', but was present in earlier
* frames.
* @param frameN number of the current frame.
* @return Anomaly diagnostic message if an anomaly has been detected; null otherwise.
*/
abstract void detectAnomalies(
abstract String detectAnomalies(
@Nullable AnalysisNode oldInfo, @Nullable AnalysisNode newInfo, int frameN);
}
@@ -101,25 +102,31 @@ public class ViewCaptureAnalyzer {
@Override
public String toString() {
return String.format("window coordinates: (%s, %s), class path from the root: %s",
left, top, diagPathFromRoot(this));
return String.format("view window coordinates: (%s, %s)", left, top);
}
}
/**
* Scans a view capture record and throws an error if an anomaly is found.
* Scans a view capture record and searches for view animation anomalies. Can find anomalies for
* multiple views.
* Returns a map from the view path to the anomaly message for the view. Non-empty map means
* that anomalies were detected.
*/
public static void assertNoAnomalies(ExportedData viewCaptureData) {
public static Map<String, String> getAnomalies(ExportedData viewCaptureData) {
final Map<String, String> anomalies = new HashMap<>();
final int scrimClassIndex = viewCaptureData.getClassnameList().indexOf(SCRIM_VIEW_CLASS);
final int windowDataCount = viewCaptureData.getWindowDataCount();
for (int i = 0; i < windowDataCount; ++i) {
analyzeWindowData(viewCaptureData, viewCaptureData.getWindowData(i), scrimClassIndex);
analyzeWindowData(
viewCaptureData, viewCaptureData.getWindowData(i), scrimClassIndex, anomalies);
}
return anomalies;
}
private static void analyzeWindowData(ExportedData viewCaptureData, WindowData windowData,
int scrimClassIndex) {
int scrimClassIndex, Map<String, String> anomalies) {
// View hash code => Last seen node with this hash code.
// The view is added when we analyze the first frame where it's visible.
// After that, it gets updated for every frame where it's visible.
@@ -128,12 +135,13 @@ public class ViewCaptureAnalyzer {
for (int frameN = 0; frameN < windowData.getFrameDataCount(); ++frameN) {
analyzeFrame(frameN, windowData.getFrameData(frameN), viewCaptureData, lastSeenNodes,
scrimClassIndex);
scrimClassIndex, anomalies);
}
}
private static void analyzeFrame(int frameN, FrameData frame, ExportedData viewCaptureData,
Map<Integer, AnalysisNode> lastSeenNodes, int scrimClassIndex) {
Map<Integer, AnalysisNode> lastSeenNodes, int scrimClassIndex,
Map<String, String> anomalies) {
// Analyze the node tree starting from the root.
analyzeView(
frame.getNode(),
@@ -143,7 +151,8 @@ public class ViewCaptureAnalyzer {
/* topShift = */ 0,
viewCaptureData,
lastSeenNodes,
scrimClassIndex);
scrimClassIndex,
anomalies);
// Analyze transitions when a view visible in the last frame become invisible in the
// current one.
@@ -151,10 +160,14 @@ public class ViewCaptureAnalyzer {
if (info.frameN == frameN - 1) {
if (!info.viewCaptureNode.getWillNotDraw()) {
Arrays.stream(ANOMALY_DETECTORS).forEach(
detector -> detector.detectAnomalies(
/* oldInfo = */ info,
/* newInfo = */ null,
frameN));
detector ->
detectAnomaly(
detector,
frameN,
/* oldInfo = */ info,
/* newInfo = */ null,
anomalies)
);
}
}
}
@@ -162,7 +175,8 @@ public class ViewCaptureAnalyzer {
private static void analyzeView(ViewNode viewCaptureNode, AnalysisNode parent, int frameN,
float leftShift, float topShift, ExportedData viewCaptureData,
Map<Integer, AnalysisNode> lastSeenNodes, int scrimClassIndex) {
Map<Integer, AnalysisNode> lastSeenNodes, int scrimClassIndex,
Map<String, String> anomalies) {
// Skip analysis of invisible views
final float parentAlpha = parent != null ? parent.alpha : 1;
final float alpha = getVisibleAlpha(viewCaptureNode, parentAlpha);
@@ -205,7 +219,10 @@ public class ViewCaptureAnalyzer {
final AnalysisNode oldAnalysisNode = lastSeenNodes.get(hashcode); // may be null
if (frameN != 0 && !viewCaptureNode.getWillNotDraw()) {
Arrays.stream(ANOMALY_DETECTORS).forEach(
detector -> detector.detectAnomalies(oldAnalysisNode, newAnalysisNode, frameN));
detector ->
detectAnomaly(detector, frameN, oldAnalysisNode, newAnalysisNode,
anomalies)
);
}
lastSeenNodes.put(hashcode, newAnalysisNode);
@@ -220,8 +237,20 @@ public class ViewCaptureAnalyzer {
if (child.getClassnameIndex() == scrimClassIndex) break;
analyzeView(child, newAnalysisNode, frameN, leftShiftForChildren, topShiftForChildren,
viewCaptureData, lastSeenNodes,
scrimClassIndex);
viewCaptureData, lastSeenNodes, scrimClassIndex, anomalies);
}
}
private static void detectAnomaly(AnomalyDetector detector, int frameN,
AnalysisNode oldAnalysisNode, AnalysisNode newAnalysisNode,
Map<String, String> anomalies) {
final String maybeAnomaly =
detector.detectAnomalies(oldAnalysisNode, newAnalysisNode, frameN);
if (maybeAnomaly != null) {
final String viewDiagPath = diagPathFromRoot(newAnalysisNode);
if (!anomalies.containsKey(viewDiagPath)) {
anomalies.put(viewDiagPath, maybeAnomaly);
}
}
}
@@ -235,9 +264,11 @@ public class ViewCaptureAnalyzer {
return className.substring(className.lastIndexOf(".") + 1);
}
private static String diagPathFromRoot(AnalysisNode nodeBox) {
final StringBuilder path = new StringBuilder(nodeBox.nodeIdentity);
for (AnalysisNode ancestor = nodeBox.parent; ancestor != null; ancestor = ancestor.parent) {
private static String diagPathFromRoot(AnalysisNode analysisNode) {
final StringBuilder path = new StringBuilder(analysisNode.nodeIdentity);
for (AnalysisNode ancestor = analysisNode.parent;
ancestor != null;
ancestor = ancestor.parent) {
path.insert(0, ancestor.nodeIdentity + "|");
}
return path.toString();