Merge "Fix ClassCastException in QuickstepModelDelegate" into sc-v2-dev
diff --git a/Android.bp b/Android.bp
index 8b7eb54..60ef5b1 100644
--- a/Android.bp
+++ b/Android.bp
@@ -204,6 +204,7 @@
],
static_libs: [
"Launcher3ResLib",
+ "lottie",
"SystemUISharedLib",
"SystemUI-statsd",
],
diff --git a/build.gradle b/build.gradle
index 617738a..683a4cf 100644
--- a/build.gradle
+++ b/build.gradle
@@ -163,6 +163,8 @@
androidTestImplementation 'com.android.support.test:rules:1.0.0'
androidTestImplementation 'com.android.support.test.uiautomator:uiautomator-v18:2.1.2'
androidTestImplementation "androidx.annotation:annotation:${ANDROID_X_VERSION}"
+
+ api 'com.airbnb.android:lottie:3.3.0'
}
protobuf {
diff --git a/quickstep/res/drawable/taskbar_icon_click_feedback_roundrect.xml b/quickstep/res/drawable/taskbar_icon_click_feedback_roundrect.xml
index d6160de..534f241 100644
--- a/quickstep/res/drawable/taskbar_icon_click_feedback_roundrect.xml
+++ b/quickstep/res/drawable/taskbar_icon_click_feedback_roundrect.xml
@@ -16,7 +16,7 @@
-->
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
- android:color="@color/taskbar_icon_selection_ripple">
+ android:color="@color/taskbar_nav_icon_selection_ripple">
<item android:id="@android:id/mask">
<shape android:shape="rectangle">
<solid android:color="@android:color/white" />
diff --git a/quickstep/res/layout/activity_allset.xml b/quickstep/res/layout/activity_allset.xml
index b4ee482..9ad10dc 100644
--- a/quickstep/res/layout/activity_allset.xml
+++ b/quickstep/res/layout/activity_allset.xml
@@ -25,82 +25,100 @@
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:layout_marginStart="@dimen/allset_page_margin_horizontal"
- android:layout_marginEnd="@dimen/allset_page_margin_horizontal"
- android:layoutDirection="locale"
- android:textDirection="locale"
android:id="@+id/content_view"
- android:forceHasOverlappingRendering="false"
- android:fitsSystemWindows="true" >
+ android:fitsSystemWindows="true">
- <ImageView
- android:id="@+id/icon"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="@dimen/allset_title_icon_margin_top"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- android:src="@drawable/ic_all_set"/>
-
- <TextView
- android:id="@+id/title"
- style="@style/TextAppearance.GestureTutorial.Feedback.Title"
+ <com.airbnb.lottie.LottieAnimationView
+ android:id="@+id/animated_background"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="@dimen/allset_title_margin_top"
- app:layout_constraintTop_toBottomOf="@id/icon"
- app:layout_constraintStart_toStartOf="parent"
- android:gravity="start"
- android:text="@string/allset_title"/>
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:scaleType="centerCrop"
- <TextView
- android:id="@+id/subtitle"
- style="@style/TextAppearance.GestureTutorial.Feedback.Subtitle"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_marginTop="@dimen/allset_subtitle_margin_top"
- app:layout_constraintTop_toBottomOf="@id/title"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintWidth_max="@dimen/allset_subtitle_width_max"
- android:gravity="start"
- android:text="@string/allset_description"/>
+ app:lottie_rawRes="@raw/all_set_page_bg"
+ app:lottie_autoPlay="true"
+ app:lottie_loop="true" />
- <androidx.constraintlayout.widget.Guideline
- android:id="@+id/navigation_settings_guideline_bottom"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
- app:layout_constraintGuide_percent="0.83" />
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginStart="@dimen/allset_page_margin_horizontal"
+ android:layout_marginEnd="@dimen/allset_page_margin_horizontal"
+ android:layoutDirection="locale"
+ android:textDirection="locale"
+ android:forceHasOverlappingRendering="false"
+ android:fitsSystemWindows="true" >
- <TextView
- android:id="@+id/navigation_settings"
- style="@style/TextAppearance.GestureTutorial.LinkText"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintBottom_toBottomOf="@id/navigation_settings_guideline_bottom"
- android:minHeight="48dp"
- android:background="?android:attr/selectableItemBackground"
- android:text="@string/allset_navigation_settings" />
+ <ImageView
+ android:id="@+id/icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/allset_title_icon_margin_top"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ android:src="@drawable/ic_all_set"/>
- <androidx.constraintlayout.widget.Guideline
- android:id="@+id/hint_guideline_bottom"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
- app:layout_constraintGuide_percent="0.94" />
+ <TextView
+ android:id="@+id/title"
+ style="@style/TextAppearance.GestureTutorial.Feedback.Title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/allset_title_margin_top"
+ app:layout_constraintTop_toBottomOf="@id/icon"
+ app:layout_constraintStart_toStartOf="parent"
+ android:gravity="start"
+ android:text="@string/allset_title"/>
- <TextView
- android:id="@+id/hint"
- style="@style/TextAppearance.GestureTutorial.Feedback.Subtitle"
- android:textSize="14sp"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintBottom_toBottomOf="@id/hint_guideline_bottom"
- android:text="@string/allset_hint"/>
+ <TextView
+ android:id="@+id/subtitle"
+ style="@style/TextAppearance.GestureTutorial.Feedback.Subtitle"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/allset_subtitle_margin_top"
+ app:layout_constraintTop_toBottomOf="@id/title"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintWidth_max="@dimen/allset_subtitle_width_max"
+ android:gravity="start"
+ android:text="@string/allset_description"/>
+
+ <androidx.constraintlayout.widget.Guideline
+ android:id="@+id/navigation_settings_guideline_bottom"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ app:layout_constraintGuide_percent="0.83" />
+
+ <TextView
+ android:id="@+id/navigation_settings"
+ style="@style/TextAppearance.GestureTutorial.LinkText"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintBottom_toBottomOf="@id/navigation_settings_guideline_bottom"
+ android:minHeight="48dp"
+ android:background="?android:attr/selectableItemBackground"
+ android:text="@string/allset_navigation_settings" />
+
+ <androidx.constraintlayout.widget.Guideline
+ android:id="@+id/hint_guideline_bottom"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ app:layout_constraintGuide_percent="0.94" />
+
+ <TextView
+ android:id="@+id/hint"
+ style="@style/TextAppearance.GestureTutorial.Feedback.Subtitle"
+ android:textSize="14sp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintBottom_toBottomOf="@id/hint_guideline_bottom"
+ android:text="@string/allset_hint"/>
+ </androidx.constraintlayout.widget.ConstraintLayout>
+
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>
\ No newline at end of file
diff --git a/quickstep/res/layout/taskbar_nav_button.xml b/quickstep/res/layout/taskbar_nav_button.xml
index 4ffb8d8..aea4885 100644
--- a/quickstep/res/layout/taskbar_nav_button.xml
+++ b/quickstep/res/layout/taskbar_nav_button.xml
@@ -15,7 +15,10 @@
-->
<ImageView
xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
android:layout_width="@dimen/taskbar_nav_buttons_size"
android:layout_height="@dimen/taskbar_nav_buttons_size"
android:background="@drawable/taskbar_icon_click_feedback_roundrect"
- android:scaleType="center"/>
\ No newline at end of file
+ android:scaleType="center"
+ android:tint="@color/taskbar_nav_icon_light_color"
+ tools:ignore="UseAppTint" />
\ No newline at end of file
diff --git a/quickstep/res/values/colors.xml b/quickstep/res/values/colors.xml
index f237d26..671a617 100644
--- a/quickstep/res/values/colors.xml
+++ b/quickstep/res/values/colors.xml
@@ -25,14 +25,14 @@
<!-- Taskbar -->
<color name="taskbar_background">@color/overview_scrim_dark</color>
- <color name="taskbar_icon_selection_ripple">#E0E0E0</color>
-
+ <color name="taskbar_nav_icon_selection_ripple">#E0E0E0</color>
+ <color name="taskbar_nav_icon_light_color">#ffffff</color>
+ <!-- The dark navigation button color is only used in the rare cases that taskbar isn't drawing
+ its background and the underlying app has requested dark buttons. -->
+ <color name="taskbar_nav_icon_dark_color">#99000000</color>
<color name="taskbar_stashed_handle_light_color">#EBffffff</color>
<color name="taskbar_stashed_handle_dark_color">#99000000</color>
- <color name="rotation_button_light_color">#FFF</color>
- <color name="rotation_button_dark_color">#99000000</color>
-
<!-- Gesture navigation tutorial -->
<color name="gesture_tutorial_back_arrow_color">#FFFFFFFF</color>
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index e48c9e8..4e6b7b9 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -28,6 +28,7 @@
<dimen name="task_menu_item_corner_radius">4dp</dimen>
<dimen name="task_menu_spacing">2dp</dimen>
<dimen name="task_menu_width_grid">234dp</dimen>
+ <dimen name="task_menu_horizontal_padding">8dp</dimen>
<dimen name="overview_proactive_row_height">48dp</dimen>
<dimen name="overview_proactive_row_bottom_margin">16dp</dimen>
diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
index 6e2d2a9..38e8e72 100644
--- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
@@ -301,7 +301,8 @@
mActionsView = findViewById(R.id.overview_actions_view);
RecentsView overviewPanel = (RecentsView) getOverviewPanel();
SplitSelectStateController controller =
- new SplitSelectStateController(mHandler, SystemUiProxy.INSTANCE.get(this));
+ new SplitSelectStateController(mHandler, SystemUiProxy.INSTANCE.get(this),
+ getStateManager(), getDepthController());
overviewPanel.init(mActionsView, controller);
mActionsView.setDp(getDeviceProfile());
mActionsView.updateVerticalMargin(SysUINavigationMode.getMode(this));
diff --git a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
index 9d70cfa..e1d89a1 100644
--- a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
+++ b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
@@ -44,7 +44,6 @@
import com.android.systemui.shared.system.BlurUtils;
import com.android.systemui.shared.system.WallpaperManagerCompat;
-import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.function.Consumer;
@@ -156,6 +155,10 @@
// Workaround for animating the depth when multiwindow mode changes.
private boolean mIgnoreStateChangesDuringMultiWindowAnimation = false;
+ // Hints that there is potentially content behind Launcher and that we shouldn't optimize by
+ // marking the launcher surface as opaque. Only used in certain Launcher states.
+ private boolean mHasContentBehindLauncher;
+
private View.OnAttachStateChangeListener mOnAttachListener;
public DepthController(Launcher l) {
@@ -199,6 +202,10 @@
mLauncher.getScrimView().addOpaquenessListener(mOpaquenessListener);
}
+ public void setHasContentBehindLauncher(boolean hasContentBehindLauncher) {
+ mHasContentBehindLauncher = hasContentBehindLauncher;
+ }
+
/**
* Sets if the underlying activity is started or not
*/
@@ -311,13 +318,14 @@
}
if (supportsBlur) {
- boolean opaque = mLauncher.getScrimView().isFullyOpaque();
+ boolean hasOpaqueBg = mLauncher.getScrimView().isFullyOpaque();
+ boolean isSurfaceOpaque = !mHasContentBehindLauncher && hasOpaqueBg;
- mCurrentBlur = !mCrossWindowBlursEnabled || mBlurDisabledForAppLaunch
+ mCurrentBlur = !mCrossWindowBlursEnabled || mBlurDisabledForAppLaunch || hasOpaqueBg
? 0 : (int) (depth * mMaxBlurRadius);
SurfaceControl.Transaction transaction = new SurfaceControl.Transaction()
.setBackgroundBlurRadius(mSurface, mCurrentBlur)
- .setOpaque(mSurface, opaque);
+ .setOpaque(mSurface, isSurfaceOpaque);
// Set early wake-up flags when we know we're executing an expensive operation, this way
// SurfaceFlinger will adjust its internal offsets to avoid jank.
diff --git a/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java
index 24a88a4..90c035f 100644
--- a/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java
@@ -46,16 +46,13 @@
}
};
- // Initialized in init.
- TaskbarControllers mControllers;
-
public FallbackTaskbarUIController(RecentsActivity recentsActivity) {
mRecentsActivity = recentsActivity;
}
@Override
protected void init(TaskbarControllers taskbarControllers) {
- mControllers = taskbarControllers;
+ super.init(taskbarControllers);
mRecentsActivity.setTaskbarUIController(this);
mRecentsActivity.getStateManager().addStateListener(mStateListener);
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index 1c0c773..7d23439 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -25,7 +25,6 @@
import android.util.Log;
import android.view.MotionEvent;
import android.view.TaskTransitionSpec;
-import android.view.View;
import android.view.WindowManagerGlobal;
import androidx.annotation.NonNull;
@@ -62,7 +61,6 @@
this::onStashedInAppChanged;
// Initialized in init.
- private TaskbarControllers mControllers;
private AnimatedFloat mTaskbarOverrideBackgroundAlpha;
private TaskbarKeyguardController mKeyguardController;
private final TaskbarLauncherStateController
@@ -83,7 +81,7 @@
@Override
protected void init(TaskbarControllers taskbarControllers) {
- mControllers = taskbarControllers;
+ super.init(taskbarControllers);
mTaskbarLauncherStateController.init(mControllers, mLauncher);
mTaskbarOverrideBackgroundAlpha = mControllers.taskbarDragLayerController
@@ -168,10 +166,6 @@
return mControllers.taskbarDragController.isDragging();
}
- public View getRootView() {
- return mControllers.taskbarActivityContext.getDragLayer();
- }
-
@Override
protected void onStashedInAppChanged() {
onStashedInAppChanged(mLauncher.getDeviceProfile());
@@ -251,4 +245,12 @@
mLauncher.logAppLaunch(mControllers.taskbarActivityContext.getStatsLogManager(), item,
instanceId);
}
+
+ @Override
+ public void setSystemGestureInProgress(boolean inProgress) {
+ super.setSystemGestureInProgress(inProgress);
+ // Launcher's ScrimView will draw the background throughout the gesture. But once the
+ // gesture ends, start drawing taskbar's background again since launcher might stop drawing.
+ forceHideBackground(inProgress);
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index 4b6dacd..f6e0426 100644
--- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -15,16 +15,12 @@
*/
package com.android.launcher3.taskbar;
-import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
-
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_A11Y;
-import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_A11Y_LONG_CLICK;
import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_BACK;
import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_HOME;
import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_IME_SWITCH;
import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_RECENTS;
-import static com.android.launcher3.taskbar.TaskbarViewController.ALPHA_INDEX_IME;
import static com.android.launcher3.taskbar.TaskbarViewController.ALPHA_INDEX_KEYGUARD;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
@@ -36,11 +32,11 @@
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
+import android.animation.ArgbEvaluator;
import android.animation.ObjectAnimator;
import android.annotation.DrawableRes;
import android.annotation.IdRes;
import android.annotation.LayoutRes;
-import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Rect;
import android.graphics.Region;
@@ -92,9 +88,8 @@
private static final int MASK_IME_SWITCHER_VISIBLE = FLAG_SWITCHER_SUPPORTED | FLAG_IME_VISIBLE;
- private View.OnLongClickListener mA11yLongClickListener;
private final ArrayList<StatePropertyHolder> mPropertyHolders = new ArrayList<>();
- private final ArrayList<View> mAllButtons = new ArrayList<>();
+ private final ArrayList<ImageView> mAllButtons = new ArrayList<>();
private int mState;
private final TaskbarActivityContext mContext;
@@ -103,11 +98,17 @@
// Used for IME+A11Y buttons
private final ViewGroup mEndContextualContainer;
private final ViewGroup mStartContextualContainer;
+ private final int mLightIconColor;
+ private final int mDarkIconColor;
private final AnimatedFloat mTaskbarNavButtonTranslationY = new AnimatedFloat(
this::updateNavButtonTranslationY);
private final AnimatedFloat mNavButtonTranslationYMultiplier = new AnimatedFloat(
this::updateNavButtonTranslationY);
+ private final AnimatedFloat mTaskbarNavButtonDarkIntensity = new AnimatedFloat(
+ this::updateNavButtonDarkIntensity);
+ private final AnimatedFloat mNavButtonDarkIntensityMultiplier = new AnimatedFloat(
+ this::updateNavButtonDarkIntensity);
private final RotationButtonListener mRotationButtonListener = new RotationButtonListener();
private final Rect mFloatingRotationButtonBounds = new Rect();
@@ -125,6 +126,9 @@
mNavButtonContainer = mNavButtonsView.findViewById(R.id.end_nav_buttons);
mEndContextualContainer = mNavButtonsView.findViewById(R.id.end_contextual_buttons);
mStartContextualContainer = mNavButtonsView.findViewById(R.id.start_contextual_buttons);
+
+ mLightIconColor = context.getColor(R.color.taskbar_nav_icon_light_color);
+ mDarkIconColor = context.getColor(R.color.taskbar_nav_icon_dark_color);
}
/**
@@ -135,16 +139,6 @@
mNavButtonsView.getLayoutParams().height = mContext.getDeviceProfile().taskbarSize;
mNavButtonTranslationYMultiplier.value = 1;
- mA11yLongClickListener = view -> {
- mControllers.navButtonController.onButtonClick(BUTTON_A11Y_LONG_CLICK);
- return true;
- };
-
- mPropertyHolders.add(new StatePropertyHolder(
- mControllers.taskbarViewController.getTaskbarIconAlpha()
- .getProperty(ALPHA_INDEX_IME),
- flags -> (flags & FLAG_IME_VISIBLE) == 0, MultiValueAlpha.VALUE, 1, 0));
-
boolean isThreeButtonNav = mContext.isThreeButtonNav();
// IME switcher
View imeSwitcherButton = addButton(R.drawable.ic_ime_switcher, BUTTON_IME_SWITCH,
@@ -196,7 +190,7 @@
}
// Animate taskbar background when any of these flags are enabled
- int flagsToShowBg = FLAG_IME_VISIBLE | FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE
+ int flagsToShowBg = FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE
| FLAG_NOTIFICATION_SHADE_EXPANDED;
mPropertyHolders.add(new StatePropertyHolder(
mControllers.taskbarDragLayerController.getNavbarBackgroundAlpha(),
@@ -278,7 +272,6 @@
mPropertyHolders.add(new StatePropertyHolder(mA11yButton,
flags -> (flags & FLAG_A11Y_VISIBLE) != 0
&& (flags & FLAG_ROTATION_BUTTON_VISIBLE) == 0));
- mA11yButton.setOnLongClickListener(mA11yLongClickListener);
}
private void parseSystemUiFlags(int sysUiStateFlags) {
@@ -379,6 +372,16 @@
return mTaskbarNavButtonTranslationY;
}
+ /** Use to set the dark intensity for the all nav+contextual buttons */
+ public AnimatedFloat getTaskbarNavButtonDarkIntensity() {
+ return mTaskbarNavButtonDarkIntensity;
+ }
+
+ /** Use to determine whether to use the dark intensity requested by the underlying app */
+ public AnimatedFloat getNavButtonDarkIntensityMultiplier() {
+ return mNavButtonDarkIntensityMultiplier;
+ }
+
/**
* Does not call {@link #applyState()}. Don't forget to!
*/
@@ -402,6 +405,16 @@
* mNavButtonTranslationYMultiplier.value);
}
+ private void updateNavButtonDarkIntensity() {
+ float darkIntensity = mTaskbarNavButtonDarkIntensity.value
+ * mNavButtonDarkIntensityMultiplier.value;
+ int iconColor = (int) ArgbEvaluator.getInstance().evaluate(darkIntensity, mLightIconColor,
+ mDarkIconColor);
+ for (ImageView button : mAllButtons) {
+ button.setImageTintList(ColorStateList.valueOf(iconColor));
+ }
+ }
+
private ImageView addButton(@DrawableRes int drawableId, @TaskbarButton int buttonType,
ViewGroup parent, TaskbarNavButtonController navButtonController, @IdRes int id) {
return addButton(drawableId, buttonType, parent, navButtonController, id,
@@ -414,6 +427,8 @@
ImageView buttonView = addButton(parent, id, layoutId);
buttonView.setImageResource(drawableId);
buttonView.setOnClickListener(view -> navButtonController.onButtonClick(buttonType));
+ buttonView.setOnLongClickListener(view ->
+ navButtonController.onButtonLongClick(buttonType));
return buttonView;
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
index 2c80f06..22ca63f 100644
--- a/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
@@ -16,6 +16,8 @@
package com.android.launcher3.taskbar;
import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.graphics.Outline;
@@ -23,8 +25,6 @@
import android.view.View;
import android.view.ViewOutlineProvider;
-import androidx.annotation.Nullable;
-
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.RevealOutlineAnimation;
@@ -66,7 +66,10 @@
private final Rect mStashedHandleBounds = new Rect();
private float mStashedHandleRadius;
- private boolean mIsAtStashedRevealBounds = true;
+ // When the reveal animation is cancelled, we can assume it's about to create a new animation,
+ // which should start off at the same point the cancelled one left off.
+ private float mStartProgressForNextRevealAnim;
+ private boolean mWasLastRevealAnimReversed;
public StashedHandleViewController(TaskbarActivityContext activity,
StashedHandleView stashedHandleView) {
@@ -148,15 +151,27 @@
* shape and size. When stashed, the shape is a thin rounded pill. When unstashed, the shape
* morphs into the size of where the taskbar icons will be.
*/
- public @Nullable Animator createRevealAnimToIsStashed(boolean isStashed) {
- if (mIsAtStashedRevealBounds == isStashed) {
- return null;
- }
- mIsAtStashedRevealBounds = isStashed;
+ public Animator createRevealAnimToIsStashed(boolean isStashed) {
final RevealOutlineAnimation handleRevealProvider = new RoundedRectRevealOutlineProvider(
mStashedHandleRadius, mStashedHandleRadius,
mControllers.taskbarViewController.getIconLayoutBounds(), mStashedHandleBounds);
- return handleRevealProvider.createRevealAnimator(mStashedHandleView, !isStashed);
+
+ boolean isReversed = !isStashed;
+ boolean changingDirection = mWasLastRevealAnimReversed != isReversed;
+ mWasLastRevealAnimReversed = isReversed;
+ if (changingDirection) {
+ mStartProgressForNextRevealAnim = 1f - mStartProgressForNextRevealAnim;
+ }
+
+ ValueAnimator revealAnim = handleRevealProvider.createRevealAnimator(mStashedHandleView,
+ isReversed, mStartProgressForNextRevealAnim);
+ revealAnim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mStartProgressForNextRevealAnim = ((ValueAnimator) animation).getAnimatedFraction();
+ }
+ });
+ return revealAnim;
}
public void onIsStashed(boolean isStashed) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index cc83431..2e1e5bb 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -20,7 +20,6 @@
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_OPEN;
-import static com.android.launcher3.testing.TestProtocol.TASKBAR_WINDOW_CRASH;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
import static com.android.systemui.shared.system.WindowManagerWrapper.ITYPE_BOTTOM_TAPPABLE_ELEMENT;
@@ -151,8 +150,8 @@
buttonController,
new NavbarButtonsViewController(this, navButtonsView),
new RotationButtonController(this,
- c.getColor(R.color.rotation_button_light_color),
- c.getColor(R.color.rotation_button_dark_color),
+ c.getColor(R.color.taskbar_nav_icon_light_color),
+ c.getColor(R.color.taskbar_nav_icon_dark_color),
R.drawable.ic_sysbar_rotate_button_ccw_start_0,
R.drawable.ic_sysbar_rotate_button_ccw_start_90,
R.drawable.ic_sysbar_rotate_button_cw_start_0,
@@ -204,7 +203,6 @@
updateSysuiStateFlags(sharedState.sysuiStateFlags, true /* fromInit */);
mWindowManager.addView(mDragLayer, mWindowLayoutParams);
- Log.d(TASKBAR_WINDOW_CRASH, "Adding taskbar window");
}
public boolean isThreeButtonNav() {
@@ -340,7 +338,6 @@
setUIController(TaskbarUIController.DEFAULT);
mControllers.onDestroy();
mWindowManager.removeViewImmediate(mDragLayer);
- Log.d(TASKBAR_WINDOW_CRASH, "Removing taskbar window");
}
public void updateSysuiStateFlags(int systemUiStateFlags, boolean fromInit) {
@@ -394,6 +391,11 @@
mControllers.rotationButtonController.onBehaviorChanged(displayId, behavior);
}
+ public void onNavButtonsDarkIntensityChanged(float darkIntensity) {
+ mControllers.navbarButtonsViewController.getTaskbarNavButtonDarkIntensity()
+ .updateValue(darkIntensity);
+ }
+
/**
* Updates the TaskbarContainer to MATCH_PARENT vs original Taskbar size.
*/
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
index 56730db..2d4942d 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
@@ -19,6 +19,9 @@
import com.android.systemui.shared.rotation.RotationButtonController;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* Hosts various taskbar controllers to facilitate passing between one another.
*/
@@ -43,6 +46,9 @@
/** Do not store this controller, as it may change at runtime. */
@NonNull public TaskbarUIController uiController = TaskbarUIController.DEFAULT;
+ private boolean mAreAllControllersInitialized;
+ private final List<Runnable> mPostInitCallbacks = new ArrayList<>();
+
public TaskbarControllers(TaskbarActivityContext taskbarActivityContext,
TaskbarDragController taskbarDragController,
TaskbarNavButtonController navButtonController,
@@ -81,6 +87,8 @@
* in constructors for now, as some controllers may still be waiting for init().
*/
public void init(TaskbarSharedState sharedState) {
+ mAreAllControllersInitialized = false;
+
taskbarDragController.init(this);
navbarButtonsViewController.init(this);
rotationButtonController.init();
@@ -92,6 +100,12 @@
stashedHandleViewController.init(this);
taskbarStashController.init(this, sharedState);
taskbarEduController.init(this);
+
+ mAreAllControllersInitialized = true;
+ for (Runnable postInitCallback : mPostInitCallbacks) {
+ postInitCallback.run();
+ }
+ mPostInitCallbacks.clear();
}
/**
@@ -108,4 +122,17 @@
stashedHandleViewController.onDestroy();
taskbarAutohideSuspendController.onDestroy();
}
+
+ /**
+ * If all controllers are already initialized, runs the given callback immediately. Otherwise,
+ * queues it to run after calling init() on all controllers. This should likely be used in any
+ * case where one controller is telling another controller to do something inside init().
+ */
+ public void runAfterInit(Runnable callback) {
+ if (mAreAllControllersInitialized) {
+ callback.run();
+ } else {
+ mPostInitCallbacks.add(callback);
+ }
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
index 8c6185c..806b390 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
@@ -44,6 +44,7 @@
private final AnimatedFloat mKeyguardBgTaskbar = new AnimatedFloat(this::updateBackgroundAlpha);
private final AnimatedFloat mNotificationShadeBgTaskbar = new AnimatedFloat(
this::updateBackgroundAlpha);
+ private final AnimatedFloat mImeBgTaskbar = new AnimatedFloat(this::updateBackgroundAlpha);
// Used to hide our background color when someone else (e.g. ScrimView) is handling it.
private final AnimatedFloat mBgOverride = new AnimatedFloat(this::updateBackgroundAlpha);
@@ -52,6 +53,9 @@
// Initialized in init.
private TaskbarControllers mControllers;
+ private AnimatedFloat mNavButtonDarkIntensityMultiplier;
+
+ private float mLastSetBackgroundAlpha;
public TaskbarDragLayerController(TaskbarActivityContext activity,
TaskbarDragLayer taskbarDragLayer) {
@@ -65,9 +69,13 @@
mControllers = controllers;
mTaskbarDragLayer.init(new TaskbarDragLayerCallbacks());
+ mNavButtonDarkIntensityMultiplier = mControllers.navbarButtonsViewController
+ .getNavButtonDarkIntensityMultiplier();
+
mBgTaskbar.value = 1;
mKeyguardBgTaskbar.value = 1;
mNotificationShadeBgTaskbar.value = 1;
+ mImeBgTaskbar.value = 1;
mBgOverride.value = 1;
updateBackgroundAlpha();
}
@@ -102,6 +110,10 @@
return mNotificationShadeBgTaskbar;
}
+ public AnimatedFloat getImeBgTaskbar() {
+ return mImeBgTaskbar;
+ }
+
public AnimatedFloat getOverrideBackgroundAlpha() {
return mBgOverride;
}
@@ -113,14 +125,23 @@
private void updateBackgroundAlpha() {
final float bgNavbar = mBgNavbar.value;
final float bgTaskbar = mBgTaskbar.value * mKeyguardBgTaskbar.value
- * mNotificationShadeBgTaskbar.value;
- mTaskbarDragLayer.setTaskbarBackgroundAlpha(
- mBgOverride.value * Math.max(bgNavbar, bgTaskbar)
- );
+ * mNotificationShadeBgTaskbar.value * mImeBgTaskbar.value;
+ mLastSetBackgroundAlpha = mBgOverride.value * Math.max(bgNavbar, bgTaskbar);
+ mTaskbarDragLayer.setTaskbarBackgroundAlpha(mLastSetBackgroundAlpha);
+
+ updateNavBarDarkIntensityMultiplier();
}
private void updateBackgroundOffset() {
mTaskbarDragLayer.setTaskbarBackgroundOffset(mBgOffset.value);
+
+ updateNavBarDarkIntensityMultiplier();
+ }
+
+ private void updateNavBarDarkIntensityMultiplier() {
+ // Zero out the app-requested dark intensity when we're drawing our own background.
+ float effectiveBgAlpha = mLastSetBackgroundAlpha * (1 - mBgOffset.value);
+ mNavButtonDarkIntensityMultiplier.updateValue(1 - effectiveBgAlpha);
}
/**
@@ -143,8 +164,7 @@
// Let touches pass through us.
insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
} else if (mControllers.navbarButtonsViewController.isImeVisible()) {
- insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_CONTENT);
- insetsIsTouchableRegion = false;
+ insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
} else if (!mControllers.uiController.isTaskbarTouchable()) {
// Let touches pass through us.
insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
index 2693bc3..152b255 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
@@ -115,6 +115,7 @@
mIconAlignmentForGestureState.finishAnimation();
mIconAlignmentForLauncherState.finishAnimation();
+ mIconAlphaForHome.setConsumer(null);
mLauncher.getHotseat().setIconsAlpha(1f);
mLauncher.getStateManager().removeStateListener(mStateListener);
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index b2b078c..a65cc56 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -18,7 +18,6 @@
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
-import static com.android.launcher3.testing.TestProtocol.TASKBAR_WINDOW_CRASH;
import static com.android.launcher3.util.DisplayController.CHANGE_ACTIVE_SCREEN;
import static com.android.launcher3.util.DisplayController.CHANGE_DENSITY;
import static com.android.launcher3.util.DisplayController.CHANGE_SUPPORTED_BOUNDS;
@@ -42,7 +41,6 @@
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.statemanager.StatefulActivity;
-import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.DisplayController.Info;
import com.android.launcher3.util.SettingsCache;
@@ -207,8 +205,6 @@
}
private void recreateTaskbar() {
- Log.d(TASKBAR_WINDOW_CRASH, "Recreating taskbar: mTaskbarActivityContext="
- + mTaskbarActivityContext);
destroyExistingTaskbar();
DeviceProfile dp =
@@ -231,7 +227,6 @@
mTaskbarActivityContext.setUIController(
createTaskbarUIControllerForActivity(mActivity));
}
- Log.d(TASKBAR_WINDOW_CRASH, "Finished recreating taskbar");
}
public void onSystemUiFlagsChanged(int systemUiStateFlags) {
@@ -269,6 +264,12 @@
}
}
+ public void onNavButtonsDarkIntensityChanged(float darkIntensity) {
+ if (mTaskbarActivityContext != null) {
+ mTaskbarActivityContext.onNavButtonsDarkIntensityChanged(darkIntensity);
+ }
+ }
+
/**
* Called when the manager is no longer needed
*/
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
index 6dcfe56..37a9b5e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
@@ -184,9 +184,12 @@
}
mContainer.updateHotseatItems(hotseatItemInfos);
- mControllers.taskbarStashController.updateStateForFlag(
- TaskbarStashController.FLAG_STASHED_IN_APP_EMPTY, isHotseatEmpty);
- mControllers.taskbarStashController.applyState();
+ final boolean finalIsHotseatEmpty = isHotseatEmpty;
+ mControllers.runAfterInit(() -> {
+ mControllers.taskbarStashController.updateStateForFlag(
+ TaskbarStashController.FLAG_STASHED_IN_APP_EMPTY, finalIsHotseatEmpty);
+ mControllers.taskbarStashController.applyState();
+ });
}
@Override
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
index a8a0b59..ae23eda 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
@@ -16,9 +16,11 @@
package com.android.launcher3.taskbar;
-import static android.view.Display.DEFAULT_DISPLAY;
-import android.view.inputmethod.InputMethodManager;
+import static com.android.internal.app.AssistUtils.INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS;
+import static com.android.internal.app.AssistUtils.INVOCATION_TYPE_KEY;
+
+import android.os.Bundle;
import androidx.annotation.IntDef;
@@ -35,11 +37,9 @@
* Controller for 3 button mode in the taskbar.
* Handles all the functionality of the various buttons, making/routing the right calls into
* launcher or sysui/system.
- *
- * TODO: Create callbacks to hook into UI layer since state will change for more context buttons/
- * assistant invocation.
*/
public class TaskbarNavButtonController {
+
@Retention(RetentionPolicy.SOURCE)
@IntDef(value = {
BUTTON_BACK,
@@ -47,7 +47,6 @@
BUTTON_RECENTS,
BUTTON_IME_SWITCH,
BUTTON_A11Y,
- BUTTON_A11Y_LONG_CLICK
})
public @interface TaskbarButton {}
@@ -57,7 +56,6 @@
static final int BUTTON_RECENTS = BUTTON_HOME << 1;
static final int BUTTON_IME_SWITCH = BUTTON_RECENTS << 1;
static final int BUTTON_A11Y = BUTTON_IME_SWITCH << 1;
- static final int BUTTON_A11Y_LONG_CLICK = BUTTON_A11Y << 1;
private final TouchInteractionService mService;
@@ -82,9 +80,22 @@
case BUTTON_A11Y:
notifyImeClick(false /* longClick */);
break;
- case BUTTON_A11Y_LONG_CLICK:
+ }
+ }
+
+ public boolean onButtonLongClick(@TaskbarButton int buttonType) {
+ switch (buttonType) {
+ case BUTTON_HOME:
+ startAssistant();
+ return true;
+ case BUTTON_A11Y:
notifyImeClick(true /* longClick */);
- break;
+ return true;
+ case BUTTON_BACK:
+ case BUTTON_IME_SWITCH:
+ case BUTTON_RECENTS:
+ default:
+ return false;
}
}
@@ -113,4 +124,11 @@
systemUiProxy.notifyAccessibilityButtonClicked(mService.getDisplayId());
}
}
+
+ private void startAssistant() {
+ Bundle args = new Bundle();
+ args.putInt(INVOCATION_TYPE_KEY, INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS);
+ SystemUiProxy systemUiProxy = SystemUiProxy.INSTANCE.getNoCreate();
+ systemUiProxy.startAssistant(args);
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index acb4aa8..8965dc4 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -19,6 +19,7 @@
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_LONGPRESS_HIDE;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_LONGPRESS_SHOW;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
import android.animation.Animator;
@@ -48,11 +49,13 @@
public static final int FLAG_STASHED_IN_APP_PINNED = 1 << 2; // app pinning
public static final int FLAG_STASHED_IN_APP_EMPTY = 1 << 3; // no hotseat icons
public static final int FLAG_STASHED_IN_APP_SETUP = 1 << 4; // setup wizard and AllSetActivity
- public static final int FLAG_IN_STASHED_LAUNCHER_STATE = 1 << 5;
+ public static final int FLAG_STASHED_IN_APP_IME = 1 << 5; // IME is visible
+ public static final int FLAG_IN_STASHED_LAUNCHER_STATE = 1 << 6;
// If we're in an app and any of these flags are enabled, taskbar should be stashed.
public static final int FLAGS_STASHED_IN_APP = FLAG_STASHED_IN_APP_MANUAL
- | FLAG_STASHED_IN_APP_PINNED | FLAG_STASHED_IN_APP_EMPTY | FLAG_STASHED_IN_APP_SETUP;
+ | FLAG_STASHED_IN_APP_PINNED | FLAG_STASHED_IN_APP_EMPTY | FLAG_STASHED_IN_APP_SETUP
+ | FLAG_STASHED_IN_APP_IME;
/**
* How long to stash/unstash when manually invoked via long press.
@@ -60,6 +63,11 @@
public static final long TASKBAR_STASH_DURATION = 300;
/**
+ * How long to stash/unstash when keyboard is appearing/disappearing.
+ */
+ private static final long TASKBAR_STASH_DURATION_FOR_IME = 80;
+
+ /**
* The scale TaskbarView animates to when being stashed.
*/
private static final float STASHED_TASKBAR_SCALE = 0.5f;
@@ -100,6 +108,7 @@
private TaskbarControllers mControllers;
// Taskbar background properties.
private AnimatedFloat mTaskbarBackgroundOffset;
+ private AnimatedFloat mTaskbarImeBgAlpha;
// TaskbarView icon properties.
private AlphaProperty mIconAlphaForStash;
private AnimatedFloat mIconScaleForStash;
@@ -113,6 +122,8 @@
private int mState;
private @Nullable AnimatorSet mAnimator;
+ private boolean mIsSystemGestureInProgress;
+ private boolean mIsImeShowing;
// Evaluate whether the handle should be stashed
private final StatePropertyHolder mStatePropertyHolder = new StatePropertyHolder(
@@ -137,6 +148,7 @@
TaskbarDragLayerController dragLayerController = controllers.taskbarDragLayerController;
mTaskbarBackgroundOffset = dragLayerController.getTaskbarBackgroundOffset();
+ mTaskbarImeBgAlpha = dragLayerController.getImeBgTaskbar();
TaskbarViewController taskbarViewController = controllers.taskbarViewController;
mIconAlphaForStash = taskbarViewController.getTaskbarIconAlpha().getProperty(
@@ -271,17 +283,27 @@
* Create a stash animation and save to {@link #mAnimator}.
* @param isStashed whether it's a stash animation or an unstash animation
* @param duration duration of the animation
+ * @param startDelay how many milliseconds to delay the animation after starting it.
*/
- private void createAnimToIsStashed(boolean isStashed, long duration) {
+ private void createAnimToIsStashed(boolean isStashed, long duration, long startDelay) {
if (mAnimator != null) {
mAnimator.cancel();
}
mAnimator = new AnimatorSet();
if (!supportsVisualStashing()) {
- // Just hide/show the icons instead of stashing into a handle.
+ // Just hide/show the icons and background instead of stashing into a handle.
mAnimator.play(mIconAlphaForStash.animateToValue(isStashed ? 0 : 1)
.setDuration(duration));
+ mAnimator.play(mTaskbarImeBgAlpha.animateToValue(
+ hasAnyFlag(FLAG_STASHED_IN_APP_IME) ? 0 : 1).setDuration(duration));
+ mAnimator.setStartDelay(startDelay);
+ mAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mAnimator = null;
+ }
+ });
return;
}
@@ -326,11 +348,8 @@
);
}
- Animator stashedHandleRevealAnim = mControllers.stashedHandleViewController
- .createRevealAnimToIsStashed(isStashed);
- if (stashedHandleRevealAnim != null) {
- fullLengthAnimatorSet.play(stashedHandleRevealAnim);
- }
+ fullLengthAnimatorSet.play(mControllers.stashedHandleViewController
+ .createRevealAnimToIsStashed(isStashed));
// Return the stashed handle to its default scale in case it was changed as part of the
// feedforward hint. Note that the reveal animation above also visually scales it.
fullLengthAnimatorSet.play(mTaskbarStashedHandleHintScale.animateToValue(1f));
@@ -342,6 +361,7 @@
mAnimator.playTogether(fullLengthAnimatorSet, firstHalfAnimatorSet,
secondHalfAnimatorSet);
+ mAnimator.setStartDelay(startDelay);
mAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
@@ -400,6 +420,10 @@
mStatePropertyHolder.setState(mState, duration, true);
}
+ public void applyState(long duration, long startDelay) {
+ mStatePropertyHolder.setState(mState, duration, startDelay, true);
+ }
+
public Animator applyStateWithoutStart() {
return applyStateWithoutStart(TASKBAR_STASH_DURATION);
}
@@ -408,11 +432,50 @@
return mStatePropertyHolder.setState(mState, duration, false);
}
+ /**
+ * Should be called when a system gesture starts and settles, so we can defer updating
+ * FLAG_STASHED_IN_APP_IME until after the gesture transition completes.
+ */
+ public void setSystemGestureInProgress(boolean inProgress) {
+ mIsSystemGestureInProgress = inProgress;
+ // Only update FLAG_STASHED_IN_APP_IME when system gesture is not in progress.
+ if (!mIsSystemGestureInProgress) {
+ updateStateForFlag(FLAG_STASHED_IN_APP_IME, mIsImeShowing);
+ applyState(TASKBAR_STASH_DURATION_FOR_IME, getTaskbarStashStartDelayForIme());
+ }
+ }
+
+ /**
+ * When hiding the IME, delay the unstash animation to align with the end of the transition.
+ */
+ private long getTaskbarStashStartDelayForIme() {
+ if (mIsImeShowing) {
+ // Only delay when IME is exiting, not entering.
+ return 0;
+ }
+ // This duration is based on input_method_extract_exit.xml.
+ long imeExitDuration = mControllers.taskbarActivityContext.getResources()
+ .getInteger(android.R.integer.config_shortAnimTime);
+ return imeExitDuration - TASKBAR_STASH_DURATION_FOR_IME;
+ }
+
/** Called when some system ui state has changed. (See SYSUI_STATE_... in QuickstepContract) */
public void updateStateForSysuiFlags(int systemUiStateFlags, boolean skipAnim) {
+ long animDuration = TASKBAR_STASH_DURATION;
+ long startDelay = 0;
+
updateStateForFlag(FLAG_STASHED_IN_APP_PINNED,
hasAnyFlag(systemUiStateFlags, SYSUI_STATE_SCREEN_PINNING));
- applyState(skipAnim ? 0 : TASKBAR_STASH_DURATION);
+
+ // Only update FLAG_STASHED_IN_APP_IME when system gesture is not in progress.
+ mIsImeShowing = hasAnyFlag(systemUiStateFlags, SYSUI_STATE_IME_SHOWING);
+ if (!mIsSystemGestureInProgress) {
+ updateStateForFlag(FLAG_STASHED_IN_APP_IME, mIsImeShowing);
+ animDuration = TASKBAR_STASH_DURATION_FOR_IME;
+ startDelay = getTaskbarStashStartDelayForIme();
+ }
+
+ applyState(skipAnim ? 0 : animDuration, skipAnim ? 0 : startDelay);
}
/**
@@ -468,16 +531,34 @@
mStashCondition = stashCondition;
}
+ /**
+ * @see #setState(int, long, long, boolean) with a default startDelay = 0.
+ */
public Animator setState(int flags, long duration, boolean start) {
+ return setState(flags, duration, 0 /* startDelay */, start);
+ }
+
+ /**
+ * Applies the latest state, potentially calling onStateChangeApplied() and creating a new
+ * animation (stored in mAnimator) which is started if {@param start} is true.
+ * @param flags The latest flags to apply (see the top of this file).
+ * @param duration The length of the animation.
+ * @param startDelay How long to delay the animation after calling start().
+ * @param start Whether to start mAnimator immediately.
+ * @return mAnimator if mIsStashed changed, else null.
+ */
+ public Animator setState(int flags, long duration, long startDelay, boolean start) {
+ int changedFlags = mPrevFlags ^ flags;
if (mPrevFlags != flags) {
- int changedFlags = mPrevFlags ^ flags;
onStateChangeApplied(changedFlags);
mPrevFlags = flags;
}
boolean isStashed = mStashCondition.test(flags);
if (mIsStashed != isStashed) {
mIsStashed = isStashed;
- createAnimToIsStashed(mIsStashed, duration);
+
+ // This sets mAnimator.
+ createAnimToIsStashed(mIsStashed, duration, startDelay);
if (start) {
mAnimator.start();
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
index d8360e0..f713dca 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
@@ -16,6 +16,7 @@
package com.android.launcher3.taskbar;
import android.graphics.Rect;
+import android.view.View;
import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -29,7 +30,12 @@
public static final TaskbarUIController DEFAULT = new TaskbarUIController();
- protected void init(TaskbarControllers taskbarControllers) { }
+ // Initialized in init.
+ protected TaskbarControllers mControllers;
+
+ protected void init(TaskbarControllers taskbarControllers) {
+ mControllers = taskbarControllers;
+ }
protected void onDestroy() { }
@@ -46,4 +52,16 @@
}
public void onTaskbarIconLaunched(WorkspaceItemInfo item) { }
+
+ public View getRootView() {
+ return mControllers.taskbarActivityContext.getDragLayer();
+ }
+
+ /**
+ * Called when swiping from the bottom nav region in fully gestural mode.
+ * @param inProgress True if the animation started, false if we just settled on an end target.
+ */
+ public void setSystemGestureInProgress(boolean inProgress) {
+ mControllers.taskbarStashController.setSystemGestureInProgress(inProgress);
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index c47bde9..445a23b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -45,12 +45,11 @@
private static final Runnable NO_OP = () -> { };
public static final int ALPHA_INDEX_HOME = 0;
- public static final int ALPHA_INDEX_IME = 1;
- public static final int ALPHA_INDEX_KEYGUARD = 2;
- public static final int ALPHA_INDEX_STASH = 3;
- public static final int ALPHA_INDEX_RECENTS_DISABLED = 4;
- public static final int ALPHA_INDEX_NOTIFICATION_EXPANDED = 5;
- private static final int NUM_ALPHA_CHANNELS = 6;
+ public static final int ALPHA_INDEX_KEYGUARD = 1;
+ public static final int ALPHA_INDEX_STASH = 2;
+ public static final int ALPHA_INDEX_RECENTS_DISABLED = 3;
+ public static final int ALPHA_INDEX_NOTIFICATION_EXPANDED = 4;
+ private static final int NUM_ALPHA_CHANNELS = 5;
private final TaskbarActivityContext mActivity;
private final TaskbarView mTaskbarView;
diff --git a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
index 2fa8b07..d74b6c5 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
@@ -16,7 +16,6 @@
package com.android.launcher3.uioverrides;
-import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT;
import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE_IN_OUT;
import static com.android.launcher3.anim.Interpolators.FINAL_FRAME;
import static com.android.launcher3.anim.Interpolators.INSTANT;
@@ -73,8 +72,6 @@
getTaskModalnessProperty().set(mRecentsView, state.getOverviewModalness());
RECENTS_GRID_PROGRESS.set(mRecentsView,
state.displayOverviewTasksAsGrid(mLauncher.getDeviceProfile()) ? 1f : 0f);
-
- applySplitScrollOffset(state);
}
@Override
@@ -120,16 +117,6 @@
boolean showAsGrid = toState.displayOverviewTasksAsGrid(mLauncher.getDeviceProfile());
setter.setFloat(mRecentsView, RECENTS_GRID_PROGRESS, showAsGrid ? 1f : 0f,
showAsGrid ? INSTANT : FINAL_FRAME);
-
- applySplitScrollOffset(toState);
- }
-
- private void applySplitScrollOffset(@NonNull final LauncherState state) {
- if (state == OVERVIEW_SPLIT_SELECT) {
- mRecentsView.applySplitPrimaryScrollOffset();
- } else {
- mRecentsView.resetSplitPrimaryScrollOffset();
- }
}
abstract FloatProperty getTaskModalnessProperty();
diff --git a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
index 1f744e1..b21d677 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -63,6 +63,9 @@
}
setAlphas(PropertySetter.NO_ANIM_PROPERTY_SETTER, new StateAnimationConfig(), state);
mRecentsView.setFullscreenProgress(state.getOverviewFullscreenProgress());
+ // In Overview, we may be layering app surfaces behind Launcher, so we need to notify
+ // DepthController to prevent optimizations which might occlude the layers behind
+ mLauncher.getDepthController().setHasContentBehindLauncher(state.overviewUi);
}
@Override
@@ -78,13 +81,19 @@
builder.addListener(
AnimatorListeners.forSuccessCallback(mRecentsView::resetTaskVisuals));
}
+ // In Overview, we may be layering app surfaces behind Launcher, so we need to notify
+ // DepthController to prevent optimizations which might occlude the layers behind
+ builder.addListener(AnimatorListeners.forSuccessCallback(() ->
+ mLauncher.getDepthController().setHasContentBehindLauncher(toState.overviewUi)));
// Create or dismiss split screen select animations
LauncherState currentState = mLauncher.getStateManager().getState();
if (isSplitSelectionState(toState) && !isSplitSelectionState(currentState)) {
builder.add(mRecentsView.createSplitSelectInitAnimation().buildAnim());
+ mRecentsView.applySplitPrimaryScrollOffset();
} else if (!isSplitSelectionState(toState) && isSplitSelectionState(currentState)) {
builder.add(mRecentsView.cancelSplitSelect(true).buildAnim());
+ mRecentsView.resetSplitPrimaryScrollOffset();
}
setAlphas(builder, config, toState);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
index d396018..a4eff87 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -21,13 +21,11 @@
import android.content.Context;
import android.graphics.Rect;
import android.os.SystemProperties;
-import android.view.View;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.R;
-import com.android.launcher3.Workspace;
import com.android.launcher3.util.Themes;
import com.android.quickstep.SysUINavigationMode;
import com.android.quickstep.util.LayoutUtils;
@@ -66,10 +64,7 @@
@Override
public ScaleAndTranslation getWorkspaceScaleAndTranslation(Launcher launcher) {
RecentsView recentsView = launcher.getOverviewPanel();
- Workspace workspace = launcher.getWorkspace();
- View workspacePage = workspace.getPageAt(workspace.getCurrentPage());
- float workspacePageWidth = workspacePage != null && workspacePage.getWidth() != 0
- ? workspacePage.getWidth() : launcher.getDeviceProfile().availableWidthPx;
+ float workspacePageWidth = launcher.getDeviceProfile().getWorkspaceWidth();
recentsView.getTaskSize(sTempRect);
float scale = (float) sTempRect.width() / workspacePageWidth;
float parallaxFactor = 0.5f;
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 45b2081..c9cbba1 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -121,8 +121,8 @@
import com.android.systemui.shared.system.TaskStackChangeListeners;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.Arrays;
+import java.util.HashMap;
import java.util.function.Consumer;
/**
@@ -687,14 +687,17 @@
}
private void onAnimatorPlaybackControllerCreated(AnimatorControllerWithResistance anim) {
+ boolean isFirstCreation = mLauncherTransitionController == null;
mLauncherTransitionController = anim;
- mStateCallback.runOnceAtState(STATE_GESTURE_STARTED, () -> {
- // Wait until the gesture is started (touch slop was passed) to start in sync with
- // mWindowTransitionController. This ensures we don't hide the taskbar background when
- // long pressing to stash it, for instance.
- mLauncherTransitionController.getNormalController().dispatchOnStart();
- updateLauncherTransitionProgress();
- });
+ if (isFirstCreation) {
+ mStateCallback.runOnceAtState(STATE_GESTURE_STARTED, () -> {
+ // Wait until the gesture is started (touch slop was passed) to start in sync with
+ // mWindowTransitionController. This ensures we don't hide the taskbar background
+ // when long pressing to stash it, for instance.
+ mLauncherTransitionController.getNormalController().dispatchOnStart();
+ updateLauncherTransitionProgress();
+ });
+ }
}
public Intent getLaunchIntent() {
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index e15aa92..a566765 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -30,6 +30,7 @@
import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION;
import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.annotation.TargetApi;
import android.content.Context;
@@ -398,6 +399,11 @@
* (This is a hack to ensure Taskbar draws its background first to avoid flickering.)
*/
public @Nullable View onSettledOnEndTarget(GestureState.GestureEndTarget endTarget) {
+ TaskbarUIController taskbarUIController = getTaskbarController();
+ if (taskbarUIController != null) {
+ taskbarUIController.setSystemGestureInProgress(false);
+ return taskbarUIController.getRootView();
+ }
return null;
}
@@ -533,6 +539,16 @@
pa.addFloat(recentsView, RECENTS_SCALE_PROPERTY,
recentsView.getMaxScaleForFullScreen(), 1, LINEAR);
pa.addFloat(recentsView, FULLSCREEN_PROGRESS, 1, 0, LINEAR);
+
+ pa.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ TaskbarUIController taskbarUIController = getTaskbarController();
+ if (taskbarUIController != null) {
+ taskbarUIController.setSystemGestureInProgress(true);
+ }
+ }
+ });
}
}
}
diff --git a/quickstep/src/com/android/quickstep/KtR.java b/quickstep/src/com/android/quickstep/KtR.java
index 57dad08..a768ef5 100644
--- a/quickstep/src/com/android/quickstep/KtR.java
+++ b/quickstep/src/com/android/quickstep/KtR.java
@@ -30,6 +30,7 @@
public static final class dimen {
public static int task_menu_spacing = R.dimen.task_menu_spacing;
+ public static int task_menu_horizontal_padding = R.dimen.task_menu_horizontal_padding;
}
public static final class layout {
diff --git a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
index aa9435b..719c2ae 100644
--- a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
@@ -25,12 +25,10 @@
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.content.Context;
import android.graphics.Rect;
import android.view.MotionEvent;
-import android.view.View;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
@@ -132,19 +130,6 @@
pa.addFloat(getDepthController(),
new ClampedDepthProperty(fromDepthRatio, toDepthRatio),
fromDepthRatio, toDepthRatio, LINEAR);
-
- pa.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationStart(Animator animation) {
- LauncherTaskbarUIController taskbarUIController =
- activity.getTaskbarUIController();
- if (taskbarUIController != null) {
- // Launcher's ScrimView will draw the background throughout the gesture.
- taskbarUIController.forceHideBackground(true);
- }
- }
- });
-
}
};
@@ -366,16 +351,4 @@
return NORMAL;
}
}
-
- @Override
- public View onSettledOnEndTarget(@Nullable GestureEndTarget endTarget) {
- View superRet = super.onSettledOnEndTarget(endTarget);
- LauncherTaskbarUIController taskbarUIController = getTaskbarController();
- if (taskbarUIController != null) {
- // Start drawing taskbar's background again since launcher might stop drawing.
- taskbarUIController.forceHideBackground(false);
- return taskbarUIController.getRootView();
- }
- return superRet;
- }
}
diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java
index c5f4a53..097850f 100644
--- a/quickstep/src/com/android/quickstep/RecentTasksList.java
+++ b/quickstep/src/com/android/quickstep/RecentTasksList.java
@@ -36,6 +36,7 @@
import com.android.wm.shell.util.GroupedRecentTaskInfo;
import com.android.wm.shell.util.StagedSplitBounds;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.function.Consumer;
@@ -219,6 +220,26 @@
return newTasks;
}
+ public void dump(String prefix, PrintWriter writer) {
+ writer.println(prefix + "RecentTasksList:");
+ writer.println(prefix + " mChangeId=" + mChangeId);
+ writer.println(prefix + " mResultsUi=[id=" + mResultsUi.mRequestId + ", tasks=");
+ for (GroupTask task : mResultsUi) {
+ writer.println(prefix + " t1=" + task.task1.key.id
+ + " t2=" + (task.hasMultipleTasks() ? task.task2.key.id : "-1"));
+ }
+ writer.println(prefix + " ]");
+ int currentUserId = Process.myUserHandle().getIdentifier();
+ ArrayList<GroupedRecentTaskInfo> rawTasks =
+ mSysUiProxy.getRecentTasks(Integer.MAX_VALUE, currentUserId);
+ writer.println(prefix + " rawTasks=[");
+ for (GroupedRecentTaskInfo task : rawTasks) {
+ writer.println(prefix + " t1=" + task.mTaskInfo1.taskId
+ + " t2=" + (task.mTaskInfo2 != null ? task.mTaskInfo2.taskId : "-1"));
+ }
+ writer.println(prefix + " ]");
+ }
+
private static class TaskLoadResult extends ArrayList<GroupTask> {
final int mRequestId;
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index 09a0b7d..d6efc71 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -128,7 +128,8 @@
SYSUI_PROGRESS.set(getRootView().getSysUiScrim(), 0f);
SplitSelectStateController controller =
- new SplitSelectStateController(mHandler, SystemUiProxy.INSTANCE.get(this));
+ new SplitSelectStateController(mHandler, SystemUiProxy.INSTANCE.get(this),
+ getStateManager(), null /*depthController*/);
mDragLayer.recreateControllers();
mFallbackRecentsView.init(mActionsView, controller);
diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index ac97dd6..5d77a6e 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -24,6 +24,7 @@
import android.app.ActivityManager;
import android.content.ComponentCallbacks2;
import android.content.Context;
+import android.content.Intent;
import android.os.Build;
import android.os.Process;
import android.os.UserHandle;
@@ -42,6 +43,7 @@
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.TaskStackChangeListeners;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
@@ -176,7 +178,7 @@
@Override
public void onTaskRemoved(int taskId) {
- Task.TaskKey stubKey = new Task.TaskKey(taskId, 0, null, null, 0, 0);
+ Task.TaskKey stubKey = new Task.TaskKey(taskId, 0, new Intent(), null, 0, 0);
mThumbnailCache.remove(stubKey);
mIconCache.onTaskRemoved(stubKey);
}
@@ -219,6 +221,11 @@
mThumbnailChangeListeners.remove(listener);
}
+ public void dump(String prefix, PrintWriter writer) {
+ writer.println(prefix + "RecentsModel:");
+ mTaskList.dump(" ", writer);
+ }
+
/**
* Listener for receiving various task properties changes
*/
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index 4239739..c8abd14 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -39,7 +39,6 @@
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
-import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.SplitConfigurationOptions;
import com.android.systemui.shared.recents.ISystemUiProxy;
@@ -52,12 +51,12 @@
import com.android.wm.shell.pip.IPipAnimationListener;
import com.android.wm.shell.recents.IRecentTasks;
import com.android.wm.shell.recents.IRecentTasksListener;
-import com.android.wm.shell.util.GroupedRecentTaskInfo;
import com.android.wm.shell.splitscreen.ISplitScreen;
import com.android.wm.shell.splitscreen.ISplitScreenListener;
import com.android.wm.shell.startingsurface.IStartingWindow;
import com.android.wm.shell.startingsurface.IStartingWindowListener;
import com.android.wm.shell.transition.IShellTransitions;
+import com.android.wm.shell.util.GroupedRecentTaskInfo;
import java.util.ArrayList;
import java.util.Arrays;
@@ -84,14 +83,16 @@
MAIN_EXECUTOR.execute(() -> clearProxy());
};
- // Save the listeners passed into the proxy since when set/register these listeners,
- // setProxy may not have been called, eg. OverviewProxyService is not connected yet.
- private IPipAnimationListener mPendingPipAnimationListener;
- private ISplitScreenListener mPendingSplitScreenListener;
- private IStartingWindowListener mPendingStartingWindowListener;
- private ISmartspaceCallback mPendingSmartspaceCallback;
- private IRecentTasksListener mPendingRecentTasksListener;
- private final ArrayList<RemoteTransitionCompat> mPendingRemoteTransitions = new ArrayList<>();
+ // Save the listeners passed into the proxy since OverviewProxyService may not have been bound
+ // yet, and we'll need to set/register these listeners with SysUI when they do. Note that it is
+ // up to the caller to clear the listeners to prevent leaks as these can be held indefinitely
+ // in case SysUI needs to rebind.
+ private IPipAnimationListener mPipAnimationListener;
+ private ISplitScreenListener mSplitScreenListener;
+ private IStartingWindowListener mStartingWindowListener;
+ private ISmartspaceCallback mSmartspaceCallback;
+ private IRecentTasksListener mRecentTasksListener;
+ private final ArrayList<RemoteTransitionCompat> mRemoteTransitions = new ArrayList<>();
// Used to dedupe calls to SystemUI
private int mLastShelfHeight;
@@ -168,29 +169,23 @@
mRecentTasks = recentTasks;
linkToDeath();
// re-attach the listeners once missing due to setProxy has not been initialized yet.
- if (mPendingPipAnimationListener != null && mPip != null) {
- setPinnedStackAnimationListener(mPendingPipAnimationListener);
- mPendingPipAnimationListener = null;
+ if (mPipAnimationListener != null && mPip != null) {
+ setPinnedStackAnimationListener(mPipAnimationListener);
}
- if (mPendingSplitScreenListener != null && mSplitScreen != null) {
- registerSplitScreenListener(mPendingSplitScreenListener);
- mPendingSplitScreenListener = null;
+ if (mSplitScreenListener != null && mSplitScreen != null) {
+ registerSplitScreenListener(mSplitScreenListener);
}
- if (mPendingStartingWindowListener != null && mStartingWindow != null) {
- setStartingWindowListener(mPendingStartingWindowListener);
- mPendingStartingWindowListener = null;
+ if (mStartingWindowListener != null && mStartingWindow != null) {
+ setStartingWindowListener(mStartingWindowListener);
}
- if (mPendingSmartspaceCallback != null && mSmartspaceTransitionController != null) {
- setSmartspaceCallback(mPendingSmartspaceCallback);
- mPendingSmartspaceCallback = null;
+ if (mSmartspaceCallback != null && mSmartspaceTransitionController != null) {
+ setSmartspaceCallback(mSmartspaceCallback);
}
- for (int i = mPendingRemoteTransitions.size() - 1; i >= 0; --i) {
- registerRemoteTransition(mPendingRemoteTransitions.get(i));
+ for (int i = mRemoteTransitions.size() - 1; i >= 0; --i) {
+ registerRemoteTransition(mRemoteTransitions.get(i));
}
- mPendingRemoteTransitions.clear();
- if (mPendingRecentTasksListener != null && mRecentTasks != null) {
- registerRecentTasksListener(mPendingRecentTasksListener);
- mPendingRecentTasksListener = null;
+ if (mRecentTasksListener != null && mRecentTasks != null) {
+ registerRecentTasksListener(mRecentTasksListener);
}
if (mPendingSetNavButtonAlpha != null) {
@@ -469,8 +464,6 @@
} catch (RemoteException e) {
Log.w(TAG, "Failed call handleImageBundleAsScreenshot");
}
- } else if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.NO_SCREENSHOT, "sysuiproxy, no proxy available");
}
}
@@ -516,9 +509,8 @@
} catch (RemoteException e) {
Log.w(TAG, "Failed call setPinnedStackAnimationListener", e);
}
- } else {
- mPendingPipAnimationListener = listener;
}
+ mPipAnimationListener = listener;
}
public Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo,
@@ -556,9 +548,8 @@
} catch (RemoteException e) {
Log.w(TAG, "Failed call registerSplitScreenListener");
}
- } else {
- mPendingSplitScreenListener = listener;
}
+ mSplitScreenListener = listener;
}
public void unregisterSplitScreenListener(ISplitScreenListener listener) {
@@ -569,7 +560,7 @@
Log.w(TAG, "Failed call unregisterSplitScreenListener");
}
}
- mPendingSplitScreenListener = null;
+ mSplitScreenListener = null;
}
/** Start multiple tasks in split-screen simultaneously. */
@@ -690,9 +681,8 @@
} catch (RemoteException e) {
Log.w(TAG, "Failed call registerRemoteTransition");
}
- } else {
- mPendingRemoteTransitions.add(remoteTransition);
}
+ mRemoteTransitions.add(remoteTransition);
}
public void unregisterRemoteTransition(RemoteTransitionCompat remoteTransition) {
@@ -703,7 +693,7 @@
Log.w(TAG, "Failed call registerRemoteTransition");
}
}
- mPendingRemoteTransitions.remove(remoteTransition);
+ mRemoteTransitions.remove(remoteTransition);
}
//
@@ -720,9 +710,8 @@
} catch (RemoteException e) {
Log.w(TAG, "Failed call setStartingWindowListener", e);
}
- } else {
- mPendingStartingWindowListener = listener;
}
+ mStartingWindowListener = listener;
}
//
@@ -736,9 +725,8 @@
} catch (RemoteException e) {
Log.w(TAG, "Failed call setStartingWindowListener", e);
}
- } else {
- mPendingSmartspaceCallback = callback;
}
+ mSmartspaceCallback = callback;
}
//
@@ -752,9 +740,8 @@
} catch (RemoteException e) {
Log.w(TAG, "Failed call registerRecentTasksListener", e);
}
- } else {
- mPendingRecentTasksListener = listener;
}
+ mRecentTasksListener = listener;
}
public void unregisterRecentTasksListener(IRecentTasksListener listener) {
@@ -765,7 +752,7 @@
Log.w(TAG, "Failed call unregisterRecentTasksListener");
}
}
- mPendingRecentTasksListener = null;
+ mRecentTasksListener = null;
}
public ArrayList<GroupedRecentTaskInfo> getRecentTasks(int numTasks, int userId) {
diff --git a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
index c45159e..0246849 100644
--- a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
@@ -209,13 +209,6 @@
}
/**
- * Called when the current task's thumbnail has changed.
- */
- public void refreshActionVisibility(ThumbnailData thumbnail) {
- getActionsView().updateDisabledFlags(DISABLED_NO_THUMBNAIL, thumbnail == null);
- }
-
- /**
* End rendering live tile in Overview.
*
* @param callback callback to run, after switching to screenshot
diff --git a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
index 8c4ba97..cbdbdb5 100644
--- a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
@@ -72,7 +72,13 @@
@Override
public SystemShortcut getShortcut(BaseDraggingActivity activity,
TaskIdAttributeContainer taskContainer) {
- return new AppInfo(activity, taskContainer.getItemInfo());
+ TaskView taskView = taskContainer.getTaskView();
+ AppInfo.SplitAccessibilityInfo accessibilityInfo =
+ new AppInfo.SplitAccessibilityInfo(taskView.containsMultipleTasks(),
+ TaskUtils.getTitle(taskView.getContext(), taskContainer.getTask()),
+ taskContainer.getA11yNodeId()
+ );
+ return new AppInfo(activity, taskContainer.getItemInfo(), accessibilityInfo);
}
@Override
diff --git a/quickstep/src/com/android/quickstep/TaskThumbnailCache.java b/quickstep/src/com/android/quickstep/TaskThumbnailCache.java
index eaa43cf..3175ba8 100644
--- a/quickstep/src/com/android/quickstep/TaskThumbnailCache.java
+++ b/quickstep/src/com/android/quickstep/TaskThumbnailCache.java
@@ -134,7 +134,8 @@
Preconditions.assertUIThread();
boolean lowResolution = !mHighResLoadingState.isEnabled();
- if (task.thumbnail != null && (!task.thumbnail.reducedResolution || lowResolution)) {
+ if (task.thumbnail != null && task.thumbnail.thumbnail != null
+ && (!task.thumbnail.reducedResolution || lowResolution)) {
// Nothing to load, the thumbnail is already high-resolution or matches what the
// request, so just callback
callback.accept(task.thumbnail);
@@ -152,7 +153,8 @@
Preconditions.assertUIThread();
ThumbnailData cachedThumbnail = mCache.getAndInvalidateIfModified(key);
- if (cachedThumbnail != null && (!cachedThumbnail.reducedResolution || lowResolution)) {
+ if (cachedThumbnail != null && cachedThumbnail.thumbnail != null
+ && (!cachedThumbnail.reducedResolution || lowResolution)) {
// Already cached, lets use that thumbnail
callback.accept(cachedThumbnail);
return null;
diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java
index ae3cc50..e77ec78 100644
--- a/quickstep/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java
@@ -32,7 +32,6 @@
import static com.android.launcher3.Utilities.getDescendantCoordRelativeToAncestor;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR;
-import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR_ACCEL_DEACCEL;
import static com.android.launcher3.anim.Interpolators.clampToProgress;
import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
import static com.android.launcher3.statehandlers.DepthController.DEPTH;
@@ -57,6 +56,7 @@
import android.window.TransitionInfo;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import com.android.launcher3.BaseActivity;
import com.android.launcher3.DeviceProfile;
@@ -73,6 +73,7 @@
import com.android.quickstep.util.SurfaceTransactionApplier;
import com.android.quickstep.util.TaskViewSimulator;
import com.android.quickstep.util.TransformParams;
+import com.android.quickstep.views.GroupedTaskView;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskThumbnailView;
import com.android.quickstep.views.TaskView;
@@ -150,10 +151,12 @@
return taskView;
}
- public static void createRecentsWindowAnimator(TaskView v, boolean skipViewChanges,
- RemoteAnimationTargetCompat[] appTargets,
- RemoteAnimationTargetCompat[] wallpaperTargets,
- RemoteAnimationTargetCompat[] nonAppTargets, DepthController depthController,
+ public static void createRecentsWindowAnimator(
+ @NonNull TaskView v, boolean skipViewChanges,
+ @NonNull RemoteAnimationTargetCompat[] appTargets,
+ @NonNull RemoteAnimationTargetCompat[] wallpaperTargets,
+ @NonNull RemoteAnimationTargetCompat[] nonAppTargets,
+ @Nullable DepthController depthController,
PendingAnimation out) {
RecentsView recentsView = v.getRecentsView();
boolean isQuickSwitch = v.isEndQuickswitchCuj();
@@ -193,8 +196,8 @@
boolean showAsGrid = dp.overviewShowAsGrid;
boolean parallaxCenterAndAdjacentTask =
taskIndex != recentsView.getCurrentPage() && !showAsGrid;
- float gridTranslationSecondary = recentsView.getGridTranslationSecondary(taskIndex);
- int startScroll = recentsView.getScrollOffset(taskIndex);
+ int taskRectTranslationPrimary = recentsView.getScrollOffset(taskIndex);
+ int taskRectTranslationSecondary = showAsGrid ? (int) v.getGridTranslationY() : 0;
RemoteTargetHandle[] topMostSimulators = null;
@@ -211,11 +214,10 @@
tvsLocal.fullScreenProgress.value = 0;
tvsLocal.recentsViewScale.value = 1;
- if (showAsGrid) {
- tvsLocal.taskSecondaryTranslation.value = gridTranslationSecondary;
- }
- tvsLocal.setScroll(startScroll);
tvsLocal.setIsGridTask(v.isGridTask());
+ tvsLocal.getOrientationState().getOrientationHandler().set(tvsLocal,
+ TaskViewSimulator::setTaskRectTranslation, taskRectTranslationPrimary,
+ taskRectTranslationSecondary);
// Fade in the task during the initial 20% of the animation
out.addFloat(targetHandle.getTransformParams(), TransformParams.TARGET_ALPHA, 0, 1,
@@ -230,10 +232,6 @@
out.setFloat(tvsLocal.recentsViewScale,
AnimatedFloat.VALUE, tvsLocal.getFullScreenScale(),
TOUCH_RESPONSE_INTERPOLATOR);
- if (showAsGrid) {
- out.setFloat(tvsLocal.taskSecondaryTranslation, AnimatedFloat.VALUE, 0,
- TOUCH_RESPONSE_INTERPOLATOR_ACCEL_DEACCEL);
- }
out.setFloat(tvsLocal.recentsViewScroll, AnimatedFloat.VALUE, 0,
TOUCH_RESPONSE_INTERPOLATOR);
@@ -426,15 +424,46 @@
finishCallback.run();
}
- /** Legacy version (until shell transitions are enabled) */
- public static void composeRecentsSplitLaunchAnimatorLegacy(@NonNull Task initialTask,
+ /**
+ * Legacy version (until shell transitions are enabled)
+ *
+ * If {@param launchingTaskView} is not null, then this will play the tasks launch animation
+ * from the position of the GroupedTaskView (when user taps on the TaskView to start it).
+ * Technically this case should be taken care of by
+ * {@link #composeRecentsSplitLaunchAnimatorLegacy()} below, but the way we launch tasks whether
+ * it's a single task or multiple tasks results in different entry-points.
+ *
+ * If it is null, then it will simply fade in the starting apps and fade out launcher (for the
+ * case where launcher handles animating starting split tasks from app icon) */
+ public static void composeRecentsSplitLaunchAnimatorLegacy(
+ @Nullable GroupedTaskView launchingTaskView,
+ @NonNull Task initialTask,
@NonNull Task secondTask, @NonNull RemoteAnimationTargetCompat[] appTargets,
@NonNull RemoteAnimationTargetCompat[] wallpaperTargets,
@NonNull RemoteAnimationTargetCompat[] nonAppTargets,
+ @NonNull StateManager stateManager,
+ @Nullable DepthController depthController,
@NonNull Runnable finishCallback) {
+ if (launchingTaskView != null) {
+ AnimatorSet animatorSet = new AnimatorSet();
+ RecentsView recentsView = launchingTaskView.getRecentsView();
+ animatorSet.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ finishCallback.run();
+ }
+ });
+ composeRecentsLaunchAnimator(animatorSet, launchingTaskView,
+ appTargets, wallpaperTargets, nonAppTargets,
+ true, stateManager,
+ recentsView, depthController);
+ animatorSet.start();
+ return;
+ }
+
final ArrayList<SurfaceControl> openingTargets = new ArrayList<>();
final ArrayList<SurfaceControl> closingTargets = new ArrayList<>();
-
for (RemoteAnimationTargetCompat appTarget : appTargets) {
final int taskId = appTarget.taskInfo != null ? appTarget.taskInfo.taskId : -1;
final int mode = appTarget.mode;
@@ -498,7 +527,7 @@
@NonNull RemoteAnimationTargetCompat[] wallpaperTargets,
@NonNull RemoteAnimationTargetCompat[] nonAppTargets, boolean launcherClosing,
@NonNull StateManager stateManager, @NonNull RecentsView recentsView,
- @NonNull DepthController depthController) {
+ @Nullable DepthController depthController) {
boolean skipLauncherChanges = !launcherClosing;
TaskView taskView = findTaskViewToLaunch(recentsView, v, appTargets);
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 377edbe..f6f2cf9 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -22,7 +22,6 @@
import static com.android.launcher3.config.FeatureFlags.ASSISTANT_GIVES_LAUNCHER_FOCUS;
import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
-import static com.android.launcher3.testing.TestProtocol.TASKBAR_WINDOW_CRASH;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.quickstep.GestureState.DEFAULT_STATE;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
@@ -289,6 +288,12 @@
.onSystemBarAttributesChanged(displayId, behavior));
}
+ @Override
+ public void onNavButtonsDarkIntensityChanged(float darkIntensity) {
+ executeForTaskbarManager(() -> mTaskbarManager
+ .onNavButtonsDarkIntensityChanged(darkIntensity));
+ }
+
private void executeForTaskbarManager(final Runnable r) {
MAIN_EXECUTOR.execute(() -> {
if (mTaskbarManager == null) {
@@ -355,7 +360,6 @@
@Override
public void onCreate() {
super.onCreate();
- Log.d(TASKBAR_WINDOW_CRASH, "TIS created");
// Initialize anything here that is needed in direct boot mode.
// Everything else should be initialized in onUserUnlocked() below.
mMainChoreographer = Choreographer.getInstance();
@@ -517,7 +521,6 @@
@Override
public void onDestroy() {
Log.d(TAG, "Touch service destroyed: user=" + getUserId());
- Log.d(TASKBAR_WINDOW_CRASH, "TIS destroyed");
sIsInitialized = false;
if (mDeviceState.isUserUnlocked()) {
mInputConsumer.unregisterInputConsumer();
@@ -970,6 +973,7 @@
pw.println(" resumed=" + resumed);
pw.println(" mConsumer=" + mConsumer.getName());
ActiveGestureLog.INSTANCE.dump("", pw);
+ RecentsModel.INSTANCE.get(this).dump("", pw);
pw.println("ProtoTrace:");
pw.println(" file=" + ProtoTracer.INSTANCE.get(this).getTraceFile());
}
diff --git a/quickstep/src/com/android/quickstep/ViewUtils.java b/quickstep/src/com/android/quickstep/ViewUtils.java
index 184ab17..e290be8 100644
--- a/quickstep/src/com/android/quickstep/ViewUtils.java
+++ b/quickstep/src/com/android/quickstep/ViewUtils.java
@@ -15,8 +15,10 @@
*/
package com.android.quickstep;
+import android.graphics.HardwareRenderer;
import android.os.Handler;
import android.view.View;
+import android.view.ViewRootImpl;
import com.android.launcher3.Utilities;
import com.android.systemui.shared.system.ViewRootImplCompat;
@@ -45,9 +47,9 @@
return new FrameHandler(view, onFinishRunnable, canceled).schedule();
}
- private static class FrameHandler implements LongConsumer {
+ private static class FrameHandler implements HardwareRenderer.FrameDrawingCallback {
- final ViewRootImplCompat mViewRoot;
+ final ViewRootImpl mViewRoot;
final Runnable mFinishCallback;
final BooleanSupplier mCancelled;
final Handler mHandler;
@@ -55,14 +57,14 @@
int mDeferFrameCount = 1;
FrameHandler(View view, Runnable finishCallback, BooleanSupplier cancelled) {
- mViewRoot = new ViewRootImplCompat(view);
+ mViewRoot = view.getViewRootImpl();
mFinishCallback = finishCallback;
mCancelled = cancelled;
mHandler = new Handler();
}
@Override
- public void accept(long l) {
+ public void onFrameDraw(long frame) {
Utilities.postAsyncCallback(mHandler, this::onFrame);
}
@@ -83,7 +85,7 @@
}
private boolean schedule() {
- if (mViewRoot.isValid()) {
+ if (mViewRoot.getView() != null) {
mViewRoot.registerRtFrameCallback(this);
mViewRoot.getView().invalidate();
return true;
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index eff59e2..22f67d2 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -218,8 +218,14 @@
@Override
public void onStateTransitionComplete(RecentsState finalState) {
- setOverlayEnabled(finalState == DEFAULT || finalState == MODAL_TASK);
+ boolean isOverlayEnabled = finalState == DEFAULT || finalState == MODAL_TASK;
+ setOverlayEnabled(isOverlayEnabled);
setFreezeViewVisibility(false);
+
+ if (isOverlayEnabled) {
+ runActionOnRemoteHandles(remoteTargetHandle ->
+ remoteTargetHandle.getTaskViewSimulator().setDrawsBelowRecents(true));
+ }
}
@Override
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/AssistantInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/AssistantInputConsumer.java
index 510820a..162ace4 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/AssistantInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/AssistantInputConsumer.java
@@ -24,6 +24,8 @@
import static android.view.MotionEvent.ACTION_POINTER_UP;
import static android.view.MotionEvent.ACTION_UP;
+import static com.android.internal.app.AssistUtils.INVOCATION_TYPE_GESTURE;
+import static com.android.internal.app.AssistUtils.INVOCATION_TYPE_KEY;
import static com.android.launcher3.Utilities.squaredHypot;
import android.animation.Animator;
@@ -64,8 +66,6 @@
private static final String OPA_BUNDLE_TRIGGER = "triggered_by";
// From //java/com/google/android/apps/gsa/assistant/shared/proto/opa_trigger.proto.
private static final int OPA_BUNDLE_TRIGGER_DIAG_SWIPE_GESTURE = 83;
- private static final String INVOCATION_TYPE_KEY = "invocation_type";
- private static final int INVOCATION_TYPE_GESTURE = 1;
private final PointF mDownPos = new PointF();
private final PointF mLastPos = new PointF();
diff --git a/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java b/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
index 965c1bc..1c3e784 100644
--- a/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
+++ b/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
@@ -19,6 +19,7 @@
import static com.android.launcher3.Utilities.mapRange;
import static com.android.launcher3.anim.Interpolators.LINEAR;
+import android.animation.Animator;
import android.app.Activity;
import android.app.ActivityManager.RunningTaskInfo;
import android.content.Context;
@@ -38,6 +39,8 @@
import android.graphics.Shader.TileMode;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
import android.util.Log;
import android.view.View;
import android.view.View.AccessibilityDelegate;
@@ -56,6 +59,8 @@
import com.android.quickstep.TouchInteractionService.TISBinder;
import com.android.quickstep.util.TISBindHelper;
+import com.airbnb.lottie.LottieAnimationView;
+
import java.net.URISyntaxException;
/**
@@ -80,6 +85,10 @@
private View mContentView;
private float mSwipeUpShift;
+ @Nullable private Vibrator mVibrator;
+ private LottieAnimationView mAnimatedBackground;
+ private Animator.AnimatorListener mBackgroundAnimatorListener;
+
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -115,6 +124,52 @@
findViewById(R.id.hint).setAccessibilityDelegate(new SkipButtonAccessibilityDelegate());
mTISBindHelper = new TISBindHelper(this, this::onTISConnected);
+
+ mVibrator = getSystemService(Vibrator.class);
+ mAnimatedBackground = findViewById(R.id.animated_background);
+ startBackgroundAnimation();
+ }
+
+ private void startBackgroundAnimation() {
+ if (Utilities.ATLEAST_S && mVibrator != null && mVibrator.areAllPrimitivesSupported(
+ VibrationEffect.Composition.PRIMITIVE_THUD)) {
+ if (mBackgroundAnimatorListener == null) {
+ mBackgroundAnimatorListener =
+ new Animator.AnimatorListener() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ mVibrator.vibrate(getVibrationEffect());
+ }
+
+ @Override
+ public void onAnimationRepeat(Animator animation) {
+ mVibrator.vibrate(getVibrationEffect());
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mVibrator.cancel();
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mVibrator.cancel();
+ }
+ };
+ }
+ mAnimatedBackground.addAnimatorListener(mBackgroundAnimatorListener);
+ }
+ mAnimatedBackground.playAnimation();
+ }
+
+ /**
+ * Sets up the vibration effect for the next round of animation. The parameters vary between
+ * different illustrations.
+ */
+ private VibrationEffect getVibrationEffect() {
+ return VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_THUD, 1.0f, 50)
+ .compose();
}
@Override
@@ -153,6 +208,9 @@
super.onDestroy();
mTISBindHelper.onDestroy();
clearBinderOverride();
+ if (mBackgroundAnimatorListener != null) {
+ mAnimatedBackground.removeAnimatorListener(mBackgroundAnimatorListener);
+ }
}
private AnimatedFloat createSwipeUpProxy(GestureState state) {
@@ -173,6 +231,12 @@
1, 0, LINEAR);
mContentView.setAlpha(alpha);
mContentView.setTranslationY((alpha - 1) * mSwipeUpShift);
+
+ if (alpha == 0f) {
+ mAnimatedBackground.pauseAnimation();
+ } else if (!mAnimatedBackground.isAnimating()) {
+ mAnimatedBackground.resumeAnimation();
+ }
}
/**
diff --git a/quickstep/src/com/android/quickstep/util/ImageActionUtils.java b/quickstep/src/com/android/quickstep/util/ImageActionUtils.java
index b0c68c5..de7dbd6 100644
--- a/quickstep/src/com/android/quickstep/util/ImageActionUtils.java
+++ b/quickstep/src/com/android/quickstep/util/ImageActionUtils.java
@@ -49,7 +49,6 @@
import com.android.internal.app.ChooserActivity;
import com.android.launcher3.BuildConfig;
-import com.android.launcher3.testing.TestProtocol;
import com.android.quickstep.SystemUiProxy;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.utilities.BitmapUtil;
@@ -78,9 +77,6 @@
public static void saveScreenshot(SystemUiProxy systemUiProxy, Bitmap screenshot,
Rect screenshotBounds,
Insets visibleInsets, Task.TaskKey task) {
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.NO_SCREENSHOT, "image action utils calling into sysuiproxy");
- }
systemUiProxy.handleImageBundleAsScreenshot(BitmapUtil.hardwareBitmapToBundle(screenshot),
screenshotBounds, visibleInsets, task);
}
diff --git a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
index 9c6fd3d..8ccab71 100644
--- a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
+++ b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
@@ -162,7 +162,7 @@
*/
public void setDeviceProfile(DeviceProfile deviceProfile) {
boolean oldMultipleOrientationsSupported = isMultipleOrientationSupportedByDevice();
- setFlag(FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_DENSITY, !deviceProfile.allowRotation);
+ setFlag(FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_DENSITY, !deviceProfile.isTablet);
if (mListenersInitialized) {
boolean newMultipleOrientationsSupported = isMultipleOrientationSupportedByDevice();
// If isMultipleOrientationSupportedByDevice is changed, init or destroy listeners
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
index b32c4e5..c784d82 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
@@ -24,19 +24,24 @@
import android.app.ActivityOptions;
import android.app.ActivityThread;
import android.graphics.Rect;
-import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.view.RemoteAnimationAdapter;
import android.view.SurfaceControl;
import android.window.TransitionInfo;
-import com.android.launcher3.Utilities;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.statehandlers.DepthController;
+import com.android.launcher3.statemanager.StateManager;
import com.android.launcher3.util.SplitConfigurationOptions;
import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.TaskAnimationManager;
import com.android.quickstep.TaskViewUtils;
+import com.android.quickstep.views.GroupedTaskView;
+import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.RemoteAnimationAdapterCompat;
import com.android.systemui.shared.system.RemoteAnimationRunnerCompat;
@@ -54,23 +59,32 @@
private final Handler mHandler;
private final SystemUiProxy mSystemUiProxy;
+ private final StateManager mStateManager;
+ private final DepthController mDepthController;
private @StagePosition int mStagePosition;
private Task mInitialTask;
private Task mSecondTask;
private Rect mInitialBounds;
private boolean mRecentsAnimationRunning;
+ /** If not null, this is the TaskView we want to launch from */
+ @Nullable
+ private GroupedTaskView mLaunchingTaskView;
- public SplitSelectStateController(Handler handler, SystemUiProxy systemUiProxy) {
+ public SplitSelectStateController(Handler handler, SystemUiProxy systemUiProxy,
+ StateManager stateManager,
+ DepthController depthController) {
mHandler = handler;
mSystemUiProxy = systemUiProxy;
+ mStateManager = stateManager;
+ mDepthController = depthController;
}
/**
* To be called after first task selected
*/
- public void setInitialTaskSelect(Task taskView, @StagePosition int stagePosition,
+ public void setInitialTaskSelect(Task task, @StagePosition int stagePosition,
Rect initialBounds) {
- mInitialTask = taskView;
+ mInitialTask = task;
mStagePosition = stagePosition;
mInitialBounds = initialBounds;
}
@@ -78,13 +92,25 @@
/**
* To be called after second task selected
*/
- public void setSecondTaskId(Task taskView, Consumer<Boolean> callback) {
- mSecondTask = taskView;
+ public void setSecondTaskId(Task task, Consumer<Boolean> callback) {
+ mSecondTask = task;
launchTasks(mInitialTask, mSecondTask, mStagePosition, callback,
false /* freezeTaskList */);
}
/**
+ * To be called when we want to launch split pairs from an existing GroupedTaskView.
+ */
+ public void launchTasks(GroupedTaskView groupedTaskView,
+ Consumer<Boolean> callback, boolean freezeTaskList) {
+ mLaunchingTaskView = groupedTaskView;
+ TaskView.TaskIdAttributeContainer[] taskIdAttributeContainers =
+ groupedTaskView.getTaskIdAttributeContainers();
+ launchTasks(taskIdAttributeContainers[0].getTask(), taskIdAttributeContainers[1].getTask(),
+ taskIdAttributeContainers[0].getStagePosition(), callback, freezeTaskList);
+ }
+
+ /**
* @param stagePosition representing location of task1
*/
public void launchTasks(Task task1, Task task2, @StagePosition int stagePosition,
@@ -171,8 +197,9 @@
RemoteAnimationTargetCompat[] wallpapers, RemoteAnimationTargetCompat[] nonApps,
Runnable finishedCallback) {
postAsyncCallback(mHandler,
- () -> TaskViewUtils.composeRecentsSplitLaunchAnimatorLegacy(mInitialTask,
- mSecondTask, apps, wallpapers, nonApps, () -> {
+ () -> TaskViewUtils.composeRecentsSplitLaunchAnimatorLegacy(
+ mLaunchingTaskView, mInitialTask, mSecondTask, apps, wallpapers,
+ nonApps, mStateManager, mDepthController, () -> {
finishedCallback.run();
if (mSuccessCallback != null) {
mSuccessCallback.accept(true);
@@ -203,6 +230,7 @@
mStagePosition = SplitConfigurationOptions.STAGE_POSITION_UNDEFINED;
mInitialBounds = null;
mRecentsAnimationRunning = false;
+ mLaunchingTaskView = null;
}
/**
diff --git a/quickstep/src/com/android/quickstep/util/SurfaceTransactionApplier.java b/quickstep/src/com/android/quickstep/util/SurfaceTransactionApplier.java
index 3b4fd31..3b1c150 100644
--- a/quickstep/src/com/android/quickstep/util/SurfaceTransactionApplier.java
+++ b/quickstep/src/com/android/quickstep/util/SurfaceTransactionApplier.java
@@ -22,10 +22,10 @@
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
import android.view.View;
+import android.view.ViewRootImpl;
import com.android.quickstep.RemoteAnimationTargets.ReleaseCheck;
import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
-import com.android.systemui.shared.system.ViewRootImplCompat;
import java.util.function.Consumer;
@@ -41,7 +41,7 @@
private static final int MSG_UPDATE_SEQUENCE_NUMBER = 0;
private final SurfaceControl mBarrierSurfaceControl;
- private final ViewRootImplCompat mTargetViewRootImpl;
+ private final ViewRootImpl mTargetViewRootImpl;
private final Handler mApplyHandler;
private int mLastSequenceNumber = 0;
@@ -50,8 +50,8 @@
* @param targetView The view in the surface that acts as synchronization anchor.
*/
public SurfaceTransactionApplier(View targetView) {
- mTargetViewRootImpl = new ViewRootImplCompat(targetView);
- mBarrierSurfaceControl = mTargetViewRootImpl.getRenderSurfaceControl();
+ mTargetViewRootImpl = targetView.getViewRootImpl();
+ mBarrierSurfaceControl = mTargetViewRootImpl.getSurfaceControl();
mApplyHandler = new Handler(this::onApplyMessage);
}
@@ -109,7 +109,7 @@
if (targetView == null) {
// No target view, no applier
callback.accept(null);
- } else if (new ViewRootImplCompat(targetView).isValid()) {
+ } else if (targetView.isAttachedToWindow()) {
// Already attached, we're good to go
callback.accept(new SurfaceTransactionApplier(targetView));
} else {
diff --git a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
index 146d235..f676091 100644
--- a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
+++ b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
@@ -102,6 +102,8 @@
private StagedSplitBounds mStagedSplitBounds;
private boolean mDrawsBelowRecents;
private boolean mIsGridTask;
+ private int mTaskRectTranslationX;
+ private int mTaskRectTranslationY;
public TaskViewSimulator(Context context, BaseActivityInterface sizeStrategy) {
mContext = context;
@@ -156,9 +158,11 @@
fullTaskSize = new Rect(mTaskRect);
mOrientationState.getOrientationHandler()
.setSplitTaskSwipeRect(mDp, mTaskRect, mStagedSplitBounds, mStagePosition);
+ mTaskRect.offset(mTaskRectTranslationX, mTaskRectTranslationY);
} else {
fullTaskSize = mTaskRect;
}
+ fullTaskSize.offset(mTaskRectTranslationX, mTaskRectTranslationY);
return mOrientationState.getFullScreenScaleAndPivot(fullTaskSize, mDp, mPivot);
}
@@ -218,6 +222,14 @@
}
/**
+ * Apply translations on TaskRect's starting location.
+ */
+ public void setTaskRectTranslation(int taskRectTranslationX, int taskRectTranslationY) {
+ mTaskRectTranslationX = taskRectTranslationX;
+ mTaskRectTranslationY = taskRectTranslationY;
+ }
+
+ /**
* Adds animation for all the components corresponding to transition from an app to overview.
*/
public void addAppToOverviewAnim(PendingAnimation pa, TimeInterpolator interpolator) {
@@ -320,20 +332,20 @@
mMatrix.postTranslate(insets.left, insets.top);
mMatrix.postScale(scale, scale);
- // Apply TaskView matrix: translate, scroll
+ // Apply TaskView matrix: taskRect, translate
mMatrix.postTranslate(mTaskRect.left, mTaskRect.top);
- mOrientationState.getOrientationHandler().set(mMatrix, MATRIX_POST_TRANSLATE,
+ mOrientationState.getOrientationHandler().setPrimary(mMatrix, MATRIX_POST_TRANSLATE,
taskPrimaryTranslation.value);
mOrientationState.getOrientationHandler().setSecondary(mMatrix, MATRIX_POST_TRANSLATE,
taskSecondaryTranslation.value);
- mOrientationState.getOrientationHandler().set(
+ mOrientationState.getOrientationHandler().setPrimary(
mMatrix, MATRIX_POST_TRANSLATE, recentsViewScroll.value);
// Apply RecentsView matrix
mMatrix.postScale(recentsViewScale.value, recentsViewScale.value, mPivot.x, mPivot.y);
mOrientationState.getOrientationHandler().setSecondary(mMatrix, MATRIX_POST_TRANSLATE,
recentsViewSecondaryTranslation.value);
- mOrientationState.getOrientationHandler().set(mMatrix, MATRIX_POST_TRANSLATE,
+ mOrientationState.getOrientationHandler().setPrimary(mMatrix, MATRIX_POST_TRANSLATE,
recentsViewPrimaryTranslation.value);
applyWindowToHomeRotation(mMatrix);
diff --git a/quickstep/src/com/android/quickstep/views/FloatingTaskView.java b/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
index e2ffa18..325ec04 100644
--- a/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
@@ -71,9 +71,34 @@
mImageView.setLayerType(LAYER_TYPE_HARDWARE, null);
mSplitPlaceholderView = findViewById(R.id.split_placeholder);
mSplitPlaceholderView.setAlpha(0);
- mSplitPlaceholderView.setBackgroundColor(getResources().getColor(android.R.color.white));
}
+ private void init(StatefulActivity launcher, TaskView originalView, RectF positionOut) {
+ mStartingPosition = positionOut;
+ updateInitialPositionForView(originalView);
+ final InsettableFrameLayout.LayoutParams lp =
+ (InsettableFrameLayout.LayoutParams) getLayoutParams();
+
+ mSplitPlaceholderView.setLayoutParams(new FrameLayout.LayoutParams(lp.width, lp.height));
+ positionOut.round(mOutline);
+ setPivotX(0);
+ setPivotY(0);
+
+ // Copy bounds of exiting thumbnail into ImageView
+ TaskThumbnailView thumbnail = originalView.getThumbnail();
+ mImageView.setImageBitmap(thumbnail.getThumbnail());
+ mImageView.setVisibility(VISIBLE);
+
+ mOrientationHandler = originalView.getRecentsView().getPagedOrientationHandler();
+ mSplitPlaceholderView.setIconView(originalView.getIconView(),
+ launcher.getDeviceProfile().overviewTaskIconDrawableSizePx);
+ mSplitPlaceholderView.getIconView().setRotation(mOrientationHandler.getDegreesRotated());
+ }
+
+ /**
+ * Configures and returns a an instance of {@link FloatingTaskView} initially matching the
+ * appearance of {@code originalView}.
+ */
public static FloatingTaskView getFloatingTaskView(StatefulActivity launcher,
TaskView originalView, RectF positionOut) {
final BaseDragLayer dragLayer = launcher.getDragLayer();
@@ -81,28 +106,7 @@
final FloatingTaskView floatingView = (FloatingTaskView) launcher.getLayoutInflater()
.inflate(R.layout.floating_split_select_view, parent, false);
- floatingView.mStartingPosition = positionOut;
- floatingView.updateInitialPositionForView(originalView);
- final InsettableFrameLayout.LayoutParams lp =
- (InsettableFrameLayout.LayoutParams) floatingView.getLayoutParams();
-
- floatingView.mSplitPlaceholderView.setLayoutParams(
- new FrameLayout.LayoutParams(lp.width, lp.height));
- positionOut.round(floatingView.mOutline);
- floatingView.setPivotX(0);
- floatingView.setPivotY(0);
-
- // Copy bounds of exiting thumbnail into ImageView
- TaskThumbnailView thumbnail = originalView.getThumbnail();
- floatingView.mImageView.setImageBitmap(thumbnail.getThumbnail());
- floatingView.mImageView.setVisibility(VISIBLE);
-
- floatingView.mOrientationHandler =
- originalView.getRecentsView().getPagedOrientationHandler();
- floatingView.mSplitPlaceholderView.setIconView(originalView.getIconView(),
- launcher.getDeviceProfile().overviewTaskIconDrawableSizePx);
- floatingView.mSplitPlaceholderView.getIconView()
- .setRotation(floatingView.mOrientationHandler.getDegreesRotated());
+ floatingView.init(launcher, originalView, positionOut);
parent.addView(floatingView);
return floatingView;
}
diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
index 30b55a8..4771d1e 100644
--- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
@@ -4,7 +4,6 @@
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
import android.content.Context;
-import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;
@@ -50,8 +49,6 @@
private final float[] mIcon2CenterCoords = new float[2];
private TransformingTouchDelegate mIcon2TouchDelegate;
@Nullable private StagedSplitBounds mSplitBoundsConfig;
- private final Rect mPrimaryTempRect = new Rect();
- private final Rect mSecondaryTempRect = new Rect();
public GroupedTaskView(Context context) {
super(context);
@@ -74,12 +71,12 @@
}
public void bind(Task primary, Task secondary, RecentsOrientedState orientedState,
- StagedSplitBounds splitBoundsConfig) {
+ @Nullable StagedSplitBounds splitBoundsConfig) {
super.bind(primary, orientedState);
mSecondaryTask = secondary;
mTaskIdContainer[1] = secondary.key.id;
mTaskIdAttributeContainer[1] = new TaskIdAttributeContainer(secondary, mSnapshotView2,
- STAGE_POSITION_BOTTOM_OR_RIGHT);
+ mIconView2, STAGE_POSITION_BOTTOM_OR_RIGHT);
mTaskIdAttributeContainer[0].setStagePosition(STAGE_POSITION_TOP_OR_LEFT);
mSnapshotView2.bind(secondary);
mSplitBoundsConfig = splitBoundsConfig;
@@ -120,14 +117,6 @@
}
}
- protected boolean showTaskMenuWithContainer(IconView iconView) {
- if (mActivity.getDeviceProfile().overviewShowAsGrid) {
- return TaskMenuViewWithArrow.Companion.showForTask(mTaskIdAttributeContainer[0]);
- } else {
- return TaskMenuView.showForTask(mTaskIdAttributeContainer[0]);
- }
- }
-
public void updateSplitBoundsConfig(StagedSplitBounds stagedSplitBounds) {
mSplitBoundsConfig = stagedSplitBounds;
invalidate();
@@ -159,8 +148,8 @@
@Nullable
@Override
public RunnableList launchTaskAnimated() {
- getRecentsView().getSplitPlaceholder().launchTasks(mTask, mSecondaryTask,
- STAGE_POSITION_TOP_OR_LEFT, null /*callback*/,
+ getRecentsView().getSplitPlaceholder().launchTasks(this /*groupedTaskView*/,
+ null /*callback*/,
false /* freezeTaskList */);
return null;
}
@@ -239,10 +228,8 @@
int taskIconHeight = deviceProfile.overviewTaskIconSizePx;
boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
- mSnapshotView.getBoundsOnScreen(mPrimaryTempRect);
- mSnapshotView2.getBoundsOnScreen(mSecondaryTempRect);
getPagedOrientationHandler().setSplitIconParams(mIconView, mIconView2,
- taskIconHeight, mPrimaryTempRect, mSecondaryTempRect,
+ taskIconHeight, mSnapshotView.getWidth(), mSnapshotView.getHeight(),
isRtl, deviceProfile, mSplitBoundsConfig);
}
diff --git a/quickstep/src/com/android/quickstep/views/IconView.java b/quickstep/src/com/android/quickstep/views/IconView.java
index ccb1a99..5895c05 100644
--- a/quickstep/src/com/android/quickstep/views/IconView.java
+++ b/quickstep/src/com/android/quickstep/views/IconView.java
@@ -87,6 +87,14 @@
return mDrawable;
}
+ public int getDrawableWidth() {
+ return mDrawableWidth;
+ }
+
+ public int getDrawableHeight() {
+ return mDrawableHeight;
+ }
+
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index 5d6b656..a2e9e57 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -104,8 +104,14 @@
@Override
public void onStateTransitionComplete(LauncherState finalState) {
- setOverlayEnabled(finalState == OVERVIEW || finalState == OVERVIEW_MODAL_TASK);
+ boolean isOverlayEnabled = finalState == OVERVIEW || finalState == OVERVIEW_MODAL_TASK;
+ setOverlayEnabled(isOverlayEnabled);
setFreezeViewVisibility(false);
+
+ if (isOverlayEnabled) {
+ runActionOnRemoteHandles(remoteTargetHandle ->
+ remoteTargetHandle.getTaskViewSimulator().setDrawsBelowRecents(true));
+ }
}
@Override
diff --git a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
index f3b6a63..b6bf59f 100644
--- a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
+++ b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
@@ -20,7 +20,6 @@
import android.content.res.Configuration;
import android.graphics.Rect;
import android.util.AttributeSet;
-import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.FrameLayout;
@@ -31,7 +30,6 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Insettable;
import com.android.launcher3.R;
-import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.util.MultiValueAlpha;
import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
import com.android.quickstep.SysUINavigationMode;
@@ -114,10 +112,6 @@
protected void onFinishInflate() {
super.onFinishInflate();
findViewById(R.id.action_screenshot).setOnClickListener(this);
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.NO_SCREENSHOT, "Inflated OverviewActionsView and added screenshot"
- + " listener.");
- }
mSplitButton = findViewById(R.id.action_split);
mSplitButton.setOnClickListener(this);
@@ -129,19 +123,11 @@
* @param callbacks for callbacks, or {@code null} to clear the listener.
*/
public void setCallbacks(T callbacks) {
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.NO_SCREENSHOT, "OverviewActionsView setCallbacks: " + callbacks);
- }
mCallbacks = callbacks;
}
@Override
public void onClick(View view) {
- if (TestProtocol.sDebugTracing) {
- Log.d(TestProtocol.NO_SCREENSHOT, "OverviewActionsView - onClick"
- + " callbacks: " + mCallbacks + " view id: " + view.getId() + " "
- + " is screenshot? " + (view.getId() == R.id.action_screenshot));
- }
if (mCallbacks == null) {
return;
}
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 92f1a67..3aa8d46 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -747,7 +747,7 @@
int primarySize = mOrientationHandler.getPrimaryValue(getWidth(), getHeight());
int scroll = OverScroll.dampedScroll(getUndampedOverScrollShift(), primarySize);
- mOrientationHandler.set(canvas, CANVAS_TRANSLATE, scroll);
+ mOrientationHandler.setPrimary(canvas, CANVAS_TRANSLATE, scroll);
if (mOverScrollShift != scroll) {
mOverScrollShift = scroll;
@@ -838,6 +838,7 @@
* Update the thumbnail of the task.
* @param refreshNow Refresh immediately if it's true.
*/
+ @Nullable
public TaskView updateThumbnail(int taskId, ThumbnailData thumbnailData, boolean refreshNow) {
TaskView taskView = getTaskViewByTaskId(taskId);
if (taskView != null) {
@@ -1043,6 +1044,7 @@
return getLastGridTaskView(getTopRowIdArray(), getBottomRowIdArray());
}
+ @Nullable
private TaskView getLastGridTaskView(IntArray topRowIdArray, IntArray bottomRowIdArray) {
if (topRowIdArray.isEmpty() && bottomRowIdArray.isEmpty()) {
return null;
@@ -1335,6 +1337,7 @@
return;
}
+ mLoadPlanEverApplied = true;
if (taskGroups == null || taskGroups.isEmpty()) {
removeTasksViewsAndClearAllButton();
onTaskStackUpdated();
@@ -1437,7 +1440,6 @@
resetTaskVisuals();
onTaskStackUpdated();
updateEnabledOverlays();
- mLoadPlanEverApplied = true;
}
private boolean isModal() {
@@ -1506,17 +1508,6 @@
}
}
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
- // Since we reuse the same mLiveTileTaskViewSimulator in the RecentsView, we need
- // to reset the params after it settles in Overview from swipe up so that we don't
- // render with obsolete param values.
- runActionOnRemoteHandles(remoteTargetHandle -> {
- TaskViewSimulator simulator = remoteTargetHandle.getTaskViewSimulator();
- simulator.taskPrimaryTranslation.value = 0;
- simulator.taskSecondaryTranslation.value = 0;
- simulator.fullScreenProgress.value = 0;
- simulator.recentsViewScale.value = 1;
- });
-
// Similar to setRunningTaskHidden below, reapply the state before runningTaskView is
// null.
if (!mRunningTaskShowScreenshot) {
@@ -1904,8 +1895,9 @@
setEnableDrawingLiveTile(false);
runActionOnRemoteHandles(remoteTargetHandle -> {
remoteTargetHandle.getTransformParams().setTargetSet(null);
- remoteTargetHandle.getTaskViewSimulator().setDrawsBelowRecents(true);
+ remoteTargetHandle.getTaskViewSimulator().setDrawsBelowRecents(false);
});
+ resetFromSplitSelectionState();
mSplitSelectStateController.resetState();
// These are relatively expensive and don't need to be done this frame (RecentsView isn't
@@ -2620,10 +2612,8 @@
clampToProgress(FINAL_FRAME, 0, 0.5f));
});
}
- boolean isTaskInBottomGridRow = showAsGrid() && !mTopRowIdSet.contains(
- taskView.getTaskViewId()) && taskView.getTaskViewId() != mFocusedTaskViewId;
anim.setFloat(taskView, VIEW_ALPHA, 0,
- clampToProgress(isTaskInBottomGridRow ? ACCEL : FINAL_FRAME, 0, 0.5f));
+ clampToProgress(isOnGridBottomRow(taskView) ? ACCEL : FINAL_FRAME, 0, 0.5f));
FloatProperty<TaskView> secondaryViewTranslate =
taskView.getSecondaryDissmissTranslationProperty();
int secondaryTaskDimension = mOrientationHandler.getSecondaryDimension(taskView);
@@ -3014,6 +3004,10 @@
@SuppressWarnings("WrongCall")
private void onEnd(boolean success) {
+ // Reset task translations as they may have updated via animations in
+ // createTaskDismissAnimation
+ resetTaskVisuals();
+
if (success) {
if (shouldRemoveTask) {
if (dismissedTaskView.getTask() != null) {
@@ -3030,10 +3024,6 @@
}
}
- // Reset task translations as they may have updated via animations in
- // createTaskDismissAnimation
- resetTaskVisuals();
-
int pageToSnapTo = mCurrentPage;
mCurrentPageScrollDiff = 0;
int taskViewIdToSnapTo = -1;
@@ -3115,22 +3105,23 @@
} else if (dismissedIndex < pageToSnapTo || pageToSnapTo == taskCount - 1) {
pageToSnapTo--;
}
+ boolean isHomeTaskDismissed = dismissedTaskView == getHomeTaskView();
removeViewInLayout(dismissedTaskView);
mTopRowIdSet.remove(dismissedTaskViewId);
if (taskCount == 1) {
removeViewInLayout(mClearAllButton);
- startHome();
+ if (isHomeTaskDismissed) {
+ updateEmptyMessage();
+ } else {
+ startHome();
+ }
} else {
// Update focus task and its size.
- if (finalIsFocusedTaskDismissed) {
- if (finalNextFocusedTaskView != null) {
- mFocusedTaskViewId = finalNextFocusedTaskView.getTaskViewId();
- mTopRowIdSet.remove(mFocusedTaskViewId);
- finalNextFocusedTaskView.animateIconScaleAndDimIntoView();
- } else {
- mFocusedTaskViewId = -1;
- }
+ if (finalIsFocusedTaskDismissed && finalNextFocusedTaskView != null) {
+ mFocusedTaskViewId = finalNextFocusedTaskView.getTaskViewId();
+ mTopRowIdSet.remove(mFocusedTaskViewId);
+ finalNextFocusedTaskView.animateIconScaleAndDimIntoView();
}
updateTaskSize(/*isTaskDismissal=*/ true);
updateChildTaskOrientations();
@@ -4024,6 +4015,7 @@
// * Focused Task
updateGridProperties();
resetFromSplitSelectionState();
+ updateScrollSynchronously();
}
});
@@ -4045,7 +4037,6 @@
resetTaskVisuals();
mSplitHiddenTaskViewIndex = -1;
if (mSplitHiddenTaskView != null) {
- mSplitHiddenTaskView.setTranslationY(0);
mSplitHiddenTaskView.setVisibility(VISIBLE);
mSplitHiddenTaskView = null;
}
@@ -4360,15 +4351,22 @@
RemoteTargetGluer gluer = new RemoteTargetGluer(getContext(), getSizeStrategy());
mRemoteTargetHandles = gluer.assignTargetsForSplitScreen(recentsAnimationTargets);
mSplitBoundsConfig = gluer.getStagedSplitBounds();
- if (mSyncTransactionApplier != null) {
- // Add release check to the targets from the RemoteTargetGluer and not the targets
- // passed in because in the event we're in split screen, we use the passed in targets
- // to create new RemoteAnimationTargets in assignTargetsForSplitScreen(), and the
- // mSyncTransactionApplier doesn't get transferred over
- runActionOnRemoteHandles(remoteTargetHandle -> remoteTargetHandle
- .getTransformParams().getTargetSet()
- .addReleaseCheck(mSyncTransactionApplier));
- }
+ // Add release check to the targets from the RemoteTargetGluer and not the targets
+ // passed in because in the event we're in split screen, we use the passed in targets
+ // to create new RemoteAnimationTargets in assignTargetsForSplitScreen(), and the
+ // mSyncTransactionApplier doesn't get transferred over
+ runActionOnRemoteHandles(remoteTargetHandle -> {
+ final TransformParams params = remoteTargetHandle.getTransformParams();
+ if (mSyncTransactionApplier != null) {
+ params.setSyncTransactionApplier(mSyncTransactionApplier);
+ params.getTargetSet().addReleaseCheck(mSyncTransactionApplier);
+ }
+
+ TaskViewSimulator tvs = remoteTargetHandle.getTaskViewSimulator();
+ tvs.setOrientationState(mOrientationState);
+ tvs.setDp(mActivity.getDeviceProfile());
+ tvs.recentsViewScale.value = 1;
+ });
TaskView runningTaskView = getRunningTaskView();
if (runningTaskView instanceof GroupedTaskView) {
@@ -4378,13 +4376,6 @@
// notified.
((GroupedTaskView) runningTaskView).updateSplitBoundsConfig(mSplitBoundsConfig);
}
- for (RemoteTargetHandle remoteTargetHandle : mRemoteTargetHandles) {
- TaskViewSimulator tvs = remoteTargetHandle.getTaskViewSimulator();
- tvs.setOrientationState(mOrientationState);
- tvs.setDp(mActivity.getDeviceProfile());
- tvs.setDrawsBelowRecents(true);
- tvs.recentsViewScale.value = 1;
- }
}
/** Helper to avoid writing some for-loops to iterate over {@link #mRemoteTargetHandles} */
@@ -4506,9 +4497,8 @@
}
private int getFirstViewIndex() {
- return mShowAsGridLastOnLayout && mFocusedTaskViewId != -1
- ? indexOfChild(getFocusedTaskView())
- : 0;
+ TaskView focusedTaskView = mShowAsGridLastOnLayout ? getFocusedTaskView() : null;
+ return focusedTaskView != null ? indexOfChild(focusedTaskView) : 0;
}
private int getLastViewIndex() {
@@ -4685,17 +4675,12 @@
}
/**
- * Returns how many pixels the task is offset on the currently laid out secondary axis
- * according to {@link #mGridProgress}.
+ * @return true if the task in on the top of the grid
*/
- public float getGridTranslationSecondary(int pageIndex) {
- TaskView taskView = getTaskViewAt(pageIndex);
- if (taskView == null) {
- return 0;
- }
-
- return mOrientationHandler.getSecondaryValue(taskView.getGridTranslationX(),
- taskView.getGridTranslationY());
+ public boolean isOnGridBottomRow(TaskView taskView) {
+ return showAsGrid()
+ && !mTopRowIdSet.contains(taskView.getTaskViewId())
+ && taskView.getTaskViewId() != mFocusedTaskViewId;
}
public Consumer<MotionEvent> getEventDispatcher(float navbarRotation) {
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt b/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt
index 5059f8b..06a5793 100644
--- a/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt
@@ -23,14 +23,18 @@
import android.graphics.drawable.ShapeDrawable
import android.graphics.drawable.shapes.RectShape
import android.util.AttributeSet
+import android.view.Gravity
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
+import android.widget.FrameLayout
import android.widget.LinearLayout
import com.android.launcher3.BaseDraggingActivity
import com.android.launcher3.DeviceProfile
+import com.android.launcher3.InsettableFrameLayout
import com.android.launcher3.R
import com.android.launcher3.popup.ArrowPopup
+import com.android.launcher3.popup.RoundedArrowDrawable
import com.android.launcher3.popup.SystemShortcut
import com.android.launcher3.util.Themes
import com.android.quickstep.KtR
@@ -41,34 +45,66 @@
companion object {
const val TAG = "TaskMenuViewWithArrow"
- fun showForTask(taskContainer: TaskIdAttributeContainer): Boolean {
+ fun showForTask(
+ taskContainer: TaskIdAttributeContainer,
+ alignSecondRow: Boolean = false
+ ): Boolean {
val activity = BaseDraggingActivity
- .fromContext<BaseDraggingActivity>(taskContainer.taskView.context)
+ .fromContext<BaseDraggingActivity>(taskContainer.taskView.context)
val taskMenuViewWithArrow = activity.layoutInflater
- .inflate(KtR.layout.task_menu_with_arrow, activity.dragLayer, false) as TaskMenuViewWithArrow<*>
+ .inflate(
+ KtR.layout.task_menu_with_arrow,
+ activity.dragLayer,
+ false
+ ) as TaskMenuViewWithArrow<*>
- return taskMenuViewWithArrow.populateAndShowForTask(taskContainer)
+ return taskMenuViewWithArrow.populateAndShowForTask(taskContainer, alignSecondRow)
}
}
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
- constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
+ constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(
+ context,
+ attrs,
+ defStyleAttr
+ )
init {
clipToOutline = true
+
+ shouldScaleArrow = true
+ // This synchronizes the arrow and menu to open at the same time
+ OPEN_CHILD_FADE_START_DELAY = OPEN_FADE_START_DELAY
+ OPEN_CHILD_FADE_DURATION = OPEN_FADE_DURATION
+ CLOSE_FADE_START_DELAY = CLOSE_CHILD_FADE_START_DELAY
+ CLOSE_FADE_DURATION = CLOSE_CHILD_FADE_DURATION
}
+ private var alignSecondRow: Boolean = false
+ private val extraSpaceForSecondRowAlignment: Int
+ get() = if (alignSecondRow) optionMeasuredHeight else 0
private val menuWidth = context.resources.getDimensionPixelSize(R.dimen.task_menu_width_grid)
private lateinit var taskView: TaskView
private lateinit var optionLayout: LinearLayout
private lateinit var taskContainer: TaskIdAttributeContainer
+ private var optionMeasuredHeight = 0
+ private val arrowHorizontalPadding: Int
+ get() = if (taskView.isFocusedTask)
+ resources.getDimensionPixelSize(KtR.dimen.task_menu_horizontal_padding)
+ else
+ 0
+
+ private var iconView: IconView? = null
+ private var scrim: View? = null
+ private val scrimAlpha = 0.8f
+
override fun isOfType(type: Int): Boolean = type and TYPE_TASK_MENU != 0
override fun getTargetObjectLocation(outPos: Rect?) {
- popupContainer.getDescendantRectRelativeToSelf(taskView.iconView, outPos)
+ popupContainer.getDescendantRectRelativeToSelf(taskContainer.iconView, outPos)
}
override fun onControllerInterceptTouchEvent(ev: MotionEvent?): Boolean {
@@ -86,18 +122,35 @@
optionLayout = findViewById(KtR.id.menu_option_layout)
}
- private fun populateAndShowForTask(taskContainer: TaskIdAttributeContainer): Boolean {
+ private fun populateAndShowForTask(
+ taskContainer: TaskIdAttributeContainer,
+ alignSecondRow: Boolean
+ ): Boolean {
if (isAttachedToWindow) {
return false
}
taskView = taskContainer.taskView
this.taskContainer = taskContainer
+ this.alignSecondRow = alignSecondRow
if (!populateMenu()) return false
+ addScrim()
show()
return true
}
+ private fun addScrim() {
+ scrim = View(context).apply {
+ layoutParams = FrameLayout.LayoutParams(
+ FrameLayout.LayoutParams.MATCH_PARENT,
+ FrameLayout.LayoutParams.MATCH_PARENT
+ )
+ setBackgroundColor(Themes.getAttrColor(context, R.attr.overviewScrimColor))
+ alpha = 0f
+ }
+ popupContainer.addView(scrim)
+ }
+
/** @return true if successfully able to populate task view menu, false otherwise
*/
private fun populateMenu(): Boolean {
@@ -147,21 +200,145 @@
}
override fun assignMarginsAndBackgrounds(viewGroup: ViewGroup) {
- assignMarginsAndBackgrounds(this, Themes.getAttrColor(context, com.android.internal.R.attr.colorSurface))
+ assignMarginsAndBackgrounds(
+ this,
+ Themes.getAttrColor(context, com.android.internal.R.attr.colorSurface)
+ )
}
override fun onCreateOpenAnimation(anim: AnimatorSet) {
- anim.play(
- ObjectAnimator.ofFloat(
- taskContainer.thumbnailView, TaskThumbnailView.DIM_ALPHA,
- TaskView.MAX_PAGE_SCRIM_ALPHA
+ scrim?.let {
+ anim.play(
+ ObjectAnimator.ofFloat(it, View.ALPHA, 0f, scrimAlpha)
+ .setDuration(OPEN_DURATION.toLong())
)
- )
+ }
}
override fun onCreateCloseAnimation(anim: AnimatorSet) {
- anim.play(
- ObjectAnimator.ofFloat(taskContainer.thumbnailView, TaskThumbnailView.DIM_ALPHA, 0f)
- )
+ scrim?.let {
+ anim.play(
+ ObjectAnimator.ofFloat(it, View.ALPHA, scrimAlpha, 0f)
+ .setDuration(CLOSE_DURATION.toLong())
+ )
+ }
}
+
+ override fun closeComplete() {
+ super.closeComplete()
+ popupContainer.removeView(scrim)
+ popupContainer.removeView(iconView)
+ }
+
+ /**
+ * Copy the iconView from taskView to dragLayer so it can stay on top of the scrim.
+ * It needs to be called after [getTargetObjectLocation] because [mTempRect] needs to be
+ * populated.
+ */
+ private fun copyIconToDragLayer(insets: Rect) {
+ iconView = IconView(context).apply {
+ layoutParams = FrameLayout.LayoutParams(
+ taskContainer.iconView.width,
+ taskContainer.iconView.height
+ )
+ x = mTempRect.left.toFloat() - insets.left
+ y = mTempRect.top.toFloat() - insets.top
+ drawable = taskContainer.iconView.drawable
+ setDrawableSize(
+ taskContainer.iconView.drawableWidth,
+ taskContainer.iconView.drawableHeight
+ )
+ }
+
+ popupContainer.addView(iconView)
+ }
+
+ /**
+ * Orients this container to the left or right of the given icon, aligning with the first option
+ * or second.
+ *
+ * These are the preferred orientations, in order (RTL prefers right-aligned over left):
+ * - Right and first option aligned
+ * - Right and second option aligned
+ * - Left and first option aligned
+ * - Left and second option aligned
+ *
+ * So we always align right if there is enough horizontal space
+ */
+ override fun orientAboutObject() {
+ measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED)
+ // Needed for offsets later
+ optionMeasuredHeight = optionLayout.getChildAt(0).measuredHeight
+ val extraHorizontalSpace = (mArrowHeight + mArrowOffsetVertical + arrowHorizontalPadding)
+
+ val widthWithArrow = measuredWidth + paddingLeft + paddingRight + extraHorizontalSpace
+ getTargetObjectLocation(mTempRect)
+ val dragLayer: InsettableFrameLayout = popupContainer
+ val insets = dragLayer.insets
+
+ copyIconToDragLayer(insets)
+
+ // Put this menu to the right of the icon if there is space,
+ // which means the arrow is left aligned with the menu
+ val rightAlignedMenuStartX = mTempRect.left - widthWithArrow
+ val leftAlignedMenuStartX = mTempRect.right + extraHorizontalSpace
+ mIsLeftAligned = if (mIsRtl) {
+ rightAlignedMenuStartX + insets.left < 0
+ } else {
+ leftAlignedMenuStartX + (widthWithArrow - extraHorizontalSpace) + insets.left <
+ dragLayer.width - insets.right
+ }
+
+ var menuStartX = if (mIsLeftAligned) leftAlignedMenuStartX else rightAlignedMenuStartX
+
+ // Offset y so that the arrow and row are center-aligned with the original icon.
+ val iconHeight = mTempRect.height()
+ val yOffset = (optionMeasuredHeight - iconHeight) / 2
+ var menuStartY = mTempRect.top - yOffset - extraSpaceForSecondRowAlignment
+
+ // Insets are added later, so subtract them now.
+ menuStartX -= insets.left
+ menuStartY -= insets.top
+
+ x = menuStartX.toFloat()
+ y = menuStartY.toFloat()
+
+ val lp = layoutParams as FrameLayout.LayoutParams
+ val arrowLp = mArrow.layoutParams as FrameLayout.LayoutParams
+ lp.gravity = Gravity.TOP
+ arrowLp.gravity = lp.gravity
+ }
+
+ override fun addArrow() {
+ popupContainer.addView(mArrow)
+ mArrow.x = getArrowX()
+ mArrow.y = y + (optionMeasuredHeight / 2) - (mArrowHeight / 2) +
+ extraSpaceForSecondRowAlignment
+
+ updateArrowColor()
+
+ // This is inverted (x = height, y = width) because the arrow is rotated
+ mArrow.pivotX = if (mIsLeftAligned) 0f else mArrowHeight.toFloat()
+ mArrow.pivotY = 0f
+ }
+
+ private fun getArrowX(): Float {
+ return if (mIsLeftAligned)
+ x - mArrowHeight
+ else
+ x + measuredWidth + mArrowOffsetVertical
+ }
+
+ override fun updateArrowColor() {
+ mArrow.background = RoundedArrowDrawable(
+ mArrowWidth.toFloat(),
+ mArrowHeight.toFloat(),
+ mArrowPointRadius.toFloat(),
+ mIsLeftAligned,
+ mArrowColor
+ )
+ elevation = mElevation
+ mArrow.elevation = mElevation
+ }
+
}
\ No newline at end of file
diff --git a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
index f8368ae..d91669a 100644
--- a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -148,10 +148,11 @@
public void setThumbnail(@Nullable Task task, @Nullable ThumbnailData thumbnailData,
boolean refreshNow) {
mTask = task;
+ boolean thumbnailWasNull = mThumbnailData == null;
mThumbnailData =
(thumbnailData != null && thumbnailData.thumbnail != null) ? thumbnailData : null;
if (refreshNow) {
- refresh();
+ refresh(thumbnailWasNull && mThumbnailData != null);
}
}
@@ -162,14 +163,22 @@
/** Updates the shader, paint, matrix to redraw. */
public void refresh() {
+ refresh(false);
+ }
+
+ /**
+ * Updates the shader, paint, matrix to redraw.
+ * @param shouldRefreshOverlay whether to re-initialize overlay
+ */
+ private void refresh(boolean shouldRefreshOverlay) {
if (mThumbnailData != null && mThumbnailData.thumbnail != null) {
Bitmap bm = mThumbnailData.thumbnail;
bm.prepareToDraw();
mBitmapShader = new BitmapShader(bm, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
mPaint.setShader(mBitmapShader);
updateThumbnailMatrix();
- if (mOverlayEnabled) {
- getTaskOverlay().refreshActionVisibility(mThumbnailData);
+ if (shouldRefreshOverlay) {
+ refreshOverlay();
}
} else {
mBitmapShader = null;
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index 3da7893..e8077cf 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -30,6 +30,7 @@
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_LAUNCH_TAP;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED;
import static java.lang.annotation.RetentionPolicy.SOURCE;
@@ -38,6 +39,7 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
+import android.annotation.IdRes;
import android.app.ActivityOptions;
import android.content.Context;
import android.content.Intent;
@@ -539,7 +541,7 @@
mTask = task;
mTaskIdContainer[0] = mTask.key.id;
mTaskIdAttributeContainer[0] = new TaskIdAttributeContainer(task, mSnapshotView,
- STAGE_POSITION_UNDEFINED);
+ mIconView, STAGE_POSITION_UNDEFINED);
mSnapshotView.bind(task);
setOrientationState(orientedState);
}
@@ -648,7 +650,7 @@
recentsView.getDepthController());
anim.addListener(new AnimatorListenerAdapter() {
@Override
- public void onAnimationStart(Animator animator) {
+ public void onAnimationStart(Animator animation) {
recentsView.runActionOnRemoteHandles(
(Consumer<RemoteTargetHandle>) remoteTargetHandle ->
remoteTargetHandle
@@ -658,11 +660,6 @@
@Override
public void onAnimationEnd(Animator animator) {
- recentsView.runActionOnRemoteHandles(
- (Consumer<RemoteTargetHandle>) remoteTargetHandle ->
- remoteTargetHandle
- .getTaskViewSimulator()
- .setDrawsBelowRecents(true));
mIsClickableAsLiveTile = true;
}
});
@@ -842,10 +839,14 @@
}
protected boolean showTaskMenuWithContainer(IconView iconView) {
+ TaskIdAttributeContainer menuContainer =
+ mTaskIdAttributeContainer[iconView == mIconView ? 0 : 1];
if (mActivity.getDeviceProfile().overviewShowAsGrid) {
- return TaskMenuViewWithArrow.Companion.showForTask(mTaskIdAttributeContainer[0]);
+ boolean alignSecondRow = getRecentsView().isOnGridBottomRow(menuContainer.getTaskView())
+ && mActivity.getDeviceProfile().isLandscape;
+ return TaskMenuViewWithArrow.Companion.showForTask(menuContainer, alignSecondRow);
} else {
- return TaskMenuView.showForTask(mTaskIdAttributeContainer[0]);
+ return TaskMenuView.showForTask(menuContainer);
}
}
@@ -1305,10 +1306,14 @@
getContext().getText(R.string.accessibility_close)));
final Context context = getContext();
- // TODO(b/200609838) Determine which task to run A11y action on when in split screen
- for (SystemShortcut s : TaskOverlayFactory.getEnabledShortcuts(this,
- mActivity.getDeviceProfile(), mTaskIdAttributeContainer[0])) {
- info.addAction(s.createAccessibilityAction(context));
+ for (TaskIdAttributeContainer taskContainer : mTaskIdAttributeContainer) {
+ if (taskContainer == null) {
+ continue;
+ }
+ for (SystemShortcut s : TaskOverlayFactory.getEnabledShortcuts(this,
+ mActivity.getDeviceProfile(), taskContainer)) {
+ info.addAction(s.createAccessibilityAction(context));
+ }
}
if (mDigitalWellBeingToast.hasLimit()) {
@@ -1339,12 +1344,16 @@
return true;
}
- // TODO(b/200609838) Determine which task to run A11y action on when in split screen
- for (SystemShortcut s : TaskOverlayFactory.getEnabledShortcuts(this,
- mActivity.getDeviceProfile(), mTaskIdAttributeContainer[0])) {
- if (s.hasHandlerForAction(action)) {
- s.onClick(this);
- return true;
+ for (TaskIdAttributeContainer taskContainer : mTaskIdAttributeContainer) {
+ if (taskContainer == null) {
+ continue;
+ }
+ for (SystemShortcut s : TaskOverlayFactory.getEnabledShortcuts(this,
+ mActivity.getDeviceProfile(), taskContainer)) {
+ if (s.hasHandlerForAction(action)) {
+ s.onClick(this);
+ return true;
+ }
}
}
@@ -1561,20 +1570,25 @@
mScale = previewWidth / (previewWidth + currentInsetsLeft + currentInsetsRight);
}
}
-
}
public class TaskIdAttributeContainer {
private final TaskThumbnailView mThumbnailView;
private final Task mTask;
+ private final IconView mIconView;
/** Defaults to STAGE_POSITION_UNDEFINED if in not a split screen task view */
private @SplitConfigurationOptions.StagePosition int mStagePosition;
+ @IdRes
+ private final int mA11yNodeId;
public TaskIdAttributeContainer(Task task, TaskThumbnailView thumbnailView,
- int stagePosition) {
+ IconView iconView, int stagePosition) {
this.mTask = task;
this.mThumbnailView = thumbnailView;
+ this.mIconView = iconView;
this.mStagePosition = stagePosition;
+ this.mA11yNodeId = (stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) ?
+ R.id.split_bottomRight_appInfo : R.id.split_topLeft_appInfo;
}
public TaskThumbnailView getThumbnailView() {
@@ -1593,6 +1607,10 @@
return TaskView.this;
}
+ public IconView getIconView() {
+ return mIconView;
+ }
+
public int getStagePosition() {
return mStagePosition;
}
@@ -1600,5 +1618,9 @@
void setStagePosition(@SplitConfigurationOptions.StagePosition int stagePosition) {
this.mStagePosition = stagePosition;
}
+
+ public int getA11yNodeId() {
+ return mA11yNodeId;
+ }
}
}
diff --git a/res/layout/floating_split_select_view.xml b/res/layout/floating_split_select_view.xml
index e184b91..8d47f4e 100644
--- a/res/layout/floating_split_select_view.xml
+++ b/res/layout/floating_split_select_view.xml
@@ -14,7 +14,7 @@
android:id="@+id/split_placeholder"
android:layout_width="match_parent"
android:layout_height="@dimen/split_placeholder_size"
- android:background="@android:color/white"
+ android:background="?android:colorPrimary"
android:visibility="gone" />
</com.android.quickstep.views.FloatingTaskView>
\ No newline at end of file
diff --git a/res/layout/widget_cell_content.xml b/res/layout/widget_cell_content.xml
index b27b505..0f6fc6c 100644
--- a/res/layout/widget_cell_content.xml
+++ b/res/layout/widget_cell_content.xml
@@ -33,6 +33,14 @@
android:layout_height="match_parent"
android:importantForAccessibility="no"
android:layout_gravity="fill"/>
+
+ <ImageView
+ android:id="@+id/widget_badge"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:importantForAccessibility="no"
+ android:layout_gravity="end|bottom"
+ android:layout_margin="@dimen/profile_badge_margin"/>
</com.android.launcher3.widget.WidgetCellPreview>
<!-- The name of the widget. -->
diff --git a/res/raw/all_set_page_bg.json b/res/raw/all_set_page_bg.json
new file mode 100644
index 0000000..9705837
--- /dev/null
+++ b/res/raw/all_set_page_bg.json
@@ -0,0 +1 @@
+{"v":"5.7.8","fr":24,"ip":0,"op":72,"w":2472,"h":5352,"nm":"3Second_MAIN_Welcome","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 60","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[1508,1364,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":240,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"PinkFlower","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":72,"s":[56]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.07,"y":0.986},"o":{"x":0.167,"y":0.167},"t":0,"s":[1505.832,1379.455,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.773,"y":0.01},"t":38,"s":[1505.832,575,0],"to":[0,0,0],"ti":[0,0,0]},{"t":72,"s":[1505.832,1379.455,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-3514.717,-358.642,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[75.615,-96.908],[89.338,-70.276],[111.99,-50.668],[111.764,-20.709],[122.709,7.18],[108.586,33.602],[105.316,63.383],[80.533,80.216],[63.797,105.066],[34.03,108.453],[7.663,122.679],[-20.269,111.845],[-50.226,112.189],[-69.924,89.614],[-96.61,75.997],[-103.56,46.854],[-120.861,22.395],[-113.472,-6.639],[-117.425,-36.337],[-97.389,-58.612],[-87.087,-86.745],[-58.996,-97.158],[-36.8,-117.281],[-7.086,-113.445],[21.918,-120.948],[46.446,-103.744]],"o":[[-75.615,96.909],[-89.338,70.276],[-111.99,50.668],[-111.764,20.709],[-122.709,-7.18],[-108.586,-33.602],[-105.316,-63.383],[-80.533,-80.216],[-63.797,-105.066],[-34.03,-108.453],[-7.663,-122.679],[20.269,-111.845],[50.226,-112.188],[69.924,-89.614],[96.61,-75.997],[103.56,-46.854],[120.861,-22.395],[113.472,6.64],[117.425,36.337],[97.389,58.612],[87.088,86.745],[58.995,97.158],[36.8,117.281],[7.087,113.445],[-21.918,120.948],[-46.446,103.744]],"v":[[733.209,572.105],[531.711,675.932],[383.354,847.313],[156.685,845.606],[-54.323,928.412],[-254.235,821.562],[-479.555,796.823],[-606.913,609.309],[-794.927,482.691],[-820.554,257.47],[-928.191,57.981],[-846.217,-153.353],[-848.817,-380.013],[-678.021,-529.044],[-574.99,-730.949],[-354.499,-783.537],[-169.439,-914.435],[50.234,-858.532],[274.928,-888.434],[443.46,-736.847],[656.313,-658.903],[735.094,-446.359],[887.344,-278.426],[858.327,-53.616],[915.095,165.835],[784.928,351.409]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.839215686275,0.439215686275,0.388235294118,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":4,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.839215746113,0.439215716194,0.388235324037,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":true},{"ty":"tr","p":{"a":0,"k":[-3509.952,-363.731],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Polystar 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":288,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Ellipse_Bottom","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.07],"y":[1.248]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[-56]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.719],"y":[0.172]},"t":38,"s":[-38]},{"t":72,"s":[-56]}],"ix":10},"p":{"s":true,"x":{"a":1,"k":[{"i":{"x":[0.07],"y":[1.032]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[1720]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.719],"y":[0.022]},"t":38,"s":[1544]},{"t":72,"s":[1720]}],"ix":3},"y":{"a":1,"k":[{"i":{"x":[0.07],"y":[1.034]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[4069]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.719],"y":[0.024]},"t":38,"s":[3872]},{"t":72,"s":[4069]}],"ix":4}},"a":{"a":0,"k":[164.438,1433.781,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[3079.125,4685.989],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.305882352941,0.309803921569,0.321568627451,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":4,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.882353001015,0.894118006089,0.886274988511,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":true},{"ty":"tr","p":{"a":0,"k":[164.438,1481.781],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":240,"st":0,"bm":0}],"markers":[]}
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index 3758093..0235ef0 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -36,8 +36,8 @@
<string name="add_item_request_drag_hint" msgid="5653291305078645405">"Tocca e tieni premuto il widget per spostarlo nella schermata Home"</string>
<string name="add_to_home_screen" msgid="8631549138215492708">"Aggiungi a schermata Home"</string>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"Widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g> aggiunto alla schermata Home"</string>
- <string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# widget}other{# widget}}"</string>
- <string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# scorciatoia}other{# scorciatoie}}"</string>
+ <string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# widget}one{# widget}other{# widget}}"</string>
+ <string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# scorciatoia}one{# scorciatoia}other{# scorciatoie}}"</string>
<string name="widgets_and_shortcuts_count" msgid="7209136747878365116">"<xliff:g id="WIDGETS_COUNT">%1$s</xliff:g>, <xliff:g id="SHORTCUTS_COUNT">%2$s</xliff:g>"</string>
<string name="widget_button_text" msgid="2880537293434387943">"Widget"</string>
<string name="widgets_full_sheet_search_bar_hint" msgid="8484659090860596457">"Cerca"</string>
@@ -86,7 +86,7 @@
<string name="uninstall_system_app_text" msgid="4172046090762920660">"Questa è un\'app di sistema e non può essere disinstallata."</string>
<string name="folder_hint_text" msgid="5174843001373488816">"Modifica nome"</string>
<string name="disabled_app_label" msgid="6673129024321402780">"App <xliff:g id="APP_NAME">%1$s</xliff:g> disattivata"</string>
- <string name="dotted_app_label" msgid="1704091277755818896">"{count,plural,offset:1 =1{{app_name} ha # notifica}other{{app_name} ha # notifiche}}"</string>
+ <string name="dotted_app_label" msgid="1704091277755818896">"{count,plural,offset:1 =1{{app_name} ha # notifica}one{{app_name} ha # notifica}other{{app_name} ha # notifiche}}"</string>
<string name="default_scroll_format" msgid="7475544710230993317">"Pagina %1$d di %2$d"</string>
<string name="workspace_scroll_format" msgid="8458889198184077399">"Schermata Home %1$d di %2$d"</string>
<string name="workspace_new_page" msgid="257366611030256142">"Nuova pagina Schermata Home"</string>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index 09a1200..a3c225a 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -36,8 +36,8 @@
<string name="add_item_request_drag_hint" msgid="5653291305078645405">"Toque sem soltar no widget para o mover à volta do ecrã principal"</string>
<string name="add_to_home_screen" msgid="8631549138215492708">"Adicionar ao ecrã principal"</string>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"Widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g> adicionado ao ecrã principal"</string>
- <string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# widget}other{# widgets}}"</string>
- <string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# atalho}other{# atalhos}}"</string>
+ <string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# widget}one{# widget(s)}other{# widgets}}"</string>
+ <string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# atalho}one{# atalho(s)}other{# atalhos}}"</string>
<string name="widgets_and_shortcuts_count" msgid="7209136747878365116">"<xliff:g id="WIDGETS_COUNT">%1$s</xliff:g>, <xliff:g id="SHORTCUTS_COUNT">%2$s</xliff:g>"</string>
<string name="widget_button_text" msgid="2880537293434387943">"Widgets"</string>
<string name="widgets_full_sheet_search_bar_hint" msgid="8484659090860596457">"Pesquisar"</string>
@@ -86,7 +86,7 @@
<string name="uninstall_system_app_text" msgid="4172046090762920660">"É uma app de sistema e não pode ser desinstalada."</string>
<string name="folder_hint_text" msgid="5174843001373488816">"Edite o nome"</string>
<string name="disabled_app_label" msgid="6673129024321402780">"<xliff:g id="APP_NAME">%1$s</xliff:g> desativado"</string>
- <string name="dotted_app_label" msgid="1704091277755818896">"{count,plural,offset:1 =1{A app {app_name} tem # notificação}other{A app {app_name} tem # notificações}}"</string>
+ <string name="dotted_app_label" msgid="1704091277755818896">"{count,plural,offset:1 =1{A app {app_name} tem # notificação}one{A app {app_name} tem # notificação(ões)}other{A app {app_name} tem # notificações}}"</string>
<string name="default_scroll_format" msgid="7475544710230993317">"Página %1$d de %2$d"</string>
<string name="workspace_scroll_format" msgid="8458889198184077399">"Ecrã principal %1$d de %2$d"</string>
<string name="workspace_new_page" msgid="257366611030256142">"Nova página do ecrã principal"</string>
diff --git a/res/values/id.xml b/res/values/id.xml
index ebc4075..508caff 100644
--- a/res/values/id.xml
+++ b/res/values/id.xml
@@ -21,6 +21,10 @@
<item type="id" name="view_type_widgets_list" />
<item type="id" name="view_type_widgets_header" />
<item type="id" name="view_type_widgets_search_header" />
+ <!-- Used for A11y actions in staged split to identify each task uniquely -->
+ <item type="id" name="split_topLeft_appInfo" />
+ <item type="id" name="split_bottomRight_appInfo" />
+
<!-- Do not change, must be kept in sync with sysui navbar button IDs for tests! -->
<item type="id" name="home" />
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 5f53d4e..868b5f3 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -40,9 +40,10 @@
<!-- Options for recent tasks -->
<!-- Title for an option to enter split screen mode for a given app -->
<string name="recent_task_option_split_screen">Split screen</string>
- <string translatable="false" name="split_screen_position_top">Split top</string>
- <string translatable="false" name="split_screen_position_left">Split left</string>
- <string translatable="false" name="split_screen_position_right">Split right</string>
+ <string name="split_screen_position_top">Split top</string>
+ <string name="split_screen_position_left">Split left</string>
+ <string name="split_screen_position_right">Split right</string>
+ <string name="split_app_info_accessibility">App info for %1$s</string>
<!-- Widgets -->
<!-- Message to tell the user to press and hold on a widget to add it [CHAR_LIMIT=50] -->
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 02eb1de..adb1613 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -19,6 +19,8 @@
import static android.animation.ValueAnimator.areAnimatorsEnabled;
import static com.android.launcher3.anim.Interpolators.DEACCEL_1_5;
+import static com.android.launcher3.dragndrop.DraggableView.DRAGGABLE_ICON;
+import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -37,7 +39,6 @@
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
-import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Parcelable;
import android.util.ArrayMap;
@@ -61,6 +62,7 @@
import com.android.launcher3.accessibility.DragAndDropAccessibilityDelegate;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.dragndrop.DraggableView;
import com.android.launcher3.folder.PreviewBackground;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.util.CellAndSpan;
@@ -442,18 +444,43 @@
}
if (DEBUG_VISUALIZE_OCCUPIED) {
- int[] pt = new int[2];
- ColorDrawable cd = new ColorDrawable(Color.RED);
- cd.setBounds(0, 0, mCellWidth, mCellHeight);
- for (int i = 0; i < mCountX; i++) {
- for (int j = 0; j < mCountY; j++) {
- if (mOccupied.cells[i][j]) {
- cellToPoint(i, j, pt);
- canvas.save();
- canvas.translate(pt[0], pt[1]);
- cd.draw(canvas);
- canvas.restore();
+ Rect cellBounds = new Rect();
+ // Will contain the bounds of the cell including spacing between cells.
+ Rect cellBoundsWithSpacing = new Rect();
+ int[] targetCell = new int[2];
+ int[] cellCenter = new int[2];
+ Paint debugPaint = new Paint();
+ debugPaint.setStrokeWidth(Utilities.dpToPx(1));
+ for (int x = 0; x < mCountX; x++) {
+ for (int y = 0; y < mCountY; y++) {
+ if (!mOccupied.cells[x][y]) {
+ continue;
}
+ targetCell[0] = x;
+ targetCell[1] = y;
+
+ boolean canCreateFolder = canCreateFolder(getChildAt(x, y));
+ cellToRect(x, y, 1, 1, cellBounds);
+ cellBoundsWithSpacing.set(cellBounds);
+ cellBoundsWithSpacing.inset(-mBorderSpace.x / 2, -mBorderSpace.y / 2);
+ getWorkspaceCellVisualCenter(x, y, cellCenter);
+
+ canvas.save();
+ canvas.clipRect(cellBoundsWithSpacing);
+
+ // Draw reorder drag target.
+ debugPaint.setColor(Color.RED);
+ canvas.drawCircle(cellCenter[0], cellCenter[1], getReorderRadius(targetCell),
+ debugPaint);
+
+ // Draw folder creation drag target.
+ if (canCreateFolder) {
+ debugPaint.setColor(Color.GREEN);
+ canvas.drawCircle(cellCenter[0], cellCenter[1],
+ getFolderCreationRadius(targetCell), debugPaint);
+ }
+
+ canvas.restore();
}
}
}
@@ -482,6 +509,14 @@
}
/**
+ * Returns whether dropping an icon on the given View can create (or add to) a folder.
+ */
+ private boolean canCreateFolder(View child) {
+ return child instanceof DraggableView
+ && ((DraggableView) child).getViewType() == DRAGGABLE_ICON;
+ }
+
+ /**
* Indicates the progress of the Workspace entering the SpringLoaded state; allows the
* CellLayout to update various visuals for this state.
*
@@ -817,7 +852,7 @@
}
/**
- * Given a cell coordinate and span return the point that represents the center of the regio
+ * Given a cell coordinate and span return the point that represents the center of the region
*
* @param cellX X coordinate of the cell
* @param cellY Y coordinate of the cell
@@ -830,11 +865,65 @@
result[1] = mTempRect.centerY();
}
- public float getDistanceFromCell(float x, float y, int[] cell) {
- cellToCenterPoint(cell[0], cell[1], mTmpPoint);
+ /**
+ * Returns the distance between the given coordinate and the visual center of the given cell.
+ */
+ public float getDistanceFromWorkspaceCellVisualCenter(float x, float y, int[] cell) {
+ getWorkspaceCellVisualCenter(cell[0], cell[1], mTmpPoint);
return (float) Math.hypot(x - mTmpPoint[0], y - mTmpPoint[1]);
}
+ private void getWorkspaceCellVisualCenter(int cellX, int cellY, int[] outPoint) {
+ View child = getChildAt(cellX, cellY);
+ if (child instanceof DraggableView) {
+ DraggableView draggableChild = (DraggableView) child;
+ if (draggableChild.getViewType() == DRAGGABLE_ICON) {
+ cellToPoint(cellX, cellY, outPoint);
+ draggableChild.getWorkspaceVisualDragBounds(mTempRect);
+ mTempRect.offset(outPoint[0], outPoint[1]);
+ outPoint[0] = mTempRect.centerX();
+ outPoint[1] = mTempRect.centerY();
+ return;
+ }
+ }
+ cellToCenterPoint(cellX, cellY, outPoint);
+ }
+
+ /**
+ * Returns the max distance from the center of a cell that can accept a drop to create a folder.
+ */
+ public float getFolderCreationRadius(int[] targetCell) {
+ DeviceProfile grid = mActivity.getDeviceProfile();
+ float iconVisibleRadius = ICON_VISIBLE_AREA_FACTOR * grid.iconSizePx / 2;
+ // Halfway between reorder radius and icon.
+ return (getReorderRadius(targetCell) + iconVisibleRadius) / 2;
+ }
+
+ /**
+ * Returns the max distance from the center of a cell that will start to reorder on drag over.
+ */
+ public float getReorderRadius(int[] targetCell) {
+ int[] centerPoint = mTmpPoint;
+ getWorkspaceCellVisualCenter(targetCell[0], targetCell[1], centerPoint);
+
+ Rect cellBoundsWithSpacing = mTempRect;
+ cellToRect(targetCell[0], targetCell[1], 1, 1, cellBoundsWithSpacing);
+ cellBoundsWithSpacing.inset(-mBorderSpace.x / 2, -mBorderSpace.y / 2);
+
+ if (canCreateFolder(getChildAt(targetCell[0], targetCell[1]))) {
+ // Take only the circle in the smaller dimension, to ensure we don't start reordering
+ // too soon before accepting a folder drop.
+ int minRadius = centerPoint[0] - cellBoundsWithSpacing.left;
+ minRadius = Math.min(minRadius, centerPoint[1] - cellBoundsWithSpacing.top);
+ minRadius = Math.min(minRadius, cellBoundsWithSpacing.right - centerPoint[0]);
+ minRadius = Math.min(minRadius, cellBoundsWithSpacing.bottom - centerPoint[1]);
+ return minRadius;
+ }
+ // Take up the entire cell, including space between this cell and the adjacent ones.
+ return (float) Math.hypot(cellBoundsWithSpacing.width() / 2f,
+ cellBoundsWithSpacing.height() / 2f);
+ }
+
public int getCellWidth() {
return mCellWidth;
}
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 5c16b4c..ff19918 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -16,13 +16,10 @@
package com.android.launcher3;
-import static android.util.DisplayMetrics.DENSITY_DEVICE_STABLE;
-
import static com.android.launcher3.ResourceUtils.pxFromDp;
import static com.android.launcher3.Utilities.dpiFromPx;
import static com.android.launcher3.Utilities.pxFromSp;
import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.ICON_OVERLAP_FACTOR;
-import static com.android.launcher3.util.WindowManagerCompat.MIN_TABLET_WIDTH;
import android.annotation.SuppressLint;
import android.content.Context;
@@ -64,7 +61,6 @@
public final boolean isPhone;
public final boolean transposeLayoutWithOrientation;
public final boolean isTwoPanels;
- public final boolean allowRotation;
// Device properties in current orientation
public final boolean isLandscape;
@@ -244,15 +240,9 @@
availableHeightPx = windowBounds.availableSize.y;
mInfo = info;
- // If the device's pixel density was scaled (usually via settings for A11y), use the
- // original dimensions to determine if rotation is allowed of not.
- float originalSmallestWidth = dpiFromPx(Math.min(widthPx, heightPx), DENSITY_DEVICE_STABLE);
- allowRotation = originalSmallestWidth >= MIN_TABLET_WIDTH;
- // Tablet UI does not support emulated landscape.
- isTablet = allowRotation && info.isTablet(windowBounds);
+ isTablet = info.isTablet(windowBounds);
isPhone = !isTablet;
- isTwoPanels = isTablet && useTwoPanels
- && (isLandscape || FeatureFlags.ENABLE_TWO_PANEL_HOME_IN_PORTRAIT.get());
+ isTwoPanels = isTablet && useTwoPanels;
aspectRatio = ((float) Math.max(widthPx, heightPx)) / Math.min(widthPx, heightPx);
boolean isTallDevice = Float.compare(aspectRatio, TALL_DEVICE_ASPECT_RATIO_THRESHOLD) >= 0;
@@ -823,15 +813,23 @@
Point padding = getTotalWorkspacePadding();
int numColumns = isTwoPanels ? inv.numColumns * 2 : inv.numColumns;
- int cellLayoutTotalPadding =
- isTwoPanels ? 4 * cellLayoutPaddingLeftRightPx : 2 * cellLayoutPaddingLeftRightPx;
- int screenWidthPx = availableWidthPx - padding.x - cellLayoutTotalPadding;
+ int screenWidthPx = getWorkspaceWidth(padding);
result.x = calculateCellWidth(screenWidthPx, cellLayoutBorderSpacePx.x, numColumns);
result.y = calculateCellHeight(availableHeightPx - padding.y
- cellLayoutBottomPaddingPx, cellLayoutBorderSpacePx.y, inv.numRows);
return result;
}
+ public int getWorkspaceWidth() {
+ return getWorkspaceWidth(getTotalWorkspacePadding());
+ }
+
+ public int getWorkspaceWidth(Point workspacePadding) {
+ int cellLayoutTotalPadding =
+ isTwoPanels ? 4 * cellLayoutPaddingLeftRightPx : 2 * cellLayoutPaddingLeftRightPx;
+ return availableWidthPx - workspacePadding.x - cellLayoutTotalPadding;
+ }
+
public Point getTotalWorkspacePadding() {
updateWorkspacePadding();
return new Point(workspacePadding.left + workspacePadding.right,
@@ -1032,7 +1030,6 @@
writer.println(prefix + "DeviceProfile:");
writer.println(prefix + "\t1 dp = " + mMetrics.density + " px");
- writer.println(prefix + "\tallowRotation:" + allowRotation);
writer.println(prefix + "\tisTablet:" + isTablet);
writer.println(prefix + "\tisPhone:" + isPhone);
writer.println(prefix + "\ttransposeLayoutWithOrientation:"
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index 9f3d445..68e19cb 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -383,7 +383,7 @@
case LauncherSettings.Settings.METHOD_NEW_SCREEN_ID: {
Bundle result = new Bundle();
result.putInt(LauncherSettings.Settings.EXTRA_VALUE,
- mOpenHelper.generateNewScreenId());
+ mOpenHelper.getNewScreenId());
return result;
}
case LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB: {
@@ -628,7 +628,6 @@
private final Context mContext;
private final boolean mForMigration;
private int mMaxItemId = -1;
- private int mMaxScreenId = -1;
private boolean mBackupTableExists;
private boolean mHotseatRestoreTableExists;
@@ -672,9 +671,6 @@
if (mMaxItemId == -1) {
mMaxItemId = initializeMaxItemId(getWritableDatabase());
}
- if (mMaxScreenId == -1) {
- mMaxScreenId = initializeMaxScreenId(getWritableDatabase());
- }
}
@Override
@@ -682,7 +678,6 @@
if (LOGD) Log.d(TAG, "creating new launcher database");
mMaxItemId = 1;
- mMaxScreenId = 0;
addFavoritesTable(db, false);
@@ -1043,36 +1038,19 @@
public void checkId(ContentValues values) {
int id = values.getAsInteger(Favorites._ID);
mMaxItemId = Math.max(id, mMaxItemId);
-
- Integer screen = values.getAsInteger(Favorites.SCREEN);
- Integer container = values.getAsInteger(Favorites.CONTAINER);
- if (screen != null && container != null
- && container.intValue() == Favorites.CONTAINER_DESKTOP) {
- mMaxScreenId = Math.max(screen, mMaxScreenId);
- }
}
private int initializeMaxItemId(SQLiteDatabase db) {
return getMaxId(db, "SELECT MAX(%1$s) FROM %2$s", Favorites._ID, Favorites.TABLE_NAME);
}
- // Generates a new ID to use for an workspace screen in your database. This method
- // should be only called from the main UI thread. As an exception, we do call it when we
- // call the constructor from the worker thread; however, this doesn't extend until after the
- // constructor is called, and we only pass a reference to LauncherProvider to LauncherApp
- // after that point
- public int generateNewScreenId() {
- if (mMaxScreenId < 0) {
- throw new RuntimeException("Error: max screen id was not initialized");
- }
- mMaxScreenId += 1;
- return mMaxScreenId;
- }
-
- private int initializeMaxScreenId(SQLiteDatabase db) {
- return getMaxId(db, "SELECT MAX(%1$s) FROM %2$s WHERE %3$s = %4$d AND %1$s >= 0",
+ // Returns a new ID to use for an workspace screen in your database that is greater than all
+ // existing screen IDs.
+ private int getNewScreenId() {
+ return getMaxId(getWritableDatabase(),
+ "SELECT MAX(%1$s) FROM %2$s WHERE %3$s = %4$d AND %1$s >= 0",
Favorites.SCREEN, Favorites.TABLE_NAME, Favorites.CONTAINER,
- Favorites.CONTAINER_DESKTOP);
+ Favorites.CONTAINER_DESKTOP) + 1;
}
@Thunk int loadFavorites(SQLiteDatabase db, AutoInstallsLayout loader) {
@@ -1081,7 +1059,6 @@
// Ensure that the max ids are initialized
mMaxItemId = initializeMaxItemId(db);
- mMaxScreenId = initializeMaxScreenId(db);
return count;
}
}
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index cefadf7..1ce7ebe 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -252,7 +252,7 @@
if (0 <= mCurrentPage && mCurrentPage < getPageCount()) {
newPosition = getScrollForPage(mCurrentPage) + mCurrentPageScrollDiff;
}
- mOrientationHandler.set(this, VIEW_SCROLL_TO, newPosition);
+ mOrientationHandler.setPrimary(this, VIEW_SCROLL_TO, newPosition);
mScroller.startScroll(mScroller.getCurrX(), 0, newPosition - mScroller.getCurrX(), 0);
forceFinishScroller();
}
@@ -556,7 +556,7 @@
int oldPos = mOrientationHandler.getPrimaryScroll(this);
int newPos = mScroller.getCurrX();
if (oldPos != newPos) {
- mOrientationHandler.set(this, VIEW_SCROLL_TO, mScroller.getCurrX());
+ mOrientationHandler.setPrimary(this, VIEW_SCROLL_TO, mScroller.getCurrX());
}
if (mAllowOverScroll) {
@@ -1280,7 +1280,7 @@
mLastMotionRemainder = delta - movedDelta;
if (delta != 0) {
- mOrientationHandler.set(this, VIEW_SCROLL_BY, movedDelta);
+ mOrientationHandler.setPrimary(this, VIEW_SCROLL_BY, movedDelta);
if (mAllowOverScroll) {
final float pulledToX = oldScroll + delta;
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index ce06c6e..203df0a 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -220,7 +220,6 @@
private FolderIcon mDragOverFolderIcon = null;
private boolean mCreateUserFolderOnDrop = false;
private boolean mAddToExistingFolderOnDrop = false;
- private float mMaxDistanceForFolderCreation;
// Variables relating to touch disambiguation (scrolling workspace vs. scrolling a widget)
private float mXDown;
@@ -308,8 +307,6 @@
public void setInsets(Rect insets) {
DeviceProfile grid = mLauncher.getDeviceProfile();
- mMaxDistanceForFolderCreation = grid.isTablet
- ? 0.75f * grid.iconSizePx : 0.55f * grid.iconSizePx;
mWorkspaceFadeInAdjacentScreens = grid.shouldFadeAdjacentWorkspaceScreens();
Rect padding = grid.workspacePadding;
@@ -869,13 +866,13 @@
mWorkspaceScreens.remove(emptyScreenId);
mScreenOrder.removeValue(emptyScreenId);
- int newScreenId = -1;
+ int newScreenId = LauncherSettings.Settings.call(getContext().getContentResolver(),
+ LauncherSettings.Settings.METHOD_NEW_SCREEN_ID)
+ .getInt(LauncherSettings.Settings.EXTRA_VALUE);
// Launcher database isn't aware of empty pages that are already bound, so we need to
// skip those IDs manually.
- while (newScreenId == -1 || mWorkspaceScreens.containsKey(newScreenId)) {
- newScreenId = LauncherSettings.Settings.call(getContext().getContentResolver(),
- LauncherSettings.Settings.METHOD_NEW_SCREEN_ID)
- .getInt(LauncherSettings.Settings.EXTRA_VALUE);
+ while (mWorkspaceScreens.containsKey(newScreenId)) {
+ newScreenId++;
}
mWorkspaceScreens.put(newScreenId, cl);
@@ -1774,8 +1771,8 @@
mTargetCell = findNearestArea((int) mDragViewVisualCenter[0],
(int) mDragViewVisualCenter[1], minSpanX, minSpanY, dropTargetLayout,
mTargetCell);
- float distance = dropTargetLayout.getDistanceFromCell(mDragViewVisualCenter[0],
- mDragViewVisualCenter[1], mTargetCell);
+ float distance = dropTargetLayout.getDistanceFromWorkspaceCellVisualCenter(
+ mDragViewVisualCenter[0], mDragViewVisualCenter[1], mTargetCell);
if (mCreateUserFolderOnDrop && willCreateUserFolder(d.dragInfo,
dropTargetLayout, mTargetCell, distance, true)) {
return true;
@@ -1809,7 +1806,7 @@
boolean willCreateUserFolder(ItemInfo info, CellLayout target, int[] targetCell,
float distance, boolean considerTimeout) {
- if (distance > mMaxDistanceForFolderCreation) return false;
+ if (distance > target.getFolderCreationRadius(targetCell)) return false;
View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
return willCreateUserFolder(info, dropOverView, considerTimeout);
}
@@ -1844,7 +1841,7 @@
boolean willAddToExistingUserFolder(ItemInfo dragInfo, CellLayout target, int[] targetCell,
float distance) {
- if (distance > mMaxDistanceForFolderCreation) return false;
+ if (distance > target.getFolderCreationRadius(targetCell)) return false;
View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
return willAddToExistingUserFolder(dragInfo, dropOverView);
@@ -1868,7 +1865,7 @@
boolean createUserFolderIfNecessary(View newView, int container, CellLayout target,
int[] targetCell, float distance, boolean external, DragObject d) {
- if (distance > mMaxDistanceForFolderCreation) return false;
+ if (distance > target.getFolderCreationRadius(targetCell)) return false;
View v = target.getChildAt(targetCell[0], targetCell[1]);
boolean hasntMoved = false;
@@ -1925,7 +1922,7 @@
boolean addToExistingFolderIfNecessary(View newView, CellLayout target, int[] targetCell,
float distance, DragObject d, boolean external) {
- if (distance > mMaxDistanceForFolderCreation) return false;
+ if (distance > target.getFolderCreationRadius(targetCell)) return false;
View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
if (!mAddToExistingFolderOnDrop) return false;
@@ -1989,8 +1986,8 @@
mTargetCell = findNearestArea((int) mDragViewVisualCenter[0], (int)
mDragViewVisualCenter[1], spanX, spanY, dropTargetLayout, mTargetCell);
- float distance = dropTargetLayout.getDistanceFromCell(mDragViewVisualCenter[0],
- mDragViewVisualCenter[1], mTargetCell);
+ float distance = dropTargetLayout.getDistanceFromWorkspaceCellVisualCenter(
+ mDragViewVisualCenter[0], mDragViewVisualCenter[1], mTargetCell);
// If the item being dropped is a shortcut and the nearest drop
// cell also contains a shortcut, then create a folder with the two shortcuts.
@@ -2418,7 +2415,7 @@
setCurrentDropOverCell(mTargetCell[0], mTargetCell[1]);
- float targetCellDistance = mDragTargetLayout.getDistanceFromCell(
+ float targetCellDistance = mDragTargetLayout.getDistanceFromWorkspaceCellVisualCenter(
mDragViewVisualCenter[0], mDragViewVisualCenter[1], mTargetCell);
manageFolderFeedback(targetCellDistance, d);
@@ -2431,8 +2428,9 @@
mDragTargetLayout.visualizeDropLocation(mTargetCell[0], mTargetCell[1],
item.spanX, item.spanY, d);
} else if ((mDragMode == DRAG_MODE_NONE || mDragMode == DRAG_MODE_REORDER)
- && !mReorderAlarm.alarmPending() && (mLastReorderX != reorderX ||
- mLastReorderY != reorderY)) {
+ && !mReorderAlarm.alarmPending()
+ && (mLastReorderX != reorderX || mLastReorderY != reorderY)
+ && targetCellDistance < mDragTargetLayout.getReorderRadius(mTargetCell)) {
int[] resultSpan = new int[2];
mDragTargetLayout.performReorder((int) mDragViewVisualCenter[0],
@@ -2529,7 +2527,7 @@
}
private void manageFolderFeedback(float distance, DragObject dragObject) {
- if (distance > mMaxDistanceForFolderCreation) {
+ if (distance > mDragTargetLayout.getFolderCreationRadius(mTargetCell)) {
if ((mDragMode == DRAG_MODE_ADD_TO_FOLDER
|| mDragMode == DRAG_MODE_CREATE_FOLDER)) {
setDragMode(DRAG_MODE_NONE);
@@ -2674,8 +2672,8 @@
if (pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
mTargetCell = findNearestArea(touchXY[0], touchXY[1], spanX, spanY,
cellLayout, mTargetCell);
- float distance = cellLayout.getDistanceFromCell(mDragViewVisualCenter[0],
- mDragViewVisualCenter[1], mTargetCell);
+ float distance = cellLayout.getDistanceFromWorkspaceCellVisualCenter(
+ mDragViewVisualCenter[0], mDragViewVisualCenter[1], mTargetCell);
if (willCreateUserFolder(d.dragInfo, cellLayout, mTargetCell, distance, true)
|| willAddToExistingUserFolder(
d.dragInfo, cellLayout, mTargetCell, distance)) {
@@ -2774,8 +2772,8 @@
if (touchXY != null) {
mTargetCell = findNearestArea(touchXY[0], touchXY[1], spanX, spanY,
cellLayout, mTargetCell);
- float distance = cellLayout.getDistanceFromCell(mDragViewVisualCenter[0],
- mDragViewVisualCenter[1], mTargetCell);
+ float distance = cellLayout.getDistanceFromWorkspaceCellVisualCenter(
+ mDragViewVisualCenter[0], mDragViewVisualCenter[1], mTargetCell);
if (createUserFolderIfNecessary(view, container, cellLayout, mTargetCell, distance,
true, d)) {
return;
diff --git a/src/com/android/launcher3/anim/RevealOutlineAnimation.java b/src/com/android/launcher3/anim/RevealOutlineAnimation.java
index f99dabc..f5a746f 100644
--- a/src/com/android/launcher3/anim/RevealOutlineAnimation.java
+++ b/src/com/android/launcher3/anim/RevealOutlineAnimation.java
@@ -26,9 +26,27 @@
/** Sets the progress, from 0 to 1, of the reveal animation. */
abstract void setProgress(float progress);
+ /**
+ * @see #createRevealAnimator(View, boolean, float) where startProgress is set to 0.
+ */
public ValueAnimator createRevealAnimator(final View revealView, boolean isReversed) {
- ValueAnimator va =
- isReversed ? ValueAnimator.ofFloat(1f, 0f) : ValueAnimator.ofFloat(0f, 1f);
+ return createRevealAnimator(revealView, isReversed, 0f /* startProgress */);
+ }
+
+ /**
+ * Animates the given View's ViewOutline according to {@link #setProgress(float)}.
+ * @param revealView The View whose outline we are animating.
+ * @param isReversed Whether we are hiding rather than revealing the View.
+ * @param startProgress The progress at which to start the newly created animation. Useful if
+ * the previous reveal animation was cancelled and we want to create a new animation where it
+ * left off. Note that if isReversed=true, we start at 1 - startProgress (and go to 0).
+ * @return The Animator, which the caller must start.
+ */
+ public ValueAnimator createRevealAnimator(final View revealView, boolean isReversed,
+ float startProgress) {
+ ValueAnimator va = isReversed
+ ? ValueAnimator.ofFloat(1f - startProgress, 0f)
+ : ValueAnimator.ofFloat(startProgress, 1f);
final float elevation = revealView.getElevation();
va.addListener(new AnimatorListenerAdapter() {
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index e253505..98cb5ae 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -72,6 +72,7 @@
"PROMISE_APPS_NEW_INSTALLS", true,
"Adds a promise icon to the home screen for new install sessions.");
+ // TODO: b/206508141: Long pressing on some icons on home screen cause launcher to crash.
public static final BooleanFlag ENABLE_LOCAL_COLOR_POPUPS = getDebugFlag(
"ENABLE_LOCAL_COLOR_POPUPS", false, "Enable local color extraction for popups.");
@@ -210,10 +211,6 @@
"ENABLE_TWO_PANEL_HOME", true,
"Uses two panel on home screen. Only applicable on large screen devices.");
- public static final BooleanFlag ENABLE_TWO_PANEL_HOME_IN_PORTRAIT = getDebugFlag(
- "ENABLE_TWO_PANEL_HOME_IN_PORTRAIT", true,
- "Uses two panel on home screen in portrait if ENABLE_TWO_PANEL_HOME is enabled.");
-
public static final BooleanFlag ENABLE_SCRIM_FOR_APP_LAUNCH = getDebugFlag(
"ENABLE_SCRIM_FOR_APP_LAUNCH", false,
"Enables scrim during app launch animation.");
diff --git a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
index fea15c4..a13fa55 100644
--- a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
+++ b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
@@ -16,7 +16,6 @@
package com.android.launcher3.model;
import static com.android.launcher3.WorkspaceLayoutManager.FIRST_SCREEN_ID;
-import static com.android.launcher3.WorkspaceLayoutManager.SECOND_SCREEN_ID;
import android.content.Intent;
import android.content.pm.LauncherActivityInfo;
@@ -300,11 +299,6 @@
IntSet screensToExclude = new IntSet();
if (FeatureFlags.QSB_ON_FIRST_SCREEN) {
screensToExclude.add(FIRST_SCREEN_ID);
-
- // On split display we don't want to add the new items onto the second screen.
- if (app.getInvariantDeviceProfile().isSplitDisplay) {
- screensToExclude.add(SECOND_SCREEN_ID);
- }
}
for (int screen = 0; screen < screenCount; screen++) {
diff --git a/src/com/android/launcher3/model/ModelWriter.java b/src/com/android/launcher3/model/ModelWriter.java
index 0439e75..94e06d1 100644
--- a/src/com/android/launcher3/model/ModelWriter.java
+++ b/src/com/android/launcher3/model/ModelWriter.java
@@ -292,7 +292,7 @@
FileLog.d(TAG, "removing items from db " + items.stream().map(
(item) -> item.getTargetComponent() == null ? ""
: item.getTargetComponent().getPackageName()).collect(
- Collectors.joining(",")), new Exception());
+ Collectors.joining(",")));
notifyDelete(items);
enqueueDeleteRunnable(() -> {
for (ItemInfo item : items) {
diff --git a/src/com/android/launcher3/popup/ArrowPopup.java b/src/com/android/launcher3/popup/ArrowPopup.java
index 5a1e4bf..b1a4109 100644
--- a/src/com/android/launcher3/popup/ArrowPopup.java
+++ b/src/com/android/launcher3/popup/ArrowPopup.java
@@ -77,44 +77,45 @@
extends AbstractFloatingView {
// Duration values (ms) for popup open and close animations.
- private static final int OPEN_DURATION = 276;
- private static final int OPEN_FADE_START_DELAY = 0;
- private static final int OPEN_FADE_DURATION = 38;
- private static final int OPEN_CHILD_FADE_START_DELAY = 38;
- private static final int OPEN_CHILD_FADE_DURATION = 76;
+ protected int OPEN_DURATION = 276;
+ protected int OPEN_FADE_START_DELAY = 0;
+ protected int OPEN_FADE_DURATION = 38;
+ protected int OPEN_CHILD_FADE_START_DELAY = 38;
+ protected int OPEN_CHILD_FADE_DURATION = 76;
- private static final int CLOSE_DURATION = 200;
- private static final int CLOSE_FADE_START_DELAY = 140;
- private static final int CLOSE_FADE_DURATION = 50;
- private static final int CLOSE_CHILD_FADE_START_DELAY = 0;
- private static final int CLOSE_CHILD_FADE_DURATION = 140;
+ protected int CLOSE_DURATION = 200;
+ protected int CLOSE_FADE_START_DELAY = 140;
+ protected int CLOSE_FADE_DURATION = 50;
+ protected int CLOSE_CHILD_FADE_START_DELAY = 0;
+ protected int CLOSE_CHILD_FADE_DURATION = 140;
// Index used to get background color when using local wallpaper color extraction,
private static final int DARK_COLOR_EXTRACTION_INDEX = android.R.color.system_neutral2_800;
private static final int LIGHT_COLOR_EXTRACTION_INDEX = android.R.color.system_accent2_50;
- private final Rect mTempRect = new Rect();
+ protected final Rect mTempRect = new Rect();
protected final LayoutInflater mInflater;
- private final float mOutlineRadius;
+ protected final float mOutlineRadius;
protected final T mActivityContext;
protected final boolean mIsRtl;
- private final int mArrowOffsetVertical;
- private final int mArrowOffsetHorizontal;
- private final int mArrowWidth;
- private final int mArrowHeight;
- private final int mArrowPointRadius;
- private final View mArrow;
+ protected final int mArrowOffsetVertical;
+ protected final int mArrowOffsetHorizontal;
+ protected final int mArrowWidth;
+ protected final int mArrowHeight;
+ protected final int mArrowPointRadius;
+ protected final View mArrow;
private final int mMargin;
protected boolean mIsLeftAligned;
protected boolean mIsAboveIcon;
- private int mGravity;
+ protected int mGravity;
protected AnimatorSet mOpenCloseAnimator;
protected boolean mDeferContainerRemoval;
+ protected boolean shouldScaleArrow = false;
private final GradientDrawable mRoundedTop;
private final GradientDrawable mRoundedBottom;
@@ -122,10 +123,10 @@
private Runnable mOnCloseCallback = () -> { };
// The rect string of the view that the arrow is attached to, in screen reference frame.
- private int mArrowColor;
+ protected int mArrowColor;
protected final List<LocalColorExtractor> mColorExtractors;
- private final float mElevation;
+ protected final float mElevation;
private final int mBackgroundColor;
private final String mIterateChildrenTag;
@@ -729,6 +730,14 @@
scale.setInterpolator(interpolator);
animatorSet.play(scale);
+ if (shouldScaleArrow) {
+ Animator arrowScaleAnimator = ObjectAnimator.ofFloat(mArrow, View.SCALE_Y,
+ scaleValues);
+ arrowScaleAnimator.setDuration(totalDuration);
+ arrowScaleAnimator.setInterpolator(interpolator);
+ animatorSet.play(arrowScaleAnimator);
+ }
+
fadeInChildViews(this, alphaValues, childFadeStartDelay, childFadeDuration, animatorSet);
return animatorSet;
diff --git a/src/com/android/launcher3/popup/RoundedArrowDrawable.java b/src/com/android/launcher3/popup/RoundedArrowDrawable.java
index e662d5c..436aa51 100644
--- a/src/com/android/launcher3/popup/RoundedArrowDrawable.java
+++ b/src/com/android/launcher3/popup/RoundedArrowDrawable.java
@@ -78,6 +78,32 @@
mPath.transform(pathTransform);
}
+ /**
+ * Constructor for an arrow that points to the left or right.
+ *
+ * @param width of the arrow.
+ * @param height of the arrow.
+ * @param radius of the tip of the arrow.
+ * @param isPointingLeft or not.
+ * @param color to draw the triangle.
+ */
+ public RoundedArrowDrawable(float width, float height, float radius, boolean isPointingLeft,
+ int color) {
+ mPath = new Path();
+ mPaint = new Paint();
+ mPaint.setColor(color);
+ mPaint.setStyle(Paint.Style.FILL);
+ mPaint.setAntiAlias(true);
+
+ // Make the drawable with the triangle pointing down...
+ addDownPointingRoundedTriangleToPath(width, height, radius, mPath);
+
+ // ... then rotate it to the side it needs to point.
+ Matrix pathTransform = new Matrix();
+ pathTransform.setRotate(isPointingLeft ? 90 : -90, width * 0.5f, height * 0.5f);
+ mPath.transform(pathTransform);
+ }
+
@Override
public void draw(Canvas canvas) {
canvas.drawPath(mPath, mPaint);
diff --git a/src/com/android/launcher3/popup/SystemShortcut.java b/src/com/android/launcher3/popup/SystemShortcut.java
index 826c79b..af87275 100644
--- a/src/com/android/launcher3/popup/SystemShortcut.java
+++ b/src/com/android/launcher3/popup/SystemShortcut.java
@@ -41,8 +41,8 @@
implements View.OnClickListener {
private final int mIconResId;
- private final int mLabelResId;
- private final int mAccessibilityActionId;
+ protected final int mLabelResId;
+ protected int mAccessibilityActionId;
protected final T mTarget;
protected final ItemInfo mItemInfo;
@@ -139,11 +139,43 @@
public static class AppInfo<T extends Context & ActivityContext> extends SystemShortcut<T> {
+ @Nullable
+ private SplitAccessibilityInfo mSplitA11yInfo;
+
public AppInfo(T target, ItemInfo itemInfo) {
super(R.drawable.ic_info_no_shadow, R.string.app_info_drop_target_label, target,
itemInfo);
}
+ /**
+ * Constructor used by overview for staged split to provide custom A11y information.
+ *
+ * Future improvements considerations:
+ * Have the logic in {@link #createAccessibilityAction(Context)} be moved to super
+ * call in {@link SystemShortcut#createAccessibilityAction(Context)} by having
+ * SystemShortcut be aware of TaskContainers and staged split.
+ * That way it could directly create the correct node info for any shortcut that supports
+ * split, but then we'll need custom resIDs for each pair of shortcuts.
+ */
+ public AppInfo(T target, ItemInfo itemInfo, SplitAccessibilityInfo accessibilityInfo) {
+ this(target, itemInfo);
+ mSplitA11yInfo = accessibilityInfo;
+ mAccessibilityActionId = accessibilityInfo.nodeId;
+ }
+
+ @Override
+ public AccessibilityNodeInfo.AccessibilityAction createAccessibilityAction(
+ Context context) {
+ if (mSplitA11yInfo != null && mSplitA11yInfo.containsMultipleTasks) {
+ String accessibilityLabel = context.getString(R.string.split_app_info_accessibility,
+ mSplitA11yInfo.taskTitle);
+ return new AccessibilityNodeInfo.AccessibilityAction(mAccessibilityActionId,
+ accessibilityLabel);
+ } else {
+ return super.createAccessibilityAction(context);
+ }
+ }
+
@Override
public void onClick(View view) {
dismissTaskMenuView(mTarget);
@@ -153,6 +185,19 @@
mTarget.getStatsLogManager().logger().withItemInfo(mItemInfo)
.log(LAUNCHER_SYSTEM_SHORTCUT_APP_INFO_TAP);
}
+
+ public static class SplitAccessibilityInfo {
+ public final boolean containsMultipleTasks;
+ public final CharSequence taskTitle;
+ public final int nodeId;
+
+ public SplitAccessibilityInfo(boolean containsMultipleTasks,
+ CharSequence taskTitle, int nodeId) {
+ this.containsMultipleTasks = containsMultipleTasks;
+ this.taskTitle = taskTitle;
+ this.nodeId = nodeId;
+ }
+ }
}
public static final Factory<BaseDraggingActivity> INSTALL = (activity, itemInfo) -> {
diff --git a/src/com/android/launcher3/settings/SettingsActivity.java b/src/com/android/launcher3/settings/SettingsActivity.java
index f348a33..0c39632 100644
--- a/src/com/android/launcher3/settings/SettingsActivity.java
+++ b/src/com/android/launcher3/settings/SettingsActivity.java
@@ -49,6 +49,7 @@
import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.WidgetsModel;
+import com.android.launcher3.states.RotationHelper;
import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
import java.util.Collections;
@@ -253,12 +254,13 @@
case ALLOW_ROTATION_PREFERENCE_KEY:
DeviceProfile deviceProfile = InvariantDeviceProfile.INSTANCE.get(
getContext()).getDeviceProfile(getContext());
- if (deviceProfile.allowRotation) {
+ if (deviceProfile.isTablet) {
// Launcher supports rotation by default. No need to show this setting.
return false;
}
// Initialize the UI once
- preference.setDefaultValue(false);
+ preference.setDefaultValue(
+ RotationHelper.getAllowRotationDefaultValue(deviceProfile));
return true;
case FLAGS_PREFERENCE_KEY:
diff --git a/src/com/android/launcher3/states/RotationHelper.java b/src/com/android/launcher3/states/RotationHelper.java
index 87871b1..867fd99 100644
--- a/src/com/android/launcher3/states/RotationHelper.java
+++ b/src/com/android/launcher3/states/RotationHelper.java
@@ -18,6 +18,10 @@
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LOCKED;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.util.DisplayMetrics.DENSITY_DEVICE_STABLE;
+
+import static com.android.launcher3.Utilities.dpiFromPx;
+import static com.android.launcher3.util.WindowManagerCompat.MIN_TABLET_WIDTH;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
@@ -25,7 +29,6 @@
import com.android.launcher3.BaseActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Utilities;
-import com.android.launcher3.util.ActivityTracker;
import com.android.launcher3.util.UiThreadHelper;
/**
@@ -38,6 +41,17 @@
public static final String ALLOW_ROTATION_PREFERENCE_KEY = "pref_allowRotation";
+ /**
+ * Returns the default value of {@link #ALLOW_ROTATION_PREFERENCE_KEY} preference.
+ */
+ public static boolean getAllowRotationDefaultValue(DeviceProfile deviceProfile) {
+ // If the device's pixel density was scaled (usually via settings for A11y), use the
+ // original dimensions to determine if rotation is allowed of not.
+ float originalSmallestWidth = dpiFromPx(
+ Math.min(deviceProfile.widthPx, deviceProfile.heightPx), DENSITY_DEVICE_STABLE);
+ return originalSmallestWidth >= MIN_TABLET_WIDTH;
+ }
+
public static final int REQUEST_NONE = 0;
public static final int REQUEST_ROTATE = 1;
public static final int REQUEST_LOCK = 2;
@@ -51,7 +65,7 @@
/**
* Rotation request made by
- * {@link ActivityTracker.SchedulerCallback}.
+ * {@link com.android.launcher3.util.ActivityTracker.SchedulerCallback}.
* This supersedes any other request.
*/
private int mStateHandlerRequest = REQUEST_NONE;
@@ -84,7 +98,7 @@
mSharedPrefs.registerOnSharedPreferenceChangeListener(this);
}
mHomeRotationEnabled = mSharedPrefs.getBoolean(ALLOW_ROTATION_PREFERENCE_KEY,
- mActivity.getDeviceProfile().allowRotation);
+ getAllowRotationDefaultValue(mActivity.getDeviceProfile()));
} else {
if (mSharedPrefs != null) {
mSharedPrefs.unregisterOnSharedPreferenceChangeListener(this);
@@ -98,7 +112,7 @@
if (mDestroyed) return;
boolean wasRotationEnabled = mHomeRotationEnabled;
mHomeRotationEnabled = mSharedPrefs.getBoolean(ALLOW_ROTATION_PREFERENCE_KEY,
- mActivity.getDeviceProfile().allowRotation);
+ getAllowRotationDefaultValue(mActivity.getDeviceProfile()));
if (mHomeRotationEnabled != wasRotationEnabled) {
notifyChange();
}
@@ -106,7 +120,7 @@
@Override
public void onDeviceProfileChanged(DeviceProfile dp) {
- boolean ignoreAutoRotateSettings = dp.allowRotation;
+ boolean ignoreAutoRotateSettings = dp.isTablet;
if (mIgnoreAutoRotateSettings != ignoreAutoRotateSettings) {
setIgnoreAutoRotateSettings(ignoreAutoRotateSettings);
notifyChange();
@@ -143,7 +157,7 @@
public void initialize() {
if (!mInitialized) {
mInitialized = true;
- setIgnoreAutoRotateSettings(mActivity.getDeviceProfile().allowRotation);
+ setIgnoreAutoRotateSettings(mActivity.getDeviceProfile().isTablet);
mActivity.addOnDeviceProfileChangeListener(this);
notifyChange();
}
diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java
index 9a74fb1..673b011 100644
--- a/src/com/android/launcher3/testing/TestProtocol.java
+++ b/src/com/android/launcher3/testing/TestProtocol.java
@@ -122,9 +122,7 @@
public static final String REQUEST_MOCK_SENSOR_ROTATION = "mock-sensor-rotation";
public static final String PERMANENT_DIAG_TAG = "TaplTarget";
- public static final String TASKBAR_WINDOW_CRASH = "b/201305599";
public static final String TASK_VIEW_ID_CRASH = "b/195430732";
public static final String NO_DROP_TARGET = "b/195031154";
public static final String NULL_INT_SET = "b/200572078";
- public static final String NO_SCREENSHOT = "b/202414125";
}
diff --git a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
index 93e3ea7..e127074 100644
--- a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
+++ b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
@@ -101,12 +101,12 @@
}
@Override
- public <T> void set(T target, Int2DAction<T> action, int param) {
+ public <T> void setPrimary(T target, Int2DAction<T> action, int param) {
action.call(target, 0, param);
}
@Override
- public <T> void set(T target, Float2DAction<T> action, float param) {
+ public <T> void setPrimary(T target, Float2DAction<T> action, float param) {
action.call(target, 0, param);
}
@@ -116,6 +116,12 @@
}
@Override
+ public <T> void set(T target, Int2DAction<T> action, int primaryParam,
+ int secondaryParam) {
+ action.call(target, secondaryParam, primaryParam);
+ }
+
+ @Override
public float getPrimaryDirection(MotionEvent event, int pointerIndex) {
return event.getY(pointerIndex);
}
@@ -429,7 +435,7 @@
@Override
public void setSplitIconParams(View primaryIconView, View secondaryIconView,
- int taskIconHeight, Rect primarySnapshotBounds, Rect secondarySnapshotBounds,
+ int taskIconHeight, int primarySnapshotWidth, int primarySnapshotHeight,
boolean isRtl, DeviceProfile deviceProfile, StagedSplitBounds splitConfig) {
FrameLayout.LayoutParams primaryIconParams =
(FrameLayout.LayoutParams) primaryIconView.getLayoutParams();
@@ -439,13 +445,12 @@
splitConfig.visualDividerBounds.height() :
splitConfig.visualDividerBounds.width());
- int primaryHeight = primarySnapshotBounds.height();
primaryIconParams.gravity = (isRtl ? START : END) | TOP;
- primaryIconView.setTranslationY(primaryHeight - primaryIconView.getHeight() / 2f);
+ primaryIconView.setTranslationY(primarySnapshotHeight - primaryIconView.getHeight() / 2f);
primaryIconView.setTranslationX(0);
secondaryIconParams.gravity = (isRtl ? START : END) | TOP;
- secondaryIconView.setTranslationY(primaryHeight + taskIconHeight + dividerBar);
+ secondaryIconView.setTranslationY(primarySnapshotHeight + taskIconHeight + dividerBar);
secondaryIconView.setTranslationX(0);
primaryIconView.setLayoutParams(primaryIconParams);
secondaryIconView.setLayoutParams(secondaryIconParams);
diff --git a/src/com/android/launcher3/touch/PagedOrientationHandler.java b/src/com/android/launcher3/touch/PagedOrientationHandler.java
index 2ff2feb..d954552 100644
--- a/src/com/android/launcher3/touch/PagedOrientationHandler.java
+++ b/src/com/android/launcher3/touch/PagedOrientationHandler.java
@@ -65,9 +65,10 @@
Float2DAction<Canvas> CANVAS_TRANSLATE = Canvas::translate;
Float2DAction<Matrix> MATRIX_POST_TRANSLATE = Matrix::postTranslate;
- <T> void set(T target, Int2DAction<T> action, int param);
- <T> void set(T target, Float2DAction<T> action, float param);
+ <T> void setPrimary(T target, Int2DAction<T> action, int param);
+ <T> void setPrimary(T target, Float2DAction<T> action, float param);
<T> void setSecondary(T target, Float2DAction<T> action, float param);
+ <T> void set(T target, Int2DAction<T> action, int primaryParam, int secondaryParam);
float getPrimaryDirection(MotionEvent event, int pointerIndex);
float getPrimaryVelocity(VelocityTracker velocityTracker, int pointerId);
int getMeasuredSize(View view);
@@ -152,7 +153,7 @@
void setIconAndSnapshotParams(View iconView, int taskIconMargin, int taskIconHeight,
FrameLayout.LayoutParams snapshotParams, boolean isRtl);
void setSplitIconParams(View primaryIconView, View secondaryIconView,
- int taskIconHeight, Rect primarySnapshotBounds, Rect secondarySnapshotBounds,
+ int taskIconHeight, int primarySnapshotWidth, int primarySnapshotHeight,
boolean isRtl, DeviceProfile deviceProfile, StagedSplitBounds splitConfig);
/*
diff --git a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
index 8caf886..fbc335c 100644
--- a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
+++ b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
@@ -101,12 +101,12 @@
}
@Override
- public <T> void set(T target, Int2DAction<T> action, int param) {
+ public <T> void setPrimary(T target, Int2DAction<T> action, int param) {
action.call(target, param, 0);
}
@Override
- public <T> void set(T target, Float2DAction<T> action, float param) {
+ public <T> void setPrimary(T target, Float2DAction<T> action, float param) {
action.call(target, param, 0);
}
@@ -116,6 +116,12 @@
}
@Override
+ public <T> void set(T target, Int2DAction<T> action, int primaryParam,
+ int secondaryParam) {
+ action.call(target, primaryParam, secondaryParam);
+ }
+
+ @Override
public float getPrimaryDirection(MotionEvent event, int pointerIndex) {
return event.getX(pointerIndex);
}
@@ -451,24 +457,19 @@
public void setSplitTaskSwipeRect(DeviceProfile dp, Rect outRect,
StagedSplitBounds splitInfo, int desiredStagePosition) {
boolean isLandscape = dp.isLandscape;
- float verticalDividerDiff = splitInfo.visualDividerBounds.height() / 2f;
- float horizontalDividerDiff = splitInfo.visualDividerBounds.width() / 2f;
- float diff;
if (desiredStagePosition == SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT) {
if (isLandscape) {
- diff = outRect.width() * (1f - splitInfo.leftTaskPercent) + horizontalDividerDiff;
- outRect.right -= diff;
+ outRect.right = outRect.left + (int) (outRect.width() * splitInfo.leftTaskPercent);
} else {
- diff = outRect.height() * (1f - splitInfo.topTaskPercent) + verticalDividerDiff;
- outRect.bottom -= diff;
+ outRect.bottom = outRect.top + (int) (outRect.height() * splitInfo.topTaskPercent);
}
} else {
if (isLandscape) {
- diff = outRect.width() * splitInfo.leftTaskPercent + horizontalDividerDiff;
- outRect.left += diff;
+ outRect.left += (int) (outRect.width() *
+ (splitInfo.leftTaskPercent + splitInfo.dividerWidthPercent));
} else {
- diff = outRect.height() * splitInfo.topTaskPercent + verticalDividerDiff;
- outRect.top += diff;
+ outRect.top += (int) (outRect.height() *
+ (splitInfo.topTaskPercent + splitInfo.dividerHeightPercent));
}
}
}
@@ -479,9 +480,9 @@
StagedSplitBounds splitBoundsConfig, DeviceProfile dp) {
int spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx;
int totalThumbnailHeight = parentHeight - spaceAboveSnapshot;
- int dividerBar = (splitBoundsConfig.appsStackedVertically ?
- splitBoundsConfig.visualDividerBounds.height() :
- splitBoundsConfig.visualDividerBounds.width());
+ int dividerBar = splitBoundsConfig.appsStackedVertically
+ ? (int) (splitBoundsConfig.dividerHeightPercent * parentHeight)
+ : (int) (splitBoundsConfig.dividerWidthPercent * parentWidth);
int primarySnapshotHeight;
int primarySnapshotWidth;
int secondarySnapshotHeight;
@@ -528,7 +529,7 @@
@Override
public void setSplitIconParams(View primaryIconView, View secondaryIconView,
- int taskIconHeight, Rect primarySnapshotBounds, Rect secondarySnapshotBounds,
+ int taskIconHeight, int primarySnapshotWidth, int primarySnapshotHeight,
boolean isRtl, DeviceProfile deviceProfile, StagedSplitBounds splitConfig) {
FrameLayout.LayoutParams primaryIconParams =
(FrameLayout.LayoutParams) primaryIconView.getLayoutParams();
@@ -538,15 +539,12 @@
splitConfig.visualDividerBounds.height() :
splitConfig.visualDividerBounds.width());
- int primaryWidth = primarySnapshotBounds.width();
if (deviceProfile.isLandscape) {
primaryIconParams.gravity = TOP | START;
- primaryIconView.setTranslationX(primaryWidth - primaryIconView.getWidth());
+ primaryIconView.setTranslationX(primarySnapshotWidth - primaryIconView.getWidth());
primaryIconView.setTranslationY(0);
-
secondaryIconParams.gravity = TOP | START;
- secondaryIconView.setTranslationX(primaryWidth + dividerBar);
- secondaryIconView.setTranslationY(0);
+ secondaryIconView.setTranslationX(primarySnapshotWidth + dividerBar);
} else {
primaryIconParams.gravity = TOP | CENTER_HORIZONTAL;
primaryIconView.setTranslationX(-(primaryIconView.getWidth()) / 2f);
@@ -554,8 +552,8 @@
secondaryIconParams.gravity = TOP | CENTER_HORIZONTAL;
secondaryIconView.setTranslationX(secondaryIconView.getWidth() / 2f);
- secondaryIconView.setTranslationY(0);
}
+ secondaryIconView.setTranslationY(0);
primaryIconView.setLayoutParams(primaryIconParams);
secondaryIconView.setLayoutParams(secondaryIconParams);
}
diff --git a/src/com/android/launcher3/touch/SeascapePagedViewHandler.java b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
index a0dde22..539e3f8 100644
--- a/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
+++ b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
@@ -132,10 +132,10 @@
@Override
public void setSplitIconParams(View primaryIconView, View secondaryIconView,
- int taskIconHeight, Rect primarySnapshotBounds, Rect secondarySnapshotBounds,
+ int taskIconHeight, int primarySnapshotWidth, int primarySnapshotHeight,
boolean isRtl, DeviceProfile deviceProfile, StagedSplitBounds splitConfig) {
super.setSplitIconParams(primaryIconView, secondaryIconView, taskIconHeight,
- primarySnapshotBounds, secondarySnapshotBounds, isRtl, deviceProfile, splitConfig);
+ primarySnapshotWidth, primarySnapshotHeight, isRtl, deviceProfile, splitConfig);
FrameLayout.LayoutParams primaryIconParams =
(FrameLayout.LayoutParams) primaryIconView.getLayoutParams();
FrameLayout.LayoutParams secondaryIconParams =
diff --git a/src/com/android/launcher3/util/DisplayController.java b/src/com/android/launcher3/util/DisplayController.java
index 2068c29..c050c6c 100644
--- a/src/com/android/launcher3/util/DisplayController.java
+++ b/src/com/android/launcher3/util/DisplayController.java
@@ -261,6 +261,13 @@
PortraitSize realSize = new PortraitSize(newInfo.currentSize.x, newInfo.currentSize.y);
PortraitSize expectedSize = oldInfo.mInternalDisplays.get(
ApiWrapper.getUniqueId(display));
+ if (newInfo.supportedBounds.size() != oldInfo.supportedBounds.size()) {
+ Log.e("b/198965093",
+ "Inconsistent number of displays"
+ + "\ndisplay state: " + display.getState()
+ + "\noldInfo.supportedBounds: " + oldInfo.supportedBounds
+ + "\nnewInfo.supportedBounds: " + newInfo.supportedBounds);
+ }
if (!realSize.equals(expectedSize) && display.getState() == Display.STATE_OFF) {
Log.e("b/198965093", "Display size changed while display is off, ignoring change");
return;
diff --git a/src/com/android/launcher3/util/SplitConfigurationOptions.java b/src/com/android/launcher3/util/SplitConfigurationOptions.java
index 6aef38f..53b1c3e 100644
--- a/src/com/android/launcher3/util/SplitConfigurationOptions.java
+++ b/src/com/android/launcher3/util/SplitConfigurationOptions.java
@@ -99,6 +99,8 @@
// This class is orientation-agnostic, so we compute both for later use
public final float topTaskPercent;
public final float leftTaskPercent;
+ public final float dividerWidthPercent;
+ public final float dividerHeightPercent;
/**
* If {@code true}, that means at the time of creation of this object, the
* split-screened apps were vertically stacked. This is useful in scenarios like
@@ -130,6 +132,8 @@
leftTaskPercent = this.leftTopBounds.width() / (float) rightBottomBounds.right;
topTaskPercent = this.leftTopBounds.height() / (float) rightBottomBounds.bottom;
+ dividerWidthPercent = visualDividerBounds.width() / (float) rightBottomBounds.right;
+ dividerHeightPercent = visualDividerBounds.height() / (float) rightBottomBounds.bottom;
}
}
diff --git a/src/com/android/launcher3/widget/DatabaseWidgetPreviewLoader.java b/src/com/android/launcher3/widget/DatabaseWidgetPreviewLoader.java
index aacb9c5..784f4f0 100644
--- a/src/com/android/launcher3/widget/DatabaseWidgetPreviewLoader.java
+++ b/src/com/android/launcher3/widget/DatabaseWidgetPreviewLoader.java
@@ -25,11 +25,15 @@
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
+import android.graphics.Rect;
import android.graphics.RectF;
+import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.Process;
+import android.os.UserHandle;
+import android.util.ArrayMap;
import android.util.Log;
import android.util.Size;
@@ -40,6 +44,7 @@
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.icons.BitmapRenderer;
+import com.android.launcher3.icons.FastBitmapDrawable;
import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.icons.ShadowGenerator;
import com.android.launcher3.icons.cache.HandlerRunnable;
@@ -60,6 +65,9 @@
private final Context mContext;
private final float mPreviewBoxCornerRadius;
+ private final UserHandle mMyUser = Process.myUserHandle();
+ private final ArrayMap<UserHandle, Bitmap> mUserBadges = new ArrayMap<>();
+
public DatabaseWidgetPreviewLoader(Context context) {
mContext = context;
float previewCornerRadius = RoundedCornerEnforcement.computeEnforcedRadius(context);
@@ -101,6 +109,52 @@
}
/**
+ * Returns a drawable that can be used as a badge for the user or null.
+ */
+ // @UiThread
+ public Drawable getBadgeForUser(UserHandle user, int badgeSize) {
+ if (mMyUser.equals(user)) {
+ return null;
+ }
+
+ Bitmap badgeBitmap = getUserBadge(user, badgeSize);
+ FastBitmapDrawable d = new FastBitmapDrawable(badgeBitmap);
+ d.setFilterBitmap(true);
+ d.setBounds(0, 0, badgeBitmap.getWidth(), badgeBitmap.getHeight());
+ return d;
+ }
+
+ private Bitmap getUserBadge(UserHandle user, int badgeSize) {
+ synchronized (mUserBadges) {
+ Bitmap badgeBitmap = mUserBadges.get(user);
+ if (badgeBitmap != null) {
+ return badgeBitmap;
+ }
+
+ final Resources res = mContext.getResources();
+ badgeBitmap = Bitmap.createBitmap(badgeSize, badgeSize, Bitmap.Config.ARGB_8888);
+
+ Drawable drawable = mContext.getPackageManager().getUserBadgedDrawableForDensity(
+ new BitmapDrawable(res, badgeBitmap), user,
+ new Rect(0, 0, badgeSize, badgeSize),
+ 0);
+ if (drawable instanceof BitmapDrawable) {
+ badgeBitmap = ((BitmapDrawable) drawable).getBitmap();
+ } else {
+ badgeBitmap.eraseColor(Color.TRANSPARENT);
+ Canvas c = new Canvas(badgeBitmap);
+ drawable.setBounds(0, 0, badgeSize, badgeSize);
+ drawable.draw(c);
+ c.setBitmap(null);
+ }
+
+ mUserBadges.put(user, badgeBitmap);
+ return badgeBitmap;
+ }
+ }
+
+
+ /**
* Generates the widget preview from either the {@link WidgetManagerHelper} or cache
* and add badge at the bottom right corner.
*
diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java
index f1ac656..c92fe5a 100644
--- a/src/com/android/launcher3/widget/WidgetCell.java
+++ b/src/com/android/launcher3/widget/WidgetCell.java
@@ -36,6 +36,7 @@
import android.view.ViewPropertyAnimator;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
+import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RemoteViews;
import android.widget.TextView;
@@ -47,6 +48,7 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
+import com.android.launcher3.icons.BaseIconFactory;
import com.android.launcher3.icons.FastBitmapDrawable;
import com.android.launcher3.icons.RoundDrawableWrapper;
import com.android.launcher3.icons.cache.HandlerRunnable;
@@ -111,6 +113,7 @@
private FrameLayout mWidgetImageContainer;
private WidgetImageView mWidgetImage;
+ private ImageView mWidgetBadge;
private TextView mWidgetName;
private TextView mWidgetDims;
private TextView mWidgetDescription;
@@ -166,6 +169,7 @@
mWidgetImageContainer = findViewById(R.id.widget_preview_container);
mWidgetImage = findViewById(R.id.widget_preview);
+ mWidgetBadge = findViewById(R.id.widget_badge);
mWidgetName = findViewById(R.id.widget_name);
mWidgetDims = findViewById(R.id.widget_dims);
mWidgetDescription = findViewById(R.id.widget_description);
@@ -195,6 +199,8 @@
mWidgetImage.animate().cancel();
mWidgetImage.setDrawable(null);
mWidgetImage.setVisibility(View.VISIBLE);
+ mWidgetBadge.setImageDrawable(null);
+ mWidgetBadge.setVisibility(View.GONE);
mWidgetName.setText(null);
mWidgetDims.setText(null);
mWidgetDescription.setText(null);
@@ -349,6 +355,7 @@
mAppWidgetHostViewPreview = null;
}
}
+
if (mAnimatePreview) {
mWidgetImageContainer.setAlpha(0f);
ViewPropertyAnimator anim = mWidgetImageContainer.animate();
@@ -362,6 +369,20 @@
}
}
+ /** Used to show the badge when the widget is in the recommended section
+ */
+ public void showBadge() {
+ Drawable badge = mWidgetPreviewLoader.getBadgeForUser(mItem.user,
+ BaseIconFactory.getBadgeSizeForIconSize(
+ mActivity.getDeviceProfile().allAppsIconSizePx));
+ if (badge == null) {
+ mWidgetBadge.setVisibility(View.GONE);
+ } else {
+ mWidgetBadge.setVisibility(View.VISIBLE);
+ mWidgetBadge.setImageDrawable(badge);
+ }
+ }
+
private void setContainerSize(int width, int height) {
LayoutParams layoutParams = (LayoutParams) mWidgetImageContainer.getLayoutParams();
layoutParams.width = width;
diff --git a/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java b/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java
index c986007..06cc65e 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java
@@ -109,6 +109,7 @@
for (WidgetItem widgetItem : widgetItems) {
WidgetCell widgetCell = addItemCell(tableRow);
widgetCell.applyFromCellItem(widgetItem, data.mPreviewScale);
+ widgetCell.showBadge();
}
addView(tableRow);
}
diff --git a/tests/Android.bp b/tests/Android.bp
index 3670c37..c329ece 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -23,7 +23,7 @@
// Source code used for test
filegroup {
name: "launcher-tests-src",
- srcs: ["src/**/*.java"],
+ srcs: ["src/**/*.java", "src/**/*.kt"],
}
// Source code used for oop test helpers
diff --git a/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java b/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java
deleted file mode 100644
index 16f024e..0000000
--- a/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java
+++ /dev/null
@@ -1,221 +0,0 @@
-package com.android.launcher3.model;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.Rect;
-import android.util.Pair;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.LauncherSettings.Favorites;
-import com.android.launcher3.model.BgDataModel.Callbacks;
-import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.model.data.WorkspaceItemInfo;
-import com.android.launcher3.util.ContentWriter;
-import com.android.launcher3.util.Executors;
-import com.android.launcher3.util.GridOccupancy;
-import com.android.launcher3.util.IntArray;
-import com.android.launcher3.util.IntSparseArrayMap;
-import com.android.launcher3.util.LauncherModelHelper;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Tests for {@link AddWorkspaceItemsTask}
- */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class AddWorkspaceItemsTaskTest {
-
- private final ComponentName mComponent1 = new ComponentName("a", "b");
- private final ComponentName mComponent2 = new ComponentName("b", "b");
-
- private Context mTargetContext;
- private InvariantDeviceProfile mIdp;
- private LauncherAppState mAppState;
- private LauncherModelHelper mModelHelper;
-
- private IntArray mExistingScreens;
- private IntArray mNewScreens;
- private IntSparseArrayMap<GridOccupancy> mScreenOccupancy;
-
- @Before
- public void setup() {
- mModelHelper = new LauncherModelHelper();
- mTargetContext = mModelHelper.sandboxContext;
- mIdp = InvariantDeviceProfile.INSTANCE.get(mTargetContext);
- mIdp.numColumns = mIdp.numRows = 5;
- mAppState = LauncherAppState.getInstance(mTargetContext);
-
- mExistingScreens = new IntArray();
- mScreenOccupancy = new IntSparseArrayMap<>();
- mNewScreens = new IntArray();
- }
-
- @After
- public void tearDown() {
- mModelHelper.destroy();
- }
-
- private AddWorkspaceItemsTask newTask(ItemInfo... items) {
- List<Pair<ItemInfo, Object>> list = new ArrayList<>();
- for (ItemInfo item : items) {
- list.add(Pair.create(item, null));
- }
- return new AddWorkspaceItemsTask(list);
- }
-
- @Test
- public void testFindSpaceForItem_prefers_second() throws Exception {
- mIdp.isSplitDisplay = false;
-
- // First screen has only one hole of size 1
- int nextId = setupWorkspaceWithHoles(1, 1, new Rect(2, 2, 3, 3));
-
- // Second screen has 2 holes of sizes 3x2 and 2x3
- setupWorkspaceWithHoles(nextId, 2, new Rect(2, 0, 5, 2), new Rect(0, 2, 2, 5));
-
- int[] spaceFound = newTask().findSpaceForItem(
- mAppState, mModelHelper.getBgDataModel(), mExistingScreens, mNewScreens, 1, 1);
- assertEquals(1, spaceFound[0]);
- assertTrue(mScreenOccupancy.get(spaceFound[0])
- .isRegionVacant(spaceFound[1], spaceFound[2], 1, 1));
-
- // Find a larger space
- spaceFound = newTask().findSpaceForItem(
- mAppState, mModelHelper.getBgDataModel(), mExistingScreens, mNewScreens, 2, 3);
- assertEquals(2, spaceFound[0]);
- assertTrue(mScreenOccupancy.get(spaceFound[0])
- .isRegionVacant(spaceFound[1], spaceFound[2], 2, 3));
- }
-
- @Test
- public void testFindSpaceForItem_prefers_third_on_split_display() throws Exception {
- mIdp.isSplitDisplay = true;
- // First screen has only one hole of size 1
- int nextId = setupWorkspaceWithHoles(1, 1, new Rect(2, 2, 3, 3));
-
- // Second screen has 2 holes of sizes 3x2 and 2x3
- setupWorkspaceWithHoles(nextId, 2, new Rect(2, 0, 5, 2), new Rect(0, 2, 2, 5));
-
- int[] spaceFound = newTask().findSpaceForItem(
- mAppState, mModelHelper.getBgDataModel(), mExistingScreens, mNewScreens, 1, 1);
- // For split display, it picks the next screen, even if there is enough space
- // on previous screen
- assertEquals(2, spaceFound[0]);
- assertTrue(mScreenOccupancy.get(spaceFound[0])
- .isRegionVacant(spaceFound[1], spaceFound[2], 1, 1));
- }
-
- @Test
- public void testFindSpaceForItem_adds_new_screen() throws Exception {
- // First screen has 2 holes of sizes 3x2 and 2x3
- setupWorkspaceWithHoles(1, 1, new Rect(2, 0, 5, 2), new Rect(0, 2, 2, 5));
-
- IntArray oldScreens = mExistingScreens.clone();
- int[] spaceFound = newTask().findSpaceForItem(
- mAppState, mModelHelper.getBgDataModel(), mExistingScreens, mNewScreens, 3, 3);
- assertFalse(oldScreens.contains(spaceFound[0]));
- assertTrue(mNewScreens.contains(spaceFound[0]));
- }
-
- @Test
- public void testAddItem_existing_item_ignored() throws Exception {
- WorkspaceItemInfo info = new WorkspaceItemInfo();
- info.intent = new Intent().setComponent(mComponent1);
-
- // Setup a screen with a hole
- setupWorkspaceWithHoles(1, 1, new Rect(2, 2, 3, 3));
-
- // Nothing was added
- assertTrue(mModelHelper.executeTaskForTest(newTask(info)).isEmpty());
- }
-
- @Test
- public void testAddItem_some_items_added() throws Exception {
- Callbacks callbacks = mock(Callbacks.class);
- Executors.MAIN_EXECUTOR.submit(() -> mModelHelper.getModel().addCallbacks(callbacks)).get();
-
- WorkspaceItemInfo info = new WorkspaceItemInfo();
- info.intent = new Intent().setComponent(mComponent1);
-
- WorkspaceItemInfo info2 = new WorkspaceItemInfo();
- info2.intent = new Intent().setComponent(mComponent2);
-
- // Setup a screen with a hole
- setupWorkspaceWithHoles(1, 1, new Rect(2, 2, 3, 3));
-
- mModelHelper.executeTaskForTest(newTask(info, info2)).get(0).run();
- ArgumentCaptor<ArrayList> notAnimated = ArgumentCaptor.forClass(ArrayList.class);
- ArgumentCaptor<ArrayList> animated = ArgumentCaptor.forClass(ArrayList.class);
-
- // only info2 should be added because info was already added to the workspace
- // in setupWorkspaceWithHoles()
- verify(callbacks).bindAppsAdded(any(IntArray.class), notAnimated.capture(),
- animated.capture());
- assertTrue(notAnimated.getValue().isEmpty());
-
- assertEquals(1, animated.getValue().size());
- assertTrue(animated.getValue().contains(info2));
- }
-
- private int setupWorkspaceWithHoles(int startId, int screenId, Rect... holes) throws Exception {
- return mModelHelper.executeSimpleTask(
- model -> writeWorkspaceWithHoles(model, startId, screenId, holes));
- }
-
- private int writeWorkspaceWithHoles(
- BgDataModel bgDataModel, int startId, int screenId, Rect... holes) {
- GridOccupancy occupancy = new GridOccupancy(mIdp.numColumns, mIdp.numRows);
- occupancy.markCells(0, 0, mIdp.numColumns, mIdp.numRows, true);
- for (Rect r : holes) {
- occupancy.markCells(r, false);
- }
-
- mExistingScreens.add(screenId);
- mScreenOccupancy.append(screenId, occupancy);
-
- for (int x = 0; x < mIdp.numColumns; x++) {
- for (int y = 0; y < mIdp.numRows; y++) {
- if (!occupancy.cells[x][y]) {
- continue;
- }
-
- WorkspaceItemInfo info = new WorkspaceItemInfo();
- info.intent = new Intent().setComponent(mComponent1);
- info.id = startId++;
- info.screenId = screenId;
- info.cellX = x;
- info.cellY = y;
- info.container = LauncherSettings.Favorites.CONTAINER_DESKTOP;
- bgDataModel.addItem(mTargetContext, info, false);
-
- ContentWriter writer = new ContentWriter(mTargetContext);
- info.writeToValues(writer);
- writer.put(Favorites._ID, info.id);
- mTargetContext.getContentResolver().insert(Favorites.CONTENT_URI,
- writer.getValues(mTargetContext));
- }
- }
- return startId;
- }
-}
diff --git a/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.kt b/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.kt
new file mode 100644
index 0000000..e315658
--- /dev/null
+++ b/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.kt
@@ -0,0 +1,325 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+package com.android.launcher3.model
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.graphics.Rect
+import android.util.Pair
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.InvariantDeviceProfile
+import com.android.launcher3.LauncherAppState
+import com.android.launcher3.LauncherSettings
+import com.android.launcher3.model.data.ItemInfo
+import com.android.launcher3.model.data.WorkspaceItemInfo
+import com.android.launcher3.util.*
+import com.android.launcher3.util.IntArray
+import org.junit.After
+import org.junit.Assert.*
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.*
+import org.mockito.ArgumentMatchers.any
+import org.mockito.Mockito.verify
+import kotlin.collections.ArrayList
+
+/**
+ * Tests for [AddWorkspaceItemsTask]
+ */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AddWorkspaceItemsTaskTest {
+
+ @Captor
+ private lateinit var animatedItemArgumentCaptor: ArgumentCaptor<ArrayList<ItemInfo>>
+
+ @Captor
+ private lateinit var notAnimatedItemArgumentCaptor: ArgumentCaptor<ArrayList<ItemInfo>>
+
+ @Mock
+ private lateinit var dataModelCallbacks: BgDataModel.Callbacks
+
+ private lateinit var mTargetContext: Context
+ private lateinit var mIdp: InvariantDeviceProfile
+ private lateinit var mAppState: LauncherAppState
+ private lateinit var mModelHelper: LauncherModelHelper
+ private lateinit var mExistingScreens: IntArray
+ private lateinit var mNewScreens: IntArray
+ private lateinit var mScreenOccupancy: IntSparseArrayMap<GridOccupancy>
+
+ private val emptyScreenHoles = listOf(Rect(0, 0, 5, 5))
+ private val fullScreenHoles = emptyList<Rect>()
+
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ mModelHelper = LauncherModelHelper()
+ mTargetContext = mModelHelper.sandboxContext
+ mIdp = InvariantDeviceProfile.INSTANCE[mTargetContext]
+ mIdp.numRows = 5
+ mIdp.numColumns = mIdp.numRows
+ mAppState = LauncherAppState.getInstance(mTargetContext)
+ mExistingScreens = IntArray()
+ mScreenOccupancy = IntSparseArrayMap()
+ mNewScreens = IntArray()
+ Executors.MAIN_EXECUTOR.submit { mModelHelper.model.addCallbacks(dataModelCallbacks) }.get()
+ }
+
+ @After
+ fun tearDown() {
+ mModelHelper.destroy()
+ }
+
+ @Test
+ fun justEnoughSpaceOnFirstScreen_whenFindSpaceForItem_thenReturnFirstScreenId() {
+ setupWorkspacesWithHoles(
+ screen1 = listOf(Rect(2, 2, 3, 3)), // 1x1 hole
+ // 2 holes of sizes 3x2 and 2x3
+ screen2 = listOf(Rect(2, 0, 5, 2), Rect(0, 2, 2, 5)),
+ )
+
+ val spaceFound = newTask().findSpaceForItem(
+ mAppState, mModelHelper.bgDataModel, mExistingScreens, mNewScreens, 1, 1)
+ assertEquals(1, spaceFound[0])
+ assertTrue(mScreenOccupancy[spaceFound[0]]
+ .isRegionVacant(spaceFound[1], spaceFound[2], 1, 1))
+ }
+
+ @Test
+ fun notEnoughSpaceOnFirstScreen_whenFindSpaceForItem_thenReturnSecondScreenId() {
+ setupWorkspacesWithHoles(
+ screen1 = listOf(Rect(2, 2, 3, 3)), // 1x1 hole
+ // 2 holes of sizes 3x2 and 2x3
+ screen2 = listOf(Rect(2, 0, 5, 2), Rect(0, 2, 2, 5)),
+ )
+
+ // Find a larger space
+ val spaceFound = newTask().findSpaceForItem(
+ mAppState, mModelHelper.bgDataModel, mExistingScreens, mNewScreens, 2, 3)
+ assertEquals(2, spaceFound[0])
+ assertTrue(mScreenOccupancy[spaceFound[0]]
+ .isRegionVacant(spaceFound[1], spaceFound[2], 2, 3))
+ }
+
+ @Test
+ fun notEnoughSpaceOnExistingScreens_whenFindSpaceForItem_thenReturnNewScreenId() {
+ setupWorkspacesWithHoles(
+ // 2 holes of sizes 3x2 and 2x3
+ screen1 = listOf(Rect(2, 0, 5, 2), Rect(0, 2, 2, 5)),
+ // 2 holes of sizes 1x2 and 2x2
+ screen2 = listOf(Rect(1, 0, 2, 2), Rect(3, 2, 5, 4)),
+ )
+
+ val oldScreens = mExistingScreens.clone()
+ val spaceFound = newTask().findSpaceForItem(
+ mAppState, mModelHelper.bgDataModel, mExistingScreens, mNewScreens, 3, 3)
+ assertFalse(oldScreens.contains(spaceFound[0]))
+ assertTrue(mNewScreens.contains(spaceFound[0]))
+ }
+
+ @Test
+ fun enoughSpaceOnFirstScreen_whenTaskRuns_thenAddItemToFirstScreen() {
+ val workspaceHoles = createWorkspaceHoles(
+ screen1 = listOf(Rect(2, 2, 3, 3)), // 1x1 space
+ screen2 = listOf(Rect(2, 0, 5, 2)), // 3x2 space
+ )
+ val addedItems = testAddItems(workspaceHoles, getNewItem())
+ assertEquals(1, addedItems.size)
+ assertEquals(1, addedItems.first().itemInfo.screenId)
+ }
+
+ @Test
+ fun firstPageIsFull_whenTaskRuns_thenAddItemToSecondScreen() {
+ val workspaceHoles = createWorkspaceHoles(
+ screen1 = fullScreenHoles,
+ )
+ val addedItems = testAddItems(workspaceHoles, getNewItem())
+ assertEquals(1, addedItems.size)
+ assertEquals(2, addedItems.first().itemInfo.screenId)
+ }
+
+ @Test
+ fun firstScreenIsEmptyButSecondIsNotEmpty_whenTaskRuns_thenAddItemToSecondScreen() {
+ val workspaceHoles = createWorkspaceHoles(
+ screen1 = emptyScreenHoles,
+ screen2 = listOf(Rect(2, 0, 5, 2)), // 3x2 space
+ )
+ val addedItems = testAddItems(workspaceHoles, getNewItem())
+ assertEquals(1, addedItems.size)
+ assertEquals(2, addedItems.first().itemInfo.screenId)
+ }
+
+ @Test
+ fun twoEmptyMiddleScreens_whenTaskRuns_thenAddItemToThirdScreen() {
+ val workspaceHoles = createWorkspaceHoles(
+ screen1 = emptyScreenHoles,
+ screen2 = emptyScreenHoles,
+ screen3 = listOf(Rect(1, 1, 4, 4)), // 3x3 space
+ )
+ val addedItems = testAddItems(workspaceHoles, getNewItem())
+ assertEquals(1, addedItems.size)
+ assertEquals(3, addedItems.first().itemInfo.screenId)
+ }
+
+ @Test
+ fun allPagesAreFull_whenTaskRuns_thenAddItemToNewScreen() {
+ val workspaceHoles = createWorkspaceHoles(
+ screen1 = fullScreenHoles,
+ screen2 = fullScreenHoles,
+ )
+ val addedItems = testAddItems(workspaceHoles, getNewItem())
+ assertEquals(1, addedItems.size)
+ assertEquals(3, addedItems.first().itemInfo.screenId)
+ }
+
+ @Test
+ fun firstTwoPagesAreFull_and_ThirdPageIsEmpty_whenTaskRuns_thenAddItemToThirdPage() {
+ val workspaceHoles = createWorkspaceHoles(
+ screen1 = fullScreenHoles,
+ screen2 = fullScreenHoles,
+ screen3 = emptyScreenHoles
+ )
+ val addedItems = testAddItems(workspaceHoles, getNewItem())
+ assertEquals(1, addedItems.size)
+ assertEquals(3, addedItems.first().itemInfo.screenId)
+ }
+
+ @Test
+ fun itemIsAlreadyAdded_whenTaskRun_thenIgnoreItem() {
+ val task = newTask(getExistingItem())
+ setupWorkspacesWithHoles(
+ screen1 = listOf(Rect(2, 2, 3, 3)), // 1x1 hole
+ )
+
+ // Nothing was added
+ assertTrue(mModelHelper.executeTaskForTest(task).isEmpty())
+ }
+
+ @Test
+ fun newAndExistingItems_whenTaskRun_thenAddOnlyTheNewOne() {
+ val newItem = getNewItem()
+ val workspaceHoles = createWorkspaceHoles(
+ screen1 = listOf(Rect(2, 2, 3, 3)), // 1x1 hole
+ )
+ val addedItems = testAddItems(workspaceHoles, getExistingItem(), newItem)
+ assertEquals(1, addedItems.size)
+ val addedItem = addedItems.first()
+ assert(addedItem.isAnimated)
+ val addedItemInfo = addedItem.itemInfo
+ assertEquals(1, addedItemInfo.screenId)
+ assertEquals(newItem, addedItemInfo)
+ }
+
+ private fun testAddItems(
+ workspaceHoles: List<List<Rect>>,
+ vararg itemsToAdd: WorkspaceItemInfo
+ ): List<AddedItem> {
+ setupWorkspaces(workspaceHoles)
+ mModelHelper.executeTaskForTest(newTask(*itemsToAdd))[0].run()
+
+ verify(dataModelCallbacks).bindAppsAdded(any(),
+ notAnimatedItemArgumentCaptor.capture(), animatedItemArgumentCaptor.capture())
+
+ val addedItems = mutableListOf<AddedItem>()
+ addedItems.addAll(animatedItemArgumentCaptor.value.map { AddedItem(it, true) })
+ addedItems.addAll(notAnimatedItemArgumentCaptor.value.map { AddedItem(it, false) })
+ return addedItems
+ }
+
+ private fun setupWorkspaces(workspaceHoles: List<List<Rect>>) {
+ var nextItemId = 1
+ var screenId = 1
+ workspaceHoles.forEach { holes ->
+ nextItemId = setupWorkspace(nextItemId, screenId++, *holes.toTypedArray())
+ }
+ }
+
+ private fun setupWorkspace(startId: Int, screenId: Int, vararg holes: Rect): Int {
+ return mModelHelper.executeSimpleTask { dataModel ->
+ writeWorkspaceWithHoles(dataModel, startId, screenId, *holes)
+ }
+ }
+
+ private fun writeWorkspaceWithHoles(
+ bgDataModel: BgDataModel,
+ itemStartId: Int,
+ screenId: Int,
+ vararg holes: Rect,
+ ): Int {
+ var itemId = itemStartId
+ val occupancy = GridOccupancy(mIdp.numColumns, mIdp.numRows)
+ occupancy.markCells(0, 0, mIdp.numColumns, mIdp.numRows, true)
+ holes.forEach { holeRect ->
+ occupancy.markCells(holeRect, false)
+ }
+ mExistingScreens.add(screenId)
+ mScreenOccupancy.append(screenId, occupancy)
+ for (x in 0 until mIdp.numColumns) {
+ for (y in 0 until mIdp.numRows) {
+ if (!occupancy.cells[x][y]) {
+ continue
+ }
+ val info = getExistingItem()
+ info.id = itemId++
+ info.screenId = screenId
+ info.cellX = x
+ info.cellY = y
+ info.container = LauncherSettings.Favorites.CONTAINER_DESKTOP
+ bgDataModel.addItem(mTargetContext, info, false)
+ val writer = ContentWriter(mTargetContext)
+ info.writeToValues(writer)
+ writer.put(LauncherSettings.Favorites._ID, info.id)
+ mTargetContext.contentResolver.insert(LauncherSettings.Favorites.CONTENT_URI,
+ writer.getValues(mTargetContext))
+ }
+ }
+ return itemId
+ }
+
+ private fun setupWorkspacesWithHoles(
+ screen1: List<Rect>? = null,
+ screen2: List<Rect>? = null,
+ screen3: List<Rect>? = null,
+ ) = createWorkspaceHoles(screen1, screen2, screen3)
+ .let(this::setupWorkspaces)
+
+ private fun createWorkspaceHoles(
+ screen1: List<Rect>? = null,
+ screen2: List<Rect>? = null,
+ screen3: List<Rect>? = null,
+ ): List<List<Rect>> = listOfNotNull(screen1, screen2, screen3)
+
+ private fun newTask(vararg items: ItemInfo): AddWorkspaceItemsTask =
+ items.map { Pair.create(it, Any()) }
+ .toMutableList()
+ .let(::AddWorkspaceItemsTask)
+
+ private fun getExistingItem() = WorkspaceItemInfo()
+ .apply { intent = Intent().setComponent(ComponentName("a", "b")) }
+
+ private fun getNewItem() = WorkspaceItemInfo()
+ .apply { intent = Intent().setComponent(ComponentName("b", "b")) }
+}
+
+private data class AddedItem(
+ val itemInfo: ItemInfo,
+ val isAnimated: Boolean
+)
\ No newline at end of file
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 44f2719..19dca45 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -38,6 +38,7 @@
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
+import android.system.OsConstants;
import android.util.Log;
import androidx.test.InstrumentationRegistry;
@@ -83,6 +84,7 @@
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
@@ -327,7 +329,12 @@
*/
protected <T> T getOnUiThread(final Callable<T> callback) {
try {
- return mMainThreadExecutor.submit(callback).get();
+ return mMainThreadExecutor.submit(callback).get(DEFAULT_UI_TIMEOUT,
+ TimeUnit.MILLISECONDS);
+ } catch (TimeoutException e) {
+ Log.e(TAG, "Timeout in getOnUiThread, sending SIGABRT", e);
+ Process.sendSignal(Process.myPid(), OsConstants.SIGABRT);
+ throw new RuntimeException(e);
} catch (Throwable e) {
throw new RuntimeException(e);
}
diff --git a/tests/src/com/android/launcher3/ui/WorkProfileTest.java b/tests/src/com/android/launcher3/ui/WorkProfileTest.java
index 45d20e2..939cfe1 100644
--- a/tests/src/com/android/launcher3/ui/WorkProfileTest.java
+++ b/tests/src/com/android/launcher3/ui/WorkProfileTest.java
@@ -32,7 +32,6 @@
import com.android.launcher3.allapps.WorkEduCard;
import com.android.launcher3.allapps.WorkProfileManager;
import com.android.launcher3.tapl.LauncherInstrumentation;
-import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord;
import org.junit.After;
import org.junit.Before;
@@ -96,7 +95,6 @@
}
@Test
- @ScreenRecord // b/202735477
public void workTabExists() {
waitForLauncherCondition("Personal tab is missing",
launcher -> launcher.getAppsView().isPersonalTabVisible(),
diff --git a/tests/src/com/android/launcher3/util/rule/TestStabilityRule.java b/tests/src/com/android/launcher3/util/rule/TestStabilityRule.java
index de36d5f..f33a50a 100644
--- a/tests/src/com/android/launcher3/util/rule/TestStabilityRule.java
+++ b/tests/src/com/android/launcher3/util/rule/TestStabilityRule.java
@@ -41,7 +41,7 @@
Pattern.compile("^("
+ "(?<local>(BuildFromAndroidStudio|"
+ "([0-9]+|[A-Z])-eng\\.[a-z]+\\.[0-9]+\\.[0-9]+))|"
- + "(?<platform>[A-Z]([a-z]|[0-9])*)"
+ + "(?<platform>([A-Z][a-z]*[0-9]*|[0-9]+)*)"
+ ")$");
private static final Pattern PLATFORM_BUILD =
Pattern.compile("^("
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 2fbe460..3485dd1 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -544,11 +544,11 @@
: TestHelpers.getSystemHealthMessage(getContext(), mTestStartTime);
if (systemHealth != null) {
- return message
- + ";\nPerhaps linked to system health problems:\n<<<<<<<<<<<<<<<<<<\n"
+ message += ";\nPerhaps linked to system health problems:\n<<<<<<<<<<<<<<<<<<\n"
+ systemHealth + "\n>>>>>>>>>>>>>>>>>>";
}
}
+ Log.d(TAG, "About to throw the error: " + message, new Exception());
return message;
}