Snap for 10594510 from a1877d2888 to udc-qpr1-release
Change-Id: Ibc38c32e450769dba2403327071a3c394c342146
This commit is contained in:
@@ -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) {
|
||||
|
||||
+10
-3
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+52
-21
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user