Merge "Import translations. DO NOT MERGE ANYWHERE" into sc-v2-dev
diff --git a/quickstep/AndroidManifest.xml b/quickstep/AndroidManifest.xml
index b43d8d1..dc92731 100644
--- a/quickstep/AndroidManifest.xml
+++ b/quickstep/AndroidManifest.xml
@@ -22,11 +22,6 @@
xmlns:tools="http://schemas.android.com/tools"
package="com.android.launcher3">
- <permission
- android:name="${packageName}.permission.HOTSEAT_EDU"
- android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
- android:protectionLevel="signatureOrSystem" />
-
<uses-permission android:name="android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS" />
<uses-permission android:name="android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS"/>
<uses-permission android:name="android.permission.VIBRATE"/>
@@ -41,7 +36,6 @@
<uses-permission android:name="android.permission.MANAGE_ACCESSIBILITY"/>
<uses-permission android:name="android.permission.MONITOR_INPUT"/>
- <uses-permission android:name="${packageName}.permission.HOTSEAT_EDU" />
<uses-permission android:name="android.permission.SYSTEM_APPLICATION_OVERLAY" />
<application android:backupAgent="com.android.launcher3.LauncherBackupAgent"
@@ -133,20 +127,6 @@
</intent-filter>
</activity>
- <activity
- android:name=".hybridhotseat.HotseatEduActivity"
- android:theme="@android:style/Theme.NoDisplay"
- android:noHistory="true"
- android:launchMode="singleTask"
- android:clearTaskOnLaunch="true"
- android:permission="${packageName}.permission.HOTSEAT_EDU"
- android:exported="true">
- <intent-filter>
- <action android:name="com.android.launcher3.action.SHOW_HYBRID_HOTSEAT_EDU"/>
- <category android:name="android.intent.category.DEFAULT" />
- </intent-filter>
- </activity>
-
</application>
</manifest>
diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
index b4b29aa..2534699 100644
--- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
@@ -35,6 +35,8 @@
import android.content.Intent;
import android.content.IntentSender;
import android.content.ServiceConnection;
+import android.hardware.SensorManager;
+import android.hardware.devicestate.DeviceStateManager;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.Handler;
@@ -73,6 +75,8 @@
import com.android.quickstep.TaskUtils;
import com.android.quickstep.TouchInteractionService;
import com.android.quickstep.TouchInteractionService.TISBinder;
+import com.android.quickstep.util.LauncherUnfoldAnimationController;
+import com.android.quickstep.util.ProxyScreenStatusProvider;
import com.android.quickstep.util.RemoteAnimationProvider;
import com.android.quickstep.util.RemoteFadeOutAnimationListener;
import com.android.quickstep.util.SplitSelectStateController;
@@ -82,6 +86,9 @@
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.ActivityOptionsCompat;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+import com.android.systemui.unfold.UnfoldTransitionFactory;
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
+import com.android.systemui.unfold.config.UnfoldTransitionConfig;
import java.util.List;
import java.util.stream.Stream;
@@ -117,6 +124,7 @@
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
mTaskbarManager = ((TISBinder) iBinder).getTaskbarManager();
mTaskbarManager.setLauncher(BaseQuickstepLauncher.this);
+
Log.d(TAG, "TIS service connected");
resetServiceBindRetryState();
@@ -142,16 +150,41 @@
private @Nullable DragOptions mNextWorkspaceDragOptions = null;
private SplitPlaceholderView mSplitPlaceholderView;
+ private @Nullable UnfoldTransitionProgressProvider mUnfoldTransitionProgressProvider;
+ private @Nullable LauncherUnfoldAnimationController mLauncherUnfoldAnimationController;
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SysUINavigationMode.INSTANCE.get(this).addModeChangeListener(this);
addMultiWindowModeChangedListener(mDepthController);
+ initUnfoldTransitionProgressProvider();
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ if (mLauncherUnfoldAnimationController != null) {
+ mLauncherUnfoldAnimationController.onResume();
+ }
+ }
+
+ @Override
+ protected void onPause() {
+ if (mLauncherUnfoldAnimationController != null) {
+ mLauncherUnfoldAnimationController.onPause();
+ }
+
+ super.onPause();
}
@Override
public void onDestroy() {
mAppTransitionManager.onActivityDestroyed();
+ if (mUnfoldTransitionProgressProvider != null) {
+ mUnfoldTransitionProgressProvider.destroy();
+ }
SysUINavigationMode.INSTANCE.get(this).removeModeChangeListener(this);
@@ -160,6 +193,11 @@
mTaskbarManager.clearLauncher(this);
}
resetServiceBindRetryState();
+
+ if (mLauncherUnfoldAnimationController != null) {
+ mLauncherUnfoldAnimationController.onDestroy();
+ }
+
super.onDestroy();
}
@@ -297,7 +335,7 @@
mActionsView = findViewById(R.id.overview_actions_view);
RecentsView overviewPanel = (RecentsView) getOverviewPanel();
SplitSelectStateController controller =
- new SplitSelectStateController(mHandler, SystemUiProxy.INSTANCE.get(this));
+ new SplitSelectStateController(SystemUiProxy.INSTANCE.get(this));
overviewPanel.init(mActionsView, controller);
mActionsView.setDp(getDeviceProfile());
mActionsView.updateVerticalMargin(SysUINavigationMode.getMode(this));
@@ -334,6 +372,28 @@
mConnectionAttempts = 0;
}
+ private void initUnfoldTransitionProgressProvider() {
+ final UnfoldTransitionConfig config = UnfoldTransitionFactory.createConfig(this);
+ if (config.isEnabled()) {
+ mUnfoldTransitionProgressProvider =
+ UnfoldTransitionFactory.createUnfoldTransitionProgressProvider(
+ this,
+ config,
+ ProxyScreenStatusProvider.INSTANCE,
+ getSystemService(DeviceStateManager.class),
+ getSystemService(SensorManager.class),
+ getMainThreadHandler(),
+ getMainExecutor()
+ );
+
+ mLauncherUnfoldAnimationController = new LauncherUnfoldAnimationController(
+ this,
+ getWindowManager(),
+ mUnfoldTransitionProgressProvider
+ );
+ }
+ }
+
public void setTaskbarUIController(LauncherTaskbarUIController taskbarUIController) {
mTaskbarUIController = taskbarUIController;
}
@@ -373,6 +433,11 @@
return mTaskbarStateHandler;
}
+ @Nullable
+ public UnfoldTransitionProgressProvider getUnfoldTransitionProgressProvider() {
+ return mUnfoldTransitionProgressProvider;
+ }
+
@Override
public boolean supportsAdaptiveIconAnimation(View clickedView) {
return mAppTransitionManager.hasControlRemoteAppTransitionPermission()
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index 494b953..89cc1f6 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -676,7 +676,7 @@
? Math.max(crop.width(), crop.height()) / 2f
: 0f;
final float finalWindowRadius = mDeviceProfile.isMultiWindowMode
- ? 0 : getWindowCornerRadius(mLauncher.getResources());
+ ? 0 : getWindowCornerRadius(mLauncher);
final float finalShadowRadius = appTargetsAreTranslucent ? 0 : mMaxShadowRadius;
MultiValueUpdateListener listener = new MultiValueUpdateListener() {
@@ -867,7 +867,7 @@
}
final float finalWindowRadius = mDeviceProfile.isMultiWindowMode
- ? 0 : getWindowCornerRadius(mLauncher.getResources());
+ ? 0 : getWindowCornerRadius(mLauncher);
final FloatingWidgetView floatingView = FloatingWidgetView.getFloatingWidgetView(mLauncher,
v, widgetBackgroundBounds,
new Size(windowTargetBounds.width(), windowTargetBounds.height()),
@@ -1149,7 +1149,7 @@
ValueAnimator unlockAnimator = ValueAnimator.ofFloat(0, 1);
unlockAnimator.setDuration(CLOSING_TRANSITION_DURATION_MS);
float cornerRadius = mDeviceProfile.isMultiWindowMode ? 0 :
- QuickStepContract.getWindowCornerRadius(mLauncher.getResources());
+ QuickStepContract.getWindowCornerRadius(mLauncher);
unlockAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
@@ -1191,7 +1191,7 @@
ValueAnimator closingAnimator = ValueAnimator.ofFloat(0, 1);
int duration = CLOSING_TRANSITION_DURATION_MS;
float windowCornerRadius = mDeviceProfile.isMultiWindowMode
- ? 0 : getWindowCornerRadius(mLauncher.getResources());
+ ? 0 : getWindowCornerRadius(mLauncher);
float startShadowRadius = areAllTargetsTranslucent(appTargets) ? 0 : mMaxShadowRadius;
closingAnimator.setDuration(duration);
closingAnimator.addUpdateListener(new MultiValueUpdateListener() {
diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduActivity.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduActivity.java
deleted file mode 100644
index 3a7d821..0000000
--- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduActivity.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2020 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.hybridhotseat;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.os.Bundle;
-
-import com.android.launcher3.BaseActivity;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.uioverrides.QuickstepLauncher;
-import com.android.launcher3.util.ActivityTracker;
-
-/**
- * Proxy activity to return user to home screen and show halfsheet education
- */
-public class HotseatEduActivity extends Activity {
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- Intent homeIntent = new Intent(Intent.ACTION_MAIN)
- .addCategory(Intent.CATEGORY_HOME)
- .setPackage(getPackageName())
- .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-
- Launcher.ACTIVITY_TRACKER.registerCallback(new HotseatActivityTracker());
- startActivity(homeIntent);
- finish();
- }
-
- static class HotseatActivityTracker<T extends QuickstepLauncher> implements
- ActivityTracker.SchedulerCallback {
-
- @Override
- public boolean init(BaseActivity activity, boolean alreadyOnHome) {
- QuickstepLauncher launcher = (QuickstepLauncher) activity;
- if (launcher != null) {
- launcher.getHotseatPredictionController().showEdu();
- }
- return false;
- }
-
- }
-}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 8c12567..51d031b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -62,6 +62,7 @@
import com.android.launcher3.views.ActivityContext;
import com.android.quickstep.SysUINavigationMode;
import com.android.quickstep.SysUINavigationMode.Mode;
+import com.android.quickstep.util.ScopedUnfoldTransitionProgressProvider;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.WindowManagerWrapper;
@@ -98,7 +99,8 @@
private boolean mIsDestroyed = false;
public TaskbarActivityContext(Context windowContext, DeviceProfile dp,
- TaskbarNavButtonController buttonController) {
+ TaskbarNavButtonController buttonController, ScopedUnfoldTransitionProgressProvider
+ unfoldTransitionProgressProvider) {
super(windowContext, Themes.getActivityThemeRes(windowContext));
mDeviceProfile = dp;
@@ -120,6 +122,14 @@
FrameLayout navButtonsView = mDragLayer.findViewById(R.id.navbuttons_view);
StashedHandleView stashedHandleView = mDragLayer.findViewById(R.id.stashed_handle);
+ Display display = windowContext.getDisplay();
+ Context c = display.getDisplayId() == Display.DEFAULT_DISPLAY
+ ? windowContext.getApplicationContext()
+ : windowContext.getApplicationContext().createDisplayContext(display);
+ mWindowManager = c.getSystemService(WindowManager.class);
+ mLeftCorner = display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_LEFT);
+ mRightCorner = display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_RIGHT);
+
// Construct controllers.
mControllers = new TaskbarControllers(this,
new TaskbarDragController(this),
@@ -129,18 +139,12 @@
R.color.popup_color_primary_light),
new TaskbarDragLayerController(this, mDragLayer),
new TaskbarViewController(this, taskbarView),
+ new TaskbarUnfoldAnimationController(unfoldTransitionProgressProvider,
+ mWindowManager),
new TaskbarKeyguardController(this),
new StashedHandleViewController(this, stashedHandleView),
new TaskbarStashController(this),
new TaskbarEduController(this));
-
- Display display = windowContext.getDisplay();
- Context c = display.getDisplayId() == Display.DEFAULT_DISPLAY
- ? windowContext.getApplicationContext()
- : windowContext.getApplicationContext().createDisplayContext(display);
- mWindowManager = c.getSystemService(WindowManager.class);
- mLeftCorner = display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_LEFT);
- mRightCorner = display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_RIGHT);
}
public void init() {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
index 1197543..6144881 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
@@ -31,6 +31,7 @@
public final RotationButtonController rotationButtonController;
public final TaskbarDragLayerController taskbarDragLayerController;
public final TaskbarViewController taskbarViewController;
+ public final TaskbarUnfoldAnimationController taskbarUnfoldAnimationController;
public final TaskbarKeyguardController taskbarKeyguardController;
public final StashedHandleViewController stashedHandleViewController;
public final TaskbarStashController taskbarStashController;
@@ -46,6 +47,7 @@
RotationButtonController rotationButtonController,
TaskbarDragLayerController taskbarDragLayerController,
TaskbarViewController taskbarViewController,
+ TaskbarUnfoldAnimationController taskbarUnfoldAnimationController,
TaskbarKeyguardController taskbarKeyguardController,
StashedHandleViewController stashedHandleViewController,
TaskbarStashController taskbarStashController,
@@ -57,6 +59,7 @@
this.rotationButtonController = rotationButtonController;
this.taskbarDragLayerController = taskbarDragLayerController;
this.taskbarViewController = taskbarViewController;
+ this.taskbarUnfoldAnimationController = taskbarUnfoldAnimationController;
this.taskbarKeyguardController = taskbarKeyguardController;
this.stashedHandleViewController = stashedHandleViewController;
this.taskbarStashController = taskbarStashController;
@@ -75,6 +78,7 @@
}
taskbarDragLayerController.init(this);
taskbarViewController.init(this);
+ taskbarUnfoldAnimationController.init(this);
taskbarKeyguardController.init(navbarButtonsViewController);
stashedHandleViewController.init(this);
taskbarStashController.init(this);
@@ -89,6 +93,7 @@
rotationButtonController.onDestroy();
taskbarDragLayerController.onDestroy();
taskbarKeyguardController.onDestroy();
+ taskbarUnfoldAnimationController.onDestroy();
taskbarViewController.onDestroy();
stashedHandleViewController.onDestroy();
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index 4ed83f2..ec98bbf 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -42,6 +42,7 @@
import com.android.quickstep.SysUINavigationMode.Mode;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.TouchInteractionService;
+import com.android.quickstep.util.ScopedUnfoldTransitionProgressProvider;
/**
* Class to manage taskbar lifecycle
@@ -58,6 +59,10 @@
private final TaskbarNavButtonController mNavButtonController;
private final SettingsCache.OnChangeListener mUserSetupCompleteListener;
+ // The source for this provider is set when Launcher is available
+ private final ScopedUnfoldTransitionProgressProvider mUnfoldProgressProvider =
+ new ScopedUnfoldTransitionProgressProvider();
+
private TaskbarActivityContext mTaskbarActivityContext;
private BaseQuickstepLauncher mLauncher;
/**
@@ -120,6 +125,9 @@
*/
public void setLauncher(@NonNull BaseQuickstepLauncher launcher) {
mLauncher = launcher;
+ mUnfoldProgressProvider.setSourceProvider(launcher
+ .getUnfoldTransitionProgressProvider());
+
if (mTaskbarActivityContext != null) {
mTaskbarActivityContext.setUIController(
new LauncherTaskbarUIController(launcher, mTaskbarActivityContext));
@@ -135,6 +143,7 @@
if (mTaskbarActivityContext != null) {
mTaskbarActivityContext.setUIController(TaskbarUIController.DEFAULT);
}
+ mUnfoldProgressProvider.setSourceProvider(null);
}
}
@@ -153,8 +162,8 @@
return;
}
- mTaskbarActivityContext = new TaskbarActivityContext(
- mContext, dp.copy(mContext), mNavButtonController);
+ mTaskbarActivityContext = new TaskbarActivityContext(mContext, dp.copy(mContext),
+ mNavButtonController, mUnfoldProgressProvider);
mTaskbarActivityContext.init();
if (mLauncher != null) {
mTaskbarActivityContext.setUIController(
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUnfoldAnimationController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUnfoldAnimationController.java
new file mode 100644
index 0000000..43f015c
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUnfoldAnimationController.java
@@ -0,0 +1,87 @@
+/*
+ * 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.taskbar;
+
+import android.view.View;
+import android.view.WindowManager;
+
+import com.android.quickstep.util.LauncherViewsMoveFromCenterTranslationApplier;
+import com.android.quickstep.util.ScopedUnfoldTransitionProgressProvider;
+import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator;
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener;
+
+/**
+ * Controls animation of taskbar icons when unfolding foldable devices
+ */
+public class TaskbarUnfoldAnimationController {
+
+ private final ScopedUnfoldTransitionProgressProvider mUnfoldTransitionProgressProvider;
+ private final UnfoldMoveFromCenterAnimator mMoveFromCenterAnimator;
+ private final TransitionListener mTransitionListener = new TransitionListener();
+ private TaskbarViewController mTaskbarViewController;
+
+ public TaskbarUnfoldAnimationController(ScopedUnfoldTransitionProgressProvider
+ unfoldTransitionProgressProvider, WindowManager windowManager) {
+ mUnfoldTransitionProgressProvider = unfoldTransitionProgressProvider;
+ mMoveFromCenterAnimator = new UnfoldMoveFromCenterAnimator(windowManager,
+ new LauncherViewsMoveFromCenterTranslationApplier());
+ }
+
+ /**
+ * Initializes the controller
+ * @param taskbarControllers references to all other taskbar controllers
+ */
+ public void init(TaskbarControllers taskbarControllers) {
+ mTaskbarViewController = taskbarControllers.taskbarViewController;
+ mTaskbarViewController.addOneTimePreDrawListener(() ->
+ mUnfoldTransitionProgressProvider.setReadyToHandleTransition(true));
+ mUnfoldTransitionProgressProvider.addCallback(mTransitionListener);
+ }
+
+ /**
+ * Destroys the controller
+ */
+ public void onDestroy() {
+ mUnfoldTransitionProgressProvider.setReadyToHandleTransition(false);
+ mUnfoldTransitionProgressProvider.removeCallback(mTransitionListener);
+ }
+
+ private class TransitionListener implements TransitionProgressListener {
+
+ @Override
+ public void onTransitionStarted() {
+ mMoveFromCenterAnimator.updateDisplayProperties();
+ View[] icons = mTaskbarViewController.getIconViews();
+ for (View icon : icons) {
+ // TODO(b/193794563) we should re-register views if they are re-bound/re-inflated
+ // during the animation
+ mMoveFromCenterAnimator.registerViewForAnimation(icon);
+ }
+
+ mMoveFromCenterAnimator.onTransitionStarted();
+ }
+
+ @Override
+ public void onTransitionFinished() {
+ mMoveFromCenterAnimator.onTransitionFinished();
+ }
+
+ @Override
+ public void onTransitionProgress(float progress) {
+ mMoveFromCenterAnimator.onTransitionProgress(progress);
+ }
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index f1748af..f359a3d 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -16,20 +16,24 @@
package com.android.launcher3.taskbar;
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
-import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
import static com.android.launcher3.Utilities.squaredHypot;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.quickstep.AnimatedFloat.VALUE;
import android.graphics.Rect;
+import android.util.FloatProperty;
import android.view.MotionEvent;
import android.view.View;
+import android.view.ViewTreeObserver;
+import android.view.ViewTreeObserver.OnPreDrawListener;
+import com.android.launcher3.BubbleTextView;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.util.MultiValueAlpha;
import com.android.quickstep.AnimatedFloat;
@@ -117,6 +121,25 @@
mTaskbarView.setClickAndLongClickListenersForIcon(icon);
}
+ /**
+ * Adds one time pre draw listener to the taskbar view, it is called before
+ * drawing a frame and invoked only once
+ * @param listener callback that will be invoked before drawing the next frame
+ */
+ public void addOneTimePreDrawListener(Runnable listener) {
+ mTaskbarView.getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
+ @Override
+ public boolean onPreDraw() {
+ final ViewTreeObserver viewTreeObserver = mTaskbarView.getViewTreeObserver();
+ if (viewTreeObserver.isAlive()) {
+ listener.run();
+ viewTreeObserver.removeOnPreDrawListener(this);
+ }
+ return true;
+ }
+ });
+ }
+
public Rect getIconLayoutBounds() {
return mTaskbarView.getIconLayoutBounds();
}
@@ -194,7 +217,7 @@
float childCenter = (child.getLeft() + child.getRight()) / 2;
float hotseatIconCenter = hotseatPadding.left + hotseatCellSize * info.screenId
+ hotseatCellSize / 2;
- setter.setFloat(child, VIEW_TRANSLATE_X, hotseatIconCenter - childCenter, LINEAR);
+ setter.setFloat(child, ICON_TRANSLATE_X, hotseatIconCenter - childCenter, LINEAR);
}
AnimatorPlaybackController controller = setter.createPlaybackController();
@@ -257,4 +280,30 @@
return false;
}
}
+
+ public static final FloatProperty<View> ICON_TRANSLATE_X =
+ new FloatProperty<View>("taskbarAligmentTranslateX") {
+
+ @Override
+ public void setValue(View view, float v) {
+ if (view instanceof BubbleTextView) {
+ ((BubbleTextView) view).setTranslationXForTaskbarAlignmentAnimation(v);
+ } else if (view instanceof FolderIcon) {
+ ((FolderIcon) view).setTranslationForTaskbarAlignmentAnimation(v);
+ } else {
+ view.setTranslationX(v);
+ }
+ }
+
+ @Override
+ public Float get(View view) {
+ if (view instanceof BubbleTextView) {
+ return ((BubbleTextView) view)
+ .getTranslationXForTaskbarAlignmentAnimation();
+ } else if (view instanceof FolderIcon) {
+ return ((FolderIcon) view).getTranslationXForTaskbarAlignmentAnimation();
+ }
+ return view.getTranslationX();
+ }
+ };
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 14bc380..2009cd7 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -36,13 +36,9 @@
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Configuration;
-import android.hardware.SensorManager;
-import android.hardware.devicestate.DeviceStateManager;
import android.view.HapticFeedbackConstants;
import android.view.View;
-import androidx.annotation.Nullable;
-
import com.android.launcher3.BaseQuickstepLauncher;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
@@ -79,14 +75,9 @@
import com.android.quickstep.SysUINavigationMode.Mode;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.TaskUtils;
-import com.android.quickstep.util.LauncherUnfoldAnimationController;
-import com.android.quickstep.util.ProxyScreenStatusProvider;
import com.android.quickstep.util.QuickstepOnboardingPrefs;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
-import com.android.systemui.unfold.UnfoldTransitionFactory;
-import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
-import com.android.systemui.unfold.config.UnfoldTransitionConfig;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -106,51 +97,10 @@
private FixedContainerItems mAllAppsPredictions;
private HotseatPredictionController mHotseatPredictionController;
- @Nullable
- private LauncherUnfoldAnimationController mLauncherUnfoldAnimationController;
-
@Override
protected void setupViews() {
super.setupViews();
mHotseatPredictionController = new HotseatPredictionController(this);
-
- final UnfoldTransitionConfig config = UnfoldTransitionFactory.createConfig(this);
- if (config.isEnabled()) {
- final UnfoldTransitionProgressProvider unfoldTransitionProgressProvider =
- UnfoldTransitionFactory.createUnfoldTransitionProgressProvider(
- this,
- config,
- ProxyScreenStatusProvider.INSTANCE,
- getSystemService(DeviceStateManager.class),
- getSystemService(SensorManager.class),
- getMainThreadHandler(),
- getMainExecutor()
- );
-
- mLauncherUnfoldAnimationController = new LauncherUnfoldAnimationController(
- this,
- getWindowManager(),
- unfoldTransitionProgressProvider
- );
- }
- }
-
- @Override
- protected void onResume() {
- super.onResume();
-
- if (mLauncherUnfoldAnimationController != null) {
- mLauncherUnfoldAnimationController.onResume();
- }
- }
-
- @Override
- protected void onPause() {
- if (mLauncherUnfoldAnimationController != null) {
- mLauncherUnfoldAnimationController.onPause();
- }
-
- super.onPause();
}
@Override
@@ -281,10 +231,6 @@
public void onDestroy() {
super.onDestroy();
mHotseatPredictionController.destroy();
-
- if (mLauncherUnfoldAnimationController != null) {
- mLauncherUnfoldAnimationController.onDestroy();
- }
}
@Override
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 4c46683..be0c980 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -531,7 +531,7 @@
ActivityManager.RunningTaskInfo[] runningTasks;
if (mIsSwipeForStagedSplit) {
int[] splitTaskIds =
- LauncherSplitScreenListener.INSTANCE.getNoCreate().getSplitTaskIds();
+ LauncherSplitScreenListener.INSTANCE.getNoCreate().getRunningSplitTaskIds();
runningTasks = new ActivityManager.RunningTaskInfo[splitTaskIds.length];
for (int i = 0; i < splitTaskIds.length; i++) {
int taskId = splitTaskIds[i];
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index 03e2395..cc6cfd7 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -122,7 +122,7 @@
SYSUI_PROGRESS.set(getRootView().getSysUiScrim(), 0f);
SplitSelectStateController controller =
- new SplitSelectStateController(mHandler, SystemUiProxy.INSTANCE.get(this));
+ new SplitSelectStateController(SystemUiProxy.INSTANCE.get(this));
mDragLayer.recreateControllers();
mFallbackRecentsView.init(mActionsView, controller);
}
diff --git a/quickstep/src/com/android/quickstep/RotationTouchHelper.java b/quickstep/src/com/android/quickstep/RotationTouchHelper.java
index 2a422cc..35efddf 100644
--- a/quickstep/src/com/android/quickstep/RotationTouchHelper.java
+++ b/quickstep/src/com/android/quickstep/RotationTouchHelper.java
@@ -148,7 +148,7 @@
mDisplayId = DEFAULT_DISPLAY;
mOrientationTouchTransformer = new OrientationTouchTransformer(resources, mMode,
- () -> QuickStepContract.getWindowCornerRadius(resources));
+ () -> QuickStepContract.getWindowCornerRadius(mContext));
// Register for navigation mode changes
SysUINavigationMode.Mode newMode = mSysUiNavMode.addModeChangeListener(this);
diff --git a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
index 1f57e99..e14dbb1 100644
--- a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
+++ b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
@@ -90,7 +90,8 @@
mGestureState = gestureState;
mIsSwipeForStagedSplit = ENABLE_SPLIT_SELECT.get() &&
- LauncherSplitScreenListener.INSTANCE.getNoCreate().getSplitTaskIds().length > 1;
+ LauncherSplitScreenListener.INSTANCE.getNoCreate()
+ .getRunningSplitTaskIds().length > 1;
TaskViewSimulator primaryTVS = new TaskViewSimulator(context,
gestureState.getActivityInterface());
diff --git a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
index eb5c43f..978fb57 100644
--- a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
@@ -107,6 +107,13 @@
public static void addSplitOptions(List<SystemShortcut> outShortcuts,
BaseDraggingActivity activity, TaskView taskView, DeviceProfile deviceProfile) {
+ int[] taskViewTaskIds = taskView.getTaskIds();
+ boolean alreadyHasMultipleTasks = taskViewTaskIds[0] != -1 &&
+ taskViewTaskIds[1] != -1;
+ if (alreadyHasMultipleTasks) {
+ return;
+ }
+
PagedOrientationHandler orientationHandler =
taskView.getRecentsView().getPagedOrientationHandler();
List<SplitPositionOption> positions =
diff --git a/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java b/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java
index a9a9e2a..fc1dd6f 100644
--- a/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java
+++ b/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java
@@ -120,7 +120,7 @@
mAssistantGestureDetector = new GestureDetector(context, new AssistantGestureListener());
int assistantWidth = resources.getDimensionPixelSize(R.dimen.gestures_assistant_width);
final float assistantHeight = Math.max(mBottomGestureHeight,
- QuickStepContract.getWindowCornerRadius(resources));
+ QuickStepContract.getWindowCornerRadius(context));
mAssistantLeftRegion.bottom = mAssistantRightRegion.bottom = mDisplaySize.y;
mAssistantLeftRegion.top = mAssistantRightRegion.top = mDisplaySize.y - assistantHeight;
mAssistantLeftRegion.left = 0;
diff --git a/quickstep/src/com/android/quickstep/util/LauncherSplitScreenListener.java b/quickstep/src/com/android/quickstep/util/LauncherSplitScreenListener.java
index da665d4..0f4ed01 100644
--- a/quickstep/src/com/android/quickstep/util/LauncherSplitScreenListener.java
+++ b/quickstep/src/com/android/quickstep/util/LauncherSplitScreenListener.java
@@ -11,20 +11,54 @@
import com.android.launcher3.util.SplitConfigurationOptions.StageType;
import com.android.launcher3.util.SplitConfigurationOptions.StagedSplitTaskPosition;
import com.android.quickstep.SystemUiProxy;
+import com.android.systemui.shared.system.TaskStackChangeListener;
+import com.android.systemui.shared.system.TaskStackChangeListeners;
import com.android.wm.shell.splitscreen.ISplitScreenListener;
/**
* Listeners for system wide split screen position and stage changes.
- * Use {@link #getSplitTaskIds()} to determine which tasks, if any, are in staged split.
+ *
+ * Use {@link #getRunningSplitTaskIds()} to determine which tasks, if any, are actively in
+ * staged split.
+ *
+ * Use {@link #getPersistentSplitIds()} to know if tasks were in split screen before a quickswitch
+ * gesture happened.
*/
public class LauncherSplitScreenListener extends ISplitScreenListener.Stub {
public static final MainThreadInitializedObject<LauncherSplitScreenListener> INSTANCE =
new MainThreadInitializedObject<>(LauncherSplitScreenListener::new);
+ private static final int[] EMPTY_ARRAY = {};
+
private final StagedSplitTaskPosition mMainStagePosition = new StagedSplitTaskPosition();
private final StagedSplitTaskPosition mSideStagePosition = new StagedSplitTaskPosition();
+ private boolean mIsRecentsListFrozen = false;
+ private final TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
+ @Override
+ public void onRecentTaskListFrozenChanged(boolean frozen) {
+ super.onRecentTaskListFrozenChanged(frozen);
+ mIsRecentsListFrozen = frozen;
+
+ if (frozen) {
+ mPersistentGroupedIds = getRunningSplitTaskIds();
+ } else {
+ // TODO(b/198310766) Need to also explicitly exit split screen if
+ // we're not currently viewing split screened apps
+ mPersistentGroupedIds = EMPTY_ARRAY;
+ }
+ }
+ };
+
+ /**
+ * Gets set to current split taskIDs whenever the task list is frozen, and set to empty array
+ * whenever task list unfreezes.
+ * When not null, this indicates that we need to load a GroupedTaskView as the most recent
+ * page, so user can quickswitch back to a grouped task.
+ */
+ private int[] mPersistentGroupedIds;
+
public LauncherSplitScreenListener(Context context) {
mMainStagePosition.stageType = SplitConfigurationOptions.STAGE_TYPE_MAIN;
mSideStagePosition.stageType = SplitConfigurationOptions.STAGE_TYPE_SIDE;
@@ -33,17 +67,30 @@
/** Also call {@link #destroy()} when done. */
public void init() {
SystemUiProxy.INSTANCE.getNoCreate().registerSplitScreenListener(this);
+ TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener);
}
public void destroy() {
SystemUiProxy.INSTANCE.getNoCreate().unregisterSplitScreenListener(this);
+ TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener);
}
/**
+ * This method returns the active split taskIDs that were active if a user quickswitched from
+ * split screen to a fullscreen app as long as the recents task list remains frozen.
+ */
+ public int[] getPersistentSplitIds() {
+ if (mIsRecentsListFrozen) {
+ return mPersistentGroupedIds;
+ } else {
+ return getRunningSplitTaskIds();
+ }
+ }
+ /**
* @return index 0 will be task in left/top position, index 1 in right/bottom position.
* Will return empty array if device is not in staged split
*/
- public int[] getSplitTaskIds() {
+ public int[] getRunningSplitTaskIds() {
if (mMainStagePosition.taskId == -1 || mSideStagePosition.taskId == -1) {
return new int[]{};
}
diff --git a/quickstep/src/com/android/quickstep/util/LauncherUnfoldAnimationController.java b/quickstep/src/com/android/quickstep/util/LauncherUnfoldAnimationController.java
index c5ab84d..47d3580 100644
--- a/quickstep/src/com/android/quickstep/util/LauncherUnfoldAnimationController.java
+++ b/quickstep/src/com/android/quickstep/util/LauncherUnfoldAnimationController.java
@@ -37,26 +37,23 @@
private static final float MAX_WIDTH_INSET_FRACTION = 0.15f;
private final Launcher mLauncher;
- private final UnfoldTransitionProgressProvider mUnfoldTransitionProgressProvider;
- private final UnfoldMoveFromCenterWorkspaceAnimator mMoveFromCenterWorkspaceAnimation;
@Nullable
private HorizontalInsettableView mQsbInsettable;
- private final AnimationListener mAnimationListener = new AnimationListener();
-
- private boolean mIsTransitionRunning = false;
- private boolean mIsReadyToPlayAnimation = false;
+ private final ScopedUnfoldTransitionProgressProvider mProgressProvider;
public LauncherUnfoldAnimationController(
Launcher launcher,
WindowManager windowManager,
UnfoldTransitionProgressProvider unfoldTransitionProgressProvider) {
mLauncher = launcher;
- mUnfoldTransitionProgressProvider = unfoldTransitionProgressProvider;
- mMoveFromCenterWorkspaceAnimation = new UnfoldMoveFromCenterWorkspaceAnimator(launcher,
- windowManager);
- mUnfoldTransitionProgressProvider.addCallback(mAnimationListener);
+ mProgressProvider = new ScopedUnfoldTransitionProgressProvider(
+ unfoldTransitionProgressProvider);
+
+ mProgressProvider.addCallback(new UnfoldMoveFromCenterWorkspaceAnimator(launcher,
+ windowManager));
+ mProgressProvider.addCallback(new QsbAnimationListener());
}
/**
@@ -73,7 +70,7 @@
@Override
public boolean onPreDraw() {
if (obs.isAlive()) {
- onPreDrawAfterResume();
+ mProgressProvider.setReadyToHandleTransition(true);
obs.removeOnPreDrawListener(this);
}
return true;
@@ -85,12 +82,7 @@
* Called when launcher activity is paused
*/
public void onPause() {
- if (mIsTransitionRunning) {
- mIsTransitionRunning = false;
- mAnimationListener.onTransitionFinished();
- }
-
- mIsReadyToPlayAnimation = false;
+ mProgressProvider.setReadyToHandleTransition(false);
mQsbInsettable = null;
}
@@ -98,48 +90,24 @@
* Called when launcher activity is destroyed
*/
public void onDestroy() {
- mUnfoldTransitionProgressProvider.removeCallback(mAnimationListener);
+ mProgressProvider.destroy();
}
- /**
- * Called after performing layouting of the views after configuration change
- */
- private void onPreDrawAfterResume() {
- mIsReadyToPlayAnimation = true;
-
- if (mIsTransitionRunning) {
- mMoveFromCenterWorkspaceAnimation.onTransitionStarted();
- }
- }
-
- private class AnimationListener implements TransitionProgressListener {
+ private class QsbAnimationListener implements TransitionProgressListener {
@Override
public void onTransitionStarted() {
- mIsTransitionRunning = true;
-
- if (mIsReadyToPlayAnimation) {
- mMoveFromCenterWorkspaceAnimation.onTransitionStarted();
- }
}
@Override
public void onTransitionFinished() {
- if (mIsReadyToPlayAnimation) {
- mMoveFromCenterWorkspaceAnimation.onTransitionFinished();
-
- if (mQsbInsettable != null) {
- mQsbInsettable.setHorizontalInsets(0);
- }
+ if (mQsbInsettable != null) {
+ mQsbInsettable.setHorizontalInsets(0);
}
-
- mIsTransitionRunning = false;
}
@Override
public void onTransitionProgress(float progress) {
- mMoveFromCenterWorkspaceAnimation.onTransitionProgress(progress);
-
if (mQsbInsettable != null) {
float insetPercentage = comp(progress) * MAX_WIDTH_INSET_FRACTION;
mQsbInsettable.setHorizontalInsets(insetPercentage);
diff --git a/quickstep/src/com/android/quickstep/util/LauncherViewsMoveFromCenterTranslationApplier.java b/quickstep/src/com/android/quickstep/util/LauncherViewsMoveFromCenterTranslationApplier.java
new file mode 100644
index 0000000..effdfdd
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/LauncherViewsMoveFromCenterTranslationApplier.java
@@ -0,0 +1,45 @@
+/*
+ * 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.quickstep.util;
+
+import android.annotation.NonNull;
+import android.view.View;
+
+import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.widget.NavigableAppWidgetHostView;
+import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator.TranslationApplier;
+
+/**
+ * Class that allows to set translations for move from center animation independently
+ * from other translations for certain launcher views
+ */
+public class LauncherViewsMoveFromCenterTranslationApplier implements TranslationApplier {
+
+ @Override
+ public void apply(@NonNull View view, float x, float y) {
+ if (view instanceof NavigableAppWidgetHostView) {
+ ((NavigableAppWidgetHostView) view).setTranslationForMoveFromCenterAnimation(x, y);
+ } else if (view instanceof BubbleTextView) {
+ ((BubbleTextView) view).setTranslationForMoveFromCenterAnimation(x, y);
+ } else if (view instanceof FolderIcon) {
+ ((FolderIcon) view).setTranslationForMoveFromCenterAnimation(x, y);
+ } else {
+ view.setTranslationX(x);
+ view.setTranslationY(y);
+ }
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/util/ScopedUnfoldTransitionProgressProvider.java b/quickstep/src/com/android/quickstep/util/ScopedUnfoldTransitionProgressProvider.java
new file mode 100644
index 0000000..2ef311f
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/ScopedUnfoldTransitionProgressProvider.java
@@ -0,0 +1,140 @@
+/*
+ * 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.quickstep.util;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Manages progress listeners that can have smaller lifespan than the unfold animation.
+ * Allows to limit getting transition updates to only when
+ * {@link ScopedUnfoldTransitionProgressProvider#setReadyToHandleTransition} is called
+ * with readyToHandleTransition = true
+ *
+ * If the transition has already started by the moment when the clients are ready to play
+ * the transition then it will report transition started callback and current animation progress.
+ */
+public final class ScopedUnfoldTransitionProgressProvider implements
+ UnfoldTransitionProgressProvider, TransitionProgressListener {
+
+ private static final float PROGRESS_UNSET = -1f;
+
+ @Nullable
+ private UnfoldTransitionProgressProvider mSource;
+
+ private final List<TransitionProgressListener> mListeners = new ArrayList<>();
+
+ private boolean mIsReadyToHandleTransition;
+ private boolean mIsTransitionRunning;
+ private float mLastTransitionProgress = PROGRESS_UNSET;
+
+ public ScopedUnfoldTransitionProgressProvider() {
+ this(null);
+ }
+
+ public ScopedUnfoldTransitionProgressProvider(@Nullable UnfoldTransitionProgressProvider
+ source) {
+ setSourceProvider(source);
+ }
+
+ /**
+ * Sets the source for the unfold transition progress updates,
+ * it replaces current provider if it is already set
+ * @param provider transition provider that emits transition progress updates
+ */
+ public void setSourceProvider(@Nullable UnfoldTransitionProgressProvider provider) {
+ if (mSource != null) {
+ mSource.removeCallback(this);
+ }
+
+ if (provider != null) {
+ mSource = provider;
+ mSource.addCallback(this);
+ }
+ }
+
+ /**
+ * Allows to notify this provide whether the listeners can play the transition or not.
+ * Call this method with readyToHandleTransition = true when all listeners
+ * are ready to consume the transition progress events.
+ * Call it with readyToHandleTransition = false when listeners can't process the events.
+ */
+ public void setReadyToHandleTransition(boolean isReadyToHandleTransition) {
+ if (mIsTransitionRunning) {
+ if (mIsReadyToHandleTransition) {
+ mListeners.forEach(TransitionProgressListener::onTransitionStarted);
+
+ if (mLastTransitionProgress != PROGRESS_UNSET) {
+ mListeners.forEach(listener ->
+ listener.onTransitionProgress(mLastTransitionProgress));
+ }
+ } else {
+ mIsTransitionRunning = false;
+ mListeners.forEach(TransitionProgressListener::onTransitionFinished);
+ }
+ }
+
+ mIsReadyToHandleTransition = isReadyToHandleTransition;
+ }
+
+ @Override
+ public void addCallback(@NonNull TransitionProgressListener listener) {
+ mListeners.add(listener);
+ }
+
+ @Override
+ public void removeCallback(@NonNull TransitionProgressListener listener) {
+ mListeners.remove(listener);
+ }
+
+ @Override
+ public void destroy() {
+ mSource.removeCallback(this);
+ }
+
+ @Override
+ public void onTransitionStarted() {
+ this.mIsTransitionRunning = true;
+ if (mIsReadyToHandleTransition) {
+ mListeners.forEach(TransitionProgressListener::onTransitionStarted);
+ }
+ }
+
+ @Override
+ public void onTransitionProgress(float progress) {
+ if (mIsReadyToHandleTransition) {
+ mListeners.forEach(listener -> listener.onTransitionProgress(progress));
+ }
+
+ mLastTransitionProgress = progress;
+ }
+
+ @Override
+ public void onTransitionFinished() {
+ if (mIsReadyToHandleTransition) {
+ mListeners.forEach(TransitionProgressListener::onTransitionFinished);
+ }
+
+ mIsTransitionRunning = false;
+ mLastTransitionProgress = PROGRESS_UNSET;
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
index ac5f5d8..3069504 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
@@ -22,7 +22,6 @@
import android.app.ActivityThread;
import android.graphics.Rect;
-import android.os.Handler;
import android.os.IBinder;
import android.view.RemoteAnimationAdapter;
import android.view.SurfaceControl;
@@ -40,6 +39,8 @@
import com.android.systemui.shared.system.RemoteTransitionCompat;
import com.android.systemui.shared.system.RemoteTransitionRunner;
+import java.util.function.Consumer;
+
/**
* Represent data needed for the transient state when user has selected one app for split screen
* and is in the process of either a) selecting a second app or b) exiting intention to invoke split
@@ -52,7 +53,7 @@
private Task mSecondTask;
private Rect mInitialBounds;
- public SplitSelectStateController(Handler handler, SystemUiProxy systemUiProxy) {
+ public SplitSelectStateController(SystemUiProxy systemUiProxy) {
mSystemUiProxy = systemUiProxy;
}
@@ -71,13 +72,14 @@
*/
public void setSecondTaskId(Task taskView) {
mSecondTask = taskView;
- launchTasks(mInitialTask, mSecondTask, mStagePosition);
+ launchTasks(mInitialTask, mSecondTask, mStagePosition, null /*callback*/);
}
/**
* @param stagePosition representing location of task1
*/
- public void launchTasks(Task task1, Task task2, @StagePosition int stagePosition) {
+ public void launchTasks(Task task1, Task task2, @StagePosition int stagePosition,
+ Consumer<Boolean> callback) {
// Assume initial task is for top/left part of screen
final int[] taskIds = stagePosition == STAGE_POSITION_TOP_OR_LEFT
? new int[]{task1.key.id, task2.key.id}
@@ -90,7 +92,7 @@
new RemoteTransitionCompat(animationRunner, MAIN_EXECUTOR));
} else {
RemoteSplitLaunchAnimationRunner animationRunner =
- new RemoteSplitLaunchAnimationRunner(task1, task2);
+ new RemoteSplitLaunchAnimationRunner(task1, task2, callback);
final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter(
RemoteAnimationAdapterCompat.wrapRemoteAnimationRunner(animationRunner),
300, 150,
@@ -136,10 +138,13 @@
private final Task mInitialTask;
private final Task mSecondTask;
+ private final Consumer<Boolean> mSuccessCallback;
- RemoteSplitLaunchAnimationRunner(Task initialTask, Task secondTask) {
+ RemoteSplitLaunchAnimationRunner(Task initialTask, Task secondTask,
+ Consumer<Boolean> successCallback) {
mInitialTask = initialTask;
mSecondTask = secondTask;
+ mSuccessCallback = successCallback;
}
@Override
@@ -147,13 +152,21 @@
RemoteAnimationTargetCompat[] wallpapers, RemoteAnimationTargetCompat[] nonApps,
Runnable finishedCallback) {
TaskViewUtils.composeRecentsSplitLaunchAnimatorLegacy(mInitialTask,
- mSecondTask, apps, wallpapers, nonApps, finishedCallback);
+ mSecondTask, apps, wallpapers, nonApps, () -> {
+ finishedCallback.run();
+ if (mSuccessCallback != null) {
+ mSuccessCallback.accept(true);
+ }
+ });
// After successful launch, call resetState
resetState();
}
@Override
public void onAnimationCancelled() {
+ if (mSuccessCallback != null) {
+ mSuccessCallback.accept(false);
+ }
resetState();
}
}
diff --git a/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterWorkspaceAnimator.java b/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterWorkspaceAnimator.java
index 482092d..95403b2 100644
--- a/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterWorkspaceAnimator.java
+++ b/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterWorkspaceAnimator.java
@@ -15,20 +15,16 @@
*/
package com.android.quickstep.util;
-import android.annotation.NonNull;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
-import com.android.launcher3.BubbleTextView;
import com.android.launcher3.CellLayout;
import com.android.launcher3.Hotseat;
import com.android.launcher3.Launcher;
import com.android.launcher3.ShortcutAndWidgetContainer;
import com.android.launcher3.Workspace;
-import com.android.launcher3.widget.NavigableAppWidgetHostView;
import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator;
-import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator.TranslationApplier;
import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
import java.util.HashMap;
@@ -49,7 +45,7 @@
public UnfoldMoveFromCenterWorkspaceAnimator(Launcher launcher, WindowManager windowManager) {
mLauncher = launcher;
mMoveFromCenterAnimation = new UnfoldMoveFromCenterAnimator(windowManager,
- new WorkspaceViewsTranslationApplier());
+ new LauncherViewsMoveFromCenterTranslationApplier());
}
@Override
@@ -122,19 +118,4 @@
view.setClipChildren(originalClipChildren);
}
}
-
- private static class WorkspaceViewsTranslationApplier implements TranslationApplier {
-
- @Override
- public void apply(@NonNull View view, float x, float y) {
- if (view instanceof NavigableAppWidgetHostView) {
- ((NavigableAppWidgetHostView) view).setTranslationForMoveFromCenterAnimation(x, y);
- } else if (view instanceof BubbleTextView) {
- ((BubbleTextView) view).setTranslationForMoveFromCenterAnimation(x, y);
- } else {
- view.setTranslationX(x);
- view.setTranslationY(y);
- }
- }
- }
}
diff --git a/quickstep/src/com/android/quickstep/views/ClearAllButton.java b/quickstep/src/com/android/quickstep/views/ClearAllButton.java
index b9a9006..22c87b0 100644
--- a/quickstep/src/com/android/quickstep/views/ClearAllButton.java
+++ b/quickstep/src/com/android/quickstep/views/ClearAllButton.java
@@ -39,10 +39,24 @@
}
};
+ public static final FloatProperty<ClearAllButton> DISMISS_ALPHA =
+ new FloatProperty<ClearAllButton>("dismissAlpha") {
+ @Override
+ public Float get(ClearAllButton view) {
+ return view.mDismissAlpha;
+ }
+
+ @Override
+ public void setValue(ClearAllButton view, float v) {
+ view.setDismissAlpha(v);
+ }
+ };
+
private final StatefulActivity mActivity;
private float mScrollAlpha = 1;
private float mContentAlpha = 1;
private float mVisibilityAlpha = 1;
+ private float mDismissAlpha = 1;
private float mFullscreenProgress = 1;
private float mGridProgress = 1;
@@ -97,6 +111,13 @@
}
}
+ public void setDismissAlpha(float alpha) {
+ if (mDismissAlpha != alpha) {
+ mDismissAlpha = alpha;
+ updateAlpha();
+ }
+ }
+
public void onRecentsViewScroll(int scroll, boolean gridEnabled) {
RecentsView recentsView = getRecentsView();
if (recentsView == null) {
@@ -123,7 +144,7 @@
}
private void updateAlpha() {
- final float alpha = mScrollAlpha * mContentAlpha * mVisibilityAlpha;
+ final float alpha = mScrollAlpha * mContentAlpha * mVisibilityAlpha * mDismissAlpha;
setAlpha(alpha);
setClickable(Math.min(alpha, 1) == 1);
}
diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
index e1e1c65..d3077ad 100644
--- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
@@ -3,6 +3,8 @@
import android.content.Context;
import android.util.AttributeSet;
+import androidx.annotation.NonNull;
+
import com.android.launcher3.R;
import com.android.launcher3.util.RunnableList;
import com.android.launcher3.util.SplitConfigurationOptions;
@@ -12,6 +14,8 @@
import com.android.quickstep.util.RecentsOrientedState;
import com.android.systemui.shared.recents.model.Task;
+import java.util.function.Consumer;
+
/**
* TaskView that contains and shows thumbnails for not one, BUT TWO(!!) tasks
*
@@ -98,11 +102,17 @@
@Override
public RunnableList launchTaskAnimated() {
getRecentsView().getSplitPlaceholder().launchTasks(mTask, mSecondaryTask,
- SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT);
+ SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT, null /*callback*/);
return null;
}
@Override
+ public void launchTask(@NonNull Consumer<Boolean> callback, boolean freezeTaskList) {
+ getRecentsView().getSplitPlaceholder().launchTasks(mTask, mSecondaryTask,
+ SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT, callback);
+ }
+
+ @Override
public void onRecycle() {
super.onRecycle();
mSnapshotView2.setThumbnail(mSecondaryTask, null);
diff --git a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
index 5d1c202..1b28c53 100644
--- a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
+++ b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
@@ -223,7 +223,6 @@
mDp = dp;
updateVerticalMargin(SysUINavigationMode.getMode(getContext()));
requestLayout();
- setSplitButtonVisible(mDp.isTablet);
}
public void setSplitButtonVisible(boolean visible) {
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index aeb10aa..8c37644 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -29,6 +29,7 @@
import static com.android.launcher3.LauncherState.BACKGROUND_APP;
import static com.android.launcher3.QuickstepTransitionManager.RECENTS_LAUNCH_DURATION;
import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
+import static com.android.launcher3.Utilities.boundToRange;
import static com.android.launcher3.Utilities.mapToRange;
import static com.android.launcher3.Utilities.squaredHypot;
import static com.android.launcher3.Utilities.squaredTouchSlop;
@@ -49,6 +50,7 @@
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.launcher3.util.SystemUiController.UI_STATE_FULLSCREEN_TASK;
import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
+import static com.android.quickstep.views.ClearAllButton.DISMISS_ALPHA;
import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NON_ZERO_ROTATION;
import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NO_RECENTS;
import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NO_TASKS;
@@ -359,6 +361,7 @@
private static final float INITIAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET = 0.55f;
private static final float ADDITIONAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET = 0.05f;
private static final float ANIMATION_DISMISS_PROGRESS_MIDPOINT = 0.5f;
+ private static final float END_DISMISS_TRANSLATION_INTERPOLATION_OFFSET = 0.75f;
private static final float SIGNIFICANT_MOVE_THRESHOLD_TABLET = 0.15f;
@@ -1004,6 +1007,43 @@
}
}
+ private boolean isLastGridTaskVisible() {
+ TaskView lastTaskView = getLastGridTaskView();
+ return lastTaskView != null && lastTaskView.isVisibleToUser();
+ }
+
+ private TaskView getLastGridTaskView() {
+ IntArray topRowIdArray = getTopRowIdArray();
+ IntArray bottomRowIdArray = getBottomRowIdArray();
+ if (topRowIdArray.isEmpty() && bottomRowIdArray.isEmpty()) {
+ return null;
+ }
+ int lastTaskViewId = topRowIdArray.size() >= bottomRowIdArray.size() ? topRowIdArray.get(
+ topRowIdArray.size() - 1) : bottomRowIdArray.get(bottomRowIdArray.size() - 1);
+ return getTaskViewFromTaskViewId(lastTaskViewId);
+ }
+
+ private int getSnapToLastTaskScrollDiff() {
+ // Snap to a position where ClearAll is just invisible.
+ int screenStart = mOrientationHandler.getPrimaryScroll(this);
+ int clearAllWidth = mOrientationHandler.getPrimarySize(mClearAllButton);
+ int clearAllScroll = getScrollForPage(indexOfChild(mClearAllButton));
+ int targetScroll = clearAllScroll + (mIsRtl ? clearAllWidth : -clearAllWidth);
+ return screenStart - targetScroll;
+ }
+
+ private int getSnapToFocusedTaskScrollDiff(boolean isClearAllHidden) {
+ int screenStart = mOrientationHandler.getPrimaryScroll(this);
+ int targetScroll = getScrollForPage(indexOfChild(getFocusedTaskView()));
+ if (!isClearAllHidden) {
+ int clearAllWidth = mOrientationHandler.getPrimarySize(mClearAllButton);
+ int taskGridHorizontalDiff = mLastComputedTaskSize.right - mLastComputedGridSize.right;
+ int clearAllFocusScrollDiff = taskGridHorizontalDiff - clearAllWidth;
+ targetScroll += mIsRtl ? clearAllFocusScrollDiff : -clearAllFocusScrollDiff;
+ }
+ return screenStart - targetScroll;
+ }
+
private boolean isTaskViewWithinBounds(TaskView tv, int start, int end) {
int taskStart = mOrientationHandler.getChildStart(tv) + (int) tv.getOffsetAdjustment(
showAsFullscreen(), showAsGrid());
@@ -1083,7 +1123,6 @@
super.onPageEndTransition();
if (isClearAllHidden()) {
mActionsView.updateDisabledFlags(OverviewActionsView.DISABLED_SCROLLING, false);
- } else {
}
if (getNextPage() > 0) {
setSwipeDownShouldLaunchApp(true);
@@ -1259,7 +1298,7 @@
mIgnoreResetTaskId == -1 ? null : getTaskViewByTaskId(mIgnoreResetTaskId);
int[] splitTaskIds =
- LauncherSplitScreenListener.INSTANCE.getNoCreate().getSplitTaskIds();
+ LauncherSplitScreenListener.INSTANCE.getNoCreate().getPersistentSplitIds();
int requiredGroupTaskViews = splitTaskIds.length / 2;
// Subtract half the number of split tasks and not total number because we've already
@@ -1506,7 +1545,7 @@
mActionsView.setDp(dp);
mOrientationState.setDeviceProfile(dp);
- // Update RecentsView adn TaskView's DeviceProfile dependent layout.
+ // Update RecentsView and TaskView's DeviceProfile dependent layout.
updateOrientationHandler();
}
@@ -1963,6 +2002,9 @@
/**
* Called only when a swipe-up gesture from an app has completed. Only called after
* {@link #onGestureAnimationStart} and {@link #onGestureAnimationEnd()}.
+ *
+ * TODO(b/198310766) Need to also explicitly exit split screen if
+ * the swipe up was to home
*/
public void onSwipeUpAnimationSuccess() {
animateUpTaskIconScale();
@@ -2599,7 +2641,6 @@
mFirstFloatingTaskView.setAlpha(1);
mFirstFloatingTaskView.addAnimation(anim, startingTaskRect,
mTempRect, mSplitHiddenTaskView, true /*fadeWithThumbnail*/);
- anim.addEndListener(aBoolean -> mActionsView.setSplitButtonVisible(false));
}
/**
@@ -2676,6 +2717,94 @@
}
}
+ float dismissTranslationInterpolationEnd = 1;
+ boolean closeGapBetweenClearAll = false;
+ boolean isClearAllHidden = isClearAllHidden();
+ if (showAsGrid && isLastGridTaskVisible()) {
+ // After dismissal, animate translation of the remaining tasks to fill any gap left
+ // between the end of the grid and the clear all button. Only animate if the clear
+ // all button is visible or would become visible after dismissal.
+ float longGridRowWidthDiff = 0;
+
+ int topGridRowSize = mTopRowIdSet.size();
+ int bottomGridRowSize = taskCount - mTopRowIdSet.size() - 1;
+ boolean topRowLonger = topGridRowSize > bottomGridRowSize;
+ boolean bottomRowLonger = bottomGridRowSize > topGridRowSize;
+ boolean dismissedTaskFromTop = mTopRowIdSet.contains(dismissedTaskViewId);
+ boolean dismissedTaskFromBottom = !dismissedTaskFromTop && !isFocusedTaskDismissed;
+ float gapWidth = 0;
+ if ((topRowLonger && dismissedTaskFromTop)
+ || (bottomRowLonger && dismissedTaskFromBottom)) {
+ gapWidth = dismissedTaskWidth;
+ } else if ((topRowLonger && nextFocusedTaskFromTop)
+ || (bottomRowLonger && !nextFocusedTaskFromTop)) {
+ gapWidth = nextFocusedTaskWidth;
+ }
+ if (gapWidth > 0) {
+ if (taskCount > 2) {
+ // Compensate the removed gap.
+ longGridRowWidthDiff += mIsRtl ? -gapWidth : gapWidth;
+ if (isClearAllHidden) {
+ // If ClearAllButton isn't fully shown, snap to the last task.
+ longGridRowWidthDiff += getSnapToLastTaskScrollDiff();
+ }
+ } else {
+ // If only focused task will be left, snap to focused task instead.
+ longGridRowWidthDiff += getSnapToFocusedTaskScrollDiff(isClearAllHidden);
+ }
+ }
+
+ // If we need to animate the grid to compensate the clear all gap, we split the second
+ // half of the dismiss pending animation (in which the non-dismissed tasks slide into
+ // place) in half again, making the first quarter the existing non-dismissal sliding
+ // and the second quarter this new animation of gap filling. This is due to the fact
+ // that PendingAnimation is a single animation, not a sequence of animations, so we
+ // fake it using interpolation.
+ if (longGridRowWidthDiff != 0) {
+ closeGapBetweenClearAll = true;
+ // Stagger the offsets of each additional task for a delayed animation. We use
+ // half here as this animation is half of half of an animation (1/4th).
+ float halfAdditionalDismissTranslationOffset =
+ (0.5f * ADDITIONAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET);
+ dismissTranslationInterpolationEnd = Utilities.boundToRange(
+ END_DISMISS_TRANSLATION_INTERPOLATION_OFFSET
+ + (taskCount - 1) * halfAdditionalDismissTranslationOffset,
+ END_DISMISS_TRANSLATION_INTERPOLATION_OFFSET, 1);
+ for (int i = 0; i < taskCount; i++) {
+ TaskView taskView = getTaskViewAt(i);
+ anim.setFloat(taskView, TaskView.GRID_END_TRANSLATION_X, longGridRowWidthDiff,
+ clampToProgress(LINEAR, dismissTranslationInterpolationEnd, 1));
+ dismissTranslationInterpolationEnd = Utilities.boundToRange(
+ dismissTranslationInterpolationEnd
+ - halfAdditionalDismissTranslationOffset,
+ END_DISMISS_TRANSLATION_INTERPOLATION_OFFSET, 1);
+ if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mEnableDrawingLiveTile
+ && taskView.isRunningTask()) {
+ anim.addOnFrameCallback(() -> {
+ runActionOnRemoteHandles(
+ remoteTargetHandle ->
+ remoteTargetHandle.mTaskViewSimulator
+ .taskPrimaryTranslation.value =
+ TaskView.GRID_END_TRANSLATION_X.get(taskView));
+ redrawLiveTile();
+ });
+ }
+ }
+
+ // Change alpha of clear all if translating grid to hide it
+ if (isClearAllHidden) {
+ anim.setFloat(mClearAllButton, DISMISS_ALPHA, 0, LINEAR);
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ mClearAllButton.setDismissAlpha(1);
+ }
+ });
+ }
+ }
+ }
+
int distanceFromDismissedTask = 0;
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
@@ -2755,22 +2884,25 @@
float animationStartProgress = Utilities.boundToRange(
INITIAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET
+ ADDITIONAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET
- * ++distanceFromDismissedTask, 0f, 1f);
+ * ++distanceFromDismissedTask, 0f,
+ dismissTranslationInterpolationEnd);
if (taskView == nextFocusedTaskView) {
// Enlarge the task to be focused next, and translate into focus position.
float scale = mTaskWidth / (float) mLastComputedGridTaskSize.width();
anim.setFloat(taskView, TaskView.SNAPSHOT_SCALE, scale,
- clampToProgress(LINEAR, animationStartProgress, 1f));
+ clampToProgress(LINEAR, animationStartProgress,
+ dismissTranslationInterpolationEnd));
anim.setFloat(taskView, taskView.getPrimaryDismissTranslationProperty(),
mIsRtl ? dismissedTaskWidth : -dismissedTaskWidth,
- clampToProgress(LINEAR, animationStartProgress, 1f));
+ clampToProgress(LINEAR, animationStartProgress,
+ dismissTranslationInterpolationEnd));
float secondaryTranslation = -mTaskGridVerticalDiff;
if (!nextFocusedTaskFromTop) {
secondaryTranslation -= mTopBottomRowHeightDiff;
}
anim.setFloat(taskView, taskView.getSecondaryDissmissTranslationProperty(),
- secondaryTranslation,
- clampToProgress(LINEAR, animationStartProgress, 1f));
+ secondaryTranslation, clampToProgress(LINEAR, animationStartProgress,
+ dismissTranslationInterpolationEnd));
anim.setFloat(taskView, TaskView.FOCUS_TRANSITION, 0f,
clampToProgress(LINEAR, 0f, ANIMATION_DISMISS_PROGRESS_MIDPOINT));
} else {
@@ -2778,7 +2910,8 @@
isFocusedTaskDismissed ? nextFocusedTaskWidth : dismissedTaskWidth;
anim.setFloat(taskView, taskView.getPrimaryDismissTranslationProperty(),
mIsRtl ? primaryTranslation : -primaryTranslation,
- clampToProgress(LINEAR, animationStartProgress, 1f));
+ clampToProgress(LINEAR, animationStartProgress,
+ dismissTranslationInterpolationEnd));
}
}
}
@@ -2794,6 +2927,7 @@
mPendingAnimation = anim;
final TaskView finalNextFocusedTaskView = nextFocusedTaskView;
+ final boolean finalCloseGapBetweenClearAll = closeGapBetweenClearAll;
mPendingAnimation.addEndListener(new Consumer<Boolean>() {
@Override
public void accept(Boolean success) {
@@ -2829,49 +2963,68 @@
resetTaskVisuals();
int pageToSnapTo = mCurrentPage;
+ mCurrentPageScrollDiff = 0;
int taskViewIdToSnapTo = -1;
if (showAsGrid) {
- // Get the id of the task view we will snap to based on the current
- // page's relative position as the order of indices change over time due
- // to dismissals.
- TaskView snappedTaskView = getTaskViewAtByAbsoluteIndex(mCurrentPage);
- if (snappedTaskView != null) {
- if (snappedTaskView.getTaskViewId() == mFocusedTaskViewId) {
- if (finalNextFocusedTaskView != null) {
- taskViewIdToSnapTo = finalNextFocusedTaskView.getTaskViewId();
- } else {
- taskViewIdToSnapTo = mFocusedTaskViewId;
+ if (finalCloseGapBetweenClearAll) {
+ if (taskCount > 2) {
+ pageToSnapTo = indexOfChild(mClearAllButton);
+ if (isClearAllHidden) {
+ int clearAllWidth = mOrientationHandler.getPrimarySize(
+ mClearAllButton);
+ mCurrentPageScrollDiff =
+ isRtl() ? clearAllWidth : -clearAllWidth;
}
- } else {
- int snappedTaskViewId = snappedTaskView.getTaskViewId();
- boolean isSnappedTaskInTopRow = mTopRowIdSet.contains(
- snappedTaskViewId);
- IntArray taskViewIdArray =
- isSnappedTaskInTopRow ? getTopRowIdArray()
- : getBottomRowIdArray();
- int snappedIndex = taskViewIdArray.indexOf(snappedTaskViewId);
- taskViewIdArray.removeValue(dismissedTaskViewId);
- if (snappedIndex < taskViewIdArray.size()) {
- taskViewIdToSnapTo = taskViewIdArray.get(snappedIndex);
- } else if (snappedIndex == taskViewIdArray.size()) {
- // If the snapped task is the last item from the dismissed row,
- // snap to the same column in the other grid row
- IntArray inverseRowTaskViewIdArray =
- isSnappedTaskInTopRow ? getBottomRowIdArray()
- : getTopRowIdArray();
- if (snappedIndex < inverseRowTaskViewIdArray.size()) {
- taskViewIdToSnapTo = inverseRowTaskViewIdArray.get(
- snappedIndex);
+ } else if (isClearAllHidden) {
+ // Snap to focused task if clear all is hidden.
+ pageToSnapTo = 0;
+ }
+ } else {
+ // Get the id of the task view we will snap to based on the current
+ // page's relative position as the order of indices change over time due
+ // to dismissals.
+ TaskView snappedTaskView = getTaskViewAtByAbsoluteIndex(mCurrentPage);
+ if (snappedTaskView != null) {
+ if (snappedTaskView.getTaskViewId() == mFocusedTaskViewId) {
+ if (finalNextFocusedTaskView != null) {
+ taskViewIdToSnapTo =
+ finalNextFocusedTaskView.getTaskViewId();
+ } else {
+ taskViewIdToSnapTo = mFocusedTaskViewId;
+ }
+ } else {
+ int snappedTaskViewId = snappedTaskView.getTaskViewId();
+ boolean isSnappedTaskInTopRow = mTopRowIdSet.contains(
+ snappedTaskViewId);
+ IntArray taskViewIdArray =
+ isSnappedTaskInTopRow ? getTopRowIdArray()
+ : getBottomRowIdArray();
+ int snappedIndex = taskViewIdArray.indexOf(snappedTaskViewId);
+ taskViewIdArray.removeValue(dismissedTaskViewId);
+ if (snappedIndex < taskViewIdArray.size()) {
+ taskViewIdToSnapTo = taskViewIdArray.get(snappedIndex);
+ } else if (snappedIndex == taskViewIdArray.size()) {
+ // If the snapped task is the last item from the
+ // dismissed row,
+ // snap to the same column in the other grid row
+ IntArray inverseRowTaskViewIdArray =
+ isSnappedTaskInTopRow ? getBottomRowIdArray()
+ : getTopRowIdArray();
+ if (snappedIndex < inverseRowTaskViewIdArray.size()) {
+ taskViewIdToSnapTo = inverseRowTaskViewIdArray.get(
+ snappedIndex);
+ }
}
}
}
- }
- int primaryScroll = mOrientationHandler.getPrimaryScroll(RecentsView.this);
- int currentPageScroll = getScrollForPage(pageToSnapTo);
- mCurrentPageScrollDiff = primaryScroll - currentPageScroll;
+ int primaryScroll = mOrientationHandler.getPrimaryScroll(
+ RecentsView.this);
+ int currentPageScroll = getScrollForPage(pageToSnapTo);
+ mCurrentPageScrollDiff = primaryScroll - currentPageScroll;
+ }
} else if (dismissedIndex < pageToSnapTo || pageToSnapTo == taskCount - 1) {
- pageToSnapTo -= 1;
+ pageToSnapTo--;
}
removeViewInLayout(dismissedTaskView);
mTopRowIdSet.remove(dismissedTaskViewId);
@@ -2936,9 +3089,16 @@
}
}
setCurrentPage(pageToSnapTo);
+ // Update various scroll depedent UI.
dispatchScrollChanged();
+ updateActionsViewScrollAlpha();
+ if (isClearAllHidden()) {
+ mActionsView.updateDisabledFlags(OverviewActionsView.DISABLED_SCROLLING,
+ false);
+ }
}
}
+ updateFocusedSplitButtonVisibility();
onDismissAnimationEnds();
mPendingAnimation = null;
}
@@ -2947,6 +3107,19 @@
}
/**
+ * Shows split button if
+ * * We're in large screen
+ * * We're not already in split
+ * * There are at least 2 tasks to invoke split
+ */
+ private void updateFocusedSplitButtonVisibility() {
+ mActionsView.setSplitButtonVisible(mActivity.getDeviceProfile().isTablet &&
+ !(getRunningTaskView() instanceof GroupedTaskView) &&
+ getTaskViewCount() > 1
+ );
+ }
+
+ /**
* Returns all the tasks in the top row, without the focused task
*/
private IntArray getTopRowIdArray() {
@@ -3751,7 +3924,6 @@
mSecondSplitHiddenTaskView.setVisibility(VISIBLE);
mSecondSplitHiddenTaskView = null;
}
- mActionsView.setSplitButtonVisible(true);
}
private void updateDeadZoneRects() {
@@ -3966,6 +4138,7 @@
@Override
protected void notifyPageSwitchListener(int prevPage) {
super.notifyPageSwitchListener(prevPage);
+ updateFocusedSplitButtonVisibility();
loadVisibleTaskData(TaskView.FLAG_UPDATE_ALL);
updateEnabledOverlays();
}
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index bfe6ca4..8a20da6 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -343,6 +343,19 @@
}
};
+ public static final FloatProperty<TaskView> GRID_END_TRANSLATION_X =
+ new FloatProperty<TaskView>("gridEndTranslationX") {
+ @Override
+ public void setValue(TaskView taskView, float v) {
+ taskView.setGridEndTranslationX(v);
+ }
+
+ @Override
+ public Float get(TaskView taskView) {
+ return taskView.mGridEndTranslationX;
+ }
+ };
+
public static final FloatProperty<TaskView> SNAPSHOT_SCALE =
new FloatProperty<TaskView>("snapshotScale") {
@Override
@@ -381,6 +394,8 @@
// The following grid translations scales with mGridProgress.
private float mGridTranslationX;
private float mGridTranslationY;
+ // The following grid translation is used to animate closing the gap between grid and clear all.
+ private float mGridEndTranslationX;
// Applied as a complement to gridTranslation, for adjusting the carousel overview and quick
// switch.
private float mNonGridTranslationX;
@@ -950,8 +965,8 @@
protected void resetViewTransforms() {
// fullscreenTranslation and accumulatedTranslation should not be reset, as
// resetViewTransforms is called during Quickswitch scrolling.
- mDismissTranslationX = mTaskOffsetTranslationX = mTaskResistanceTranslationX =
- mSplitSelectTranslationX = 0f;
+ mDismissTranslationX = mTaskOffsetTranslationX =
+ mTaskResistanceTranslationX = mSplitSelectTranslationX = mGridEndTranslationX = 0f;
mDismissTranslationY = mTaskOffsetTranslationY = mTaskResistanceTranslationY =
mSplitSelectTranslationY = 0f;
setSnapshotScale(1f);
@@ -1162,6 +1177,11 @@
return mGridTranslationY;
}
+ private void setGridEndTranslationX(float gridEndTranslationX) {
+ mGridEndTranslationX = gridEndTranslationX;
+ applyTranslationX();
+ }
+
public float getScrollAdjustment(boolean fullscreenEnabled, boolean gridEnabled) {
float scrollAdjustment = 0;
if (gridEnabled) {
@@ -1191,7 +1211,7 @@
private void applyTranslationX() {
setTranslationX(mDismissTranslationX + mTaskOffsetTranslationX + mTaskResistanceTranslationX
- + mSplitSelectTranslationX + getPersistentTranslationX());
+ + mSplitSelectTranslationX + mGridEndTranslationX + getPersistentTranslationX());
}
private void applyTranslationY() {
@@ -1541,7 +1561,7 @@
public FullscreenDrawParams(Context context) {
mCornerRadius = TaskCornerRadius.get(context);
- mWindowCornerRadius = QuickStepContract.getWindowCornerRadius(context.getResources());
+ mWindowCornerRadius = QuickStepContract.getWindowCornerRadius(context);
mCurrentDrawnCornerRadius = mCornerRadius;
}
diff --git a/res/layout/notification_content.xml b/res/layout/notification_content.xml
index 84822a6..91897e9 100644
--- a/res/layout/notification_content.xml
+++ b/res/layout/notification_content.xml
@@ -14,10 +14,11 @@
limitations under the License.
-->
-<merge
+<com.android.launcher3.notification.NotificationMainView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
- android:layout_height="wrap_content">
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
<!-- header -->
<FrameLayout
@@ -49,7 +50,7 @@
</FrameLayout>
<!-- Main view -->
- <com.android.launcher3.notification.NotificationMainView
+ <FrameLayout
android:id="@+id/main_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -59,7 +60,6 @@
android:id="@+id/text_and_background"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:background="?attr/popupColorPrimary"
android:gravity="center_vertical"
android:orientation="vertical"
android:paddingTop="@dimen/notification_padding"
@@ -95,5 +95,5 @@
android:layout_marginTop="@dimen/notification_padding"
android:layout_marginStart="@dimen/notification_icon_padding" />
- </com.android.launcher3.notification.NotificationMainView>
-</merge>
\ No newline at end of file
+ </FrameLayout>
+</com.android.launcher3.notification.NotificationMainView>
\ No newline at end of file
diff --git a/res/layout/popup_container.xml b/res/layout/popup_container.xml
index 18014bb..9327287 100644
--- a/res/layout/popup_container.xml
+++ b/res/layout/popup_container.xml
@@ -31,12 +31,9 @@
android:elevation="@dimen/deep_shortcuts_elevation"
android:orientation="vertical"/>
- <LinearLayout
+ <com.android.launcher3.notification.NotificationContainer
android:id="@+id/notification_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:visibility="gone"
- android:background="?attr/popupColorPrimary"
- android:elevation="@dimen/deep_shortcuts_elevation"
- android:orientation="vertical"/>
+ android:visibility="gone"/>
</com.android.launcher3.popup.PopupContainerWithArrow>
\ No newline at end of file
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index bb06be8..5fc0480 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -272,6 +272,8 @@
<!-- Notifications -->
<dimen name="bg_round_rect_radius">8dp</dimen>
+ <dimen name="notification_max_trans">8dp</dimen>
+ <dimen name="notification_space">8dp</dimen>
<dimen name="notification_padding">16dp</dimen>
<dimen name="notification_padding_top">18dp</dimen>
<dimen name="notification_header_text_size">14sp</dimen>
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index e52d1be..54920e1 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -90,6 +90,8 @@
private final PointF mTranslationForReorderBounce = new PointF(0, 0);
private final PointF mTranslationForReorderPreview = new PointF(0, 0);
+ private float mTranslationXForTaskbarAlignmentAnimation = 0f;
+
private final PointF mTranslationForMoveFromCenterAnimation = new PointF(0, 0);
private float mScaleForReorderBounce = 1f;
@@ -825,7 +827,8 @@
private void updateTranslation() {
super.setTranslationX(mTranslationForReorderBounce.x + mTranslationForReorderPreview.x
- + mTranslationForMoveFromCenterAnimation.x);
+ + mTranslationForMoveFromCenterAnimation.x
+ + mTranslationXForTaskbarAlignmentAnimation);
super.setTranslationY(mTranslationForReorderBounce.y + mTranslationForReorderPreview.y
+ mTranslationForMoveFromCenterAnimation.y);
}
@@ -860,11 +863,29 @@
return mScaleForReorderBounce;
}
+ /**
+ * Sets translation values for move from center animation
+ */
public void setTranslationForMoveFromCenterAnimation(float x, float y) {
mTranslationForMoveFromCenterAnimation.set(x, y);
updateTranslation();
}
+ /**
+ * Sets translationX for taskbar to launcher alignment animation
+ */
+ public void setTranslationXForTaskbarAlignmentAnimation(float translationX) {
+ mTranslationXForTaskbarAlignmentAnimation = translationX;
+ updateTranslation();
+ }
+
+ /**
+ * Returns translationX value for taskbar to launcher alignment animation
+ */
+ public float getTranslationXForTaskbarAlignmentAnimation() {
+ return mTranslationXForTaskbarAlignmentAnimation;
+ }
+
public View getView() {
return this;
}
diff --git a/src/com/android/launcher3/DropTargetBar.java b/src/com/android/launcher3/DropTargetBar.java
index 88f6c49..eb42a65 100644
--- a/src/com/android/launcher3/DropTargetBar.java
+++ b/src/com/android/launcher3/DropTargetBar.java
@@ -25,6 +25,7 @@
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
+import android.util.Log;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
@@ -32,10 +33,13 @@
import android.view.ViewPropertyAnimator;
import android.widget.FrameLayout;
+import androidx.annotation.NonNull;
+
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragController.DragListener;
import com.android.launcher3.dragndrop.DragOptions;
+import com.android.launcher3.testing.TestProtocol;
/*
* The top bar containing various drop targets: Delete/App Info/Uninstall.
@@ -212,6 +216,9 @@
}
public void animateToVisibility(boolean isVisible) {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.NO_DROP_TARGET, "8");
+ }
if (mVisible != isVisible) {
mVisible = isVisible;
@@ -238,6 +245,9 @@
*/
@Override
public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.NO_DROP_TARGET, "7");
+ }
animateToVisibility(true);
}
@@ -261,4 +271,12 @@
public ButtonDropTarget[] getDropTargets() {
return mDropTargets;
}
+
+ @Override
+ protected void onVisibilityChanged(@NonNull View changedView, int visibility) {
+ super.onVisibilityChanged(changedView, visibility);
+ if (TestProtocol.sDebugTracing && visibility == VISIBLE) {
+ Log.d(TestProtocol.NO_DROP_TARGET, "9");
+ }
+ }
}
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index d5972a7..f091262 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -239,7 +239,7 @@
"Enables scrim during app launch animation.");
public static final BooleanFlag ENABLE_SPLIT_SELECT = getDebugFlag(
- "ENABLE_SPLIT_SELECT", false, "Uses new split screen selection overview UI");
+ "ENABLE_SPLIT_SELECT", true, "Uses new split screen selection overview UI");
public static final BooleanFlag ENABLE_ENFORCED_ROUNDED_CORNERS = new DeviceFlag(
"ENABLE_ENFORCED_ROUNDED_CORNERS", true, "Enforce rounded corners on all App Widgets");
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index 1e0edac..fdb2799 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -22,6 +22,7 @@
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.util.Log;
import android.view.DragEvent;
import android.view.KeyEvent;
import android.view.MotionEvent;
@@ -34,6 +35,7 @@
import com.android.launcher3.logging.InstanceId;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.TouchController;
import com.android.launcher3.views.ActivityContext;
@@ -146,6 +148,9 @@
float initialDragViewScale,
float dragViewScaleOnDrop,
DragOptions options) {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.NO_DROP_TARGET, "4");
+ }
return startDrag(drawable, /* view= */ null, originalView, dragLayerX, dragLayerY,
source, dragInfo, dragOffset, dragRegion, initialDragViewScale, dragViewScaleOnDrop,
options);
@@ -203,6 +208,9 @@
DragOptions options);
protected void callOnDragStart() {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.NO_DROP_TARGET, "6");
+ }
if (mOptions.preDragCondition != null) {
mOptions.preDragCondition.onPreDragEnd(mDragObject, true /* dragStarted*/);
}
diff --git a/src/com/android/launcher3/dragndrop/LauncherDragController.java b/src/com/android/launcher3/dragndrop/LauncherDragController.java
index 0e8b0a5..dcbfa50 100644
--- a/src/com/android/launcher3/dragndrop/LauncherDragController.java
+++ b/src/com/android/launcher3/dragndrop/LauncherDragController.java
@@ -24,6 +24,7 @@
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.util.Log;
import android.view.HapticFeedbackConstants;
import android.view.View;
@@ -36,6 +37,7 @@
import com.android.launcher3.R;
import com.android.launcher3.accessibility.DragViewStateAnnouncer;
import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.testing.TestProtocol;
/**
* Drag controller for Launcher activity
@@ -65,6 +67,9 @@
float initialDragViewScale,
float dragViewScaleOnDrop,
DragOptions options) {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.NO_DROP_TARGET, "5");
+ }
if (PROFILE_DRAWING_DURING_DRAG) {
android.os.Debug.startMethodTracing("Launcher");
}
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index 60d8cdb..439df80 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -132,6 +132,9 @@
private Rect mTouchArea = new Rect();
+ private final PointF mTranslationForMoveFromCenterAnimation = new PointF(0, 0);
+ private float mTranslationXForTaskbarAlignmentAnimation = 0f;
+
private final PointF mTranslationForReorderBounce = new PointF(0, 0);
private final PointF mTranslationForReorderPreview = new PointF(0, 0);
private float mScaleForReorderBounce = 1f;
@@ -765,8 +768,11 @@
}
private void updateTranslation() {
- super.setTranslationX(mTranslationForReorderBounce.x + mTranslationForReorderPreview.x);
- super.setTranslationY(mTranslationForReorderBounce.y + mTranslationForReorderPreview.y);
+ super.setTranslationX(mTranslationForReorderBounce.x + mTranslationForReorderPreview.x
+ + mTranslationForMoveFromCenterAnimation.x
+ + mTranslationXForTaskbarAlignmentAnimation);
+ super.setTranslationY(mTranslationForReorderBounce.y + mTranslationForReorderPreview.y
+ + mTranslationForMoveFromCenterAnimation.y);
}
public void setReorderBounceOffset(float x, float y) {
@@ -778,6 +784,29 @@
offset.set(mTranslationForReorderBounce);
}
+ /**
+ * Sets translationX value for taskbar to launcher alignment animation
+ */
+ public void setTranslationForTaskbarAlignmentAnimation(float translationX) {
+ mTranslationXForTaskbarAlignmentAnimation = translationX;
+ updateTranslation();
+ }
+
+ /**
+ * Returns translation values for taskbar to launcher alignment animation
+ */
+ public float getTranslationXForTaskbarAlignmentAnimation() {
+ return mTranslationXForTaskbarAlignmentAnimation;
+ }
+
+ /**
+ * Sets translation values for move from center animation
+ */
+ public void setTranslationForMoveFromCenterAnimation(float x, float y) {
+ mTranslationForMoveFromCenterAnimation.set(x, y);
+ updateTranslation();
+ }
+
@Override
public void setReorderPreviewOffset(float x, float y) {
mTranslationForReorderPreview.set(x, y);
diff --git a/src/com/android/launcher3/notification/NotificationContainer.java b/src/com/android/launcher3/notification/NotificationContainer.java
new file mode 100644
index 0000000..9eb05cd
--- /dev/null
+++ b/src/com/android/launcher3/notification/NotificationContainer.java
@@ -0,0 +1,283 @@
+/*
+ * 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.notification;
+
+import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
+import static com.android.launcher3.touch.SingleAxisSwipeDetector.HORIZONTAL;
+
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.util.FloatProperty;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.FrameLayout;
+
+import com.android.launcher3.R;
+import com.android.launcher3.anim.AnimationSuccessListener;
+import com.android.launcher3.popup.PopupContainerWithArrow;
+import com.android.launcher3.touch.BaseSwipeDetector;
+import com.android.launcher3.touch.OverScroll;
+import com.android.launcher3.touch.SingleAxisSwipeDetector;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Class to manage the notification UI in a {@link PopupContainerWithArrow}.
+ *
+ * - Has two {@link NotificationMainView} that represent the top two notifications
+ * - Handles dismissing a notification
+ */
+public class NotificationContainer extends FrameLayout implements SingleAxisSwipeDetector.Listener {
+
+ private static final FloatProperty<NotificationContainer> DRAG_TRANSLATION_X =
+ new FloatProperty<NotificationContainer>("notificationProgress") {
+ @Override
+ public void setValue(NotificationContainer view, float transX) {
+ view.setDragTranslationX(transX);
+ }
+
+ @Override
+ public Float get(NotificationContainer view) {
+ return view.mDragTranslationX;
+ }
+ };
+
+ private static final Rect sTempRect = new Rect();
+
+ private final SingleAxisSwipeDetector mSwipeDetector;
+ private final List<NotificationInfo> mNotificationInfos = new ArrayList<>();
+ private boolean mIgnoreTouch = false;
+
+ private final ObjectAnimator mContentTranslateAnimator;
+ private float mDragTranslationX = 0;
+
+ private final NotificationMainView mPrimaryView;
+ private final NotificationMainView mSecondaryView;
+ private PopupContainerWithArrow mPopupContainer;
+
+ public NotificationContainer(Context context) {
+ this(context, null, 0);
+ }
+
+ public NotificationContainer(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public NotificationContainer(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ mSwipeDetector = new SingleAxisSwipeDetector(getContext(), this, HORIZONTAL);
+ mSwipeDetector.setDetectableScrollConditions(SingleAxisSwipeDetector.DIRECTION_BOTH, false);
+ mContentTranslateAnimator = ObjectAnimator.ofFloat(this, DRAG_TRANSLATION_X, 0);
+
+ mPrimaryView = (NotificationMainView) View.inflate(getContext(),
+ R.layout.notification_content, null);
+ mSecondaryView = (NotificationMainView) View.inflate(getContext(),
+ R.layout.notification_content, null);
+ mSecondaryView.setAlpha(0);
+
+ addView(mSecondaryView);
+ addView(mPrimaryView);
+
+ }
+
+ public void setPopupView(PopupContainerWithArrow popupView) {
+ mPopupContainer = popupView;
+ }
+
+ /**
+ * Returns true if we should intercept the swipe.
+ */
+ public boolean onInterceptSwipeEvent(MotionEvent ev) {
+ if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+ sTempRect.set(getLeft(), getTop(), getRight(), getBottom());
+ mIgnoreTouch = !sTempRect.contains((int) ev.getX(), (int) ev.getY());
+ if (!mIgnoreTouch) {
+ mPopupContainer.getParent().requestDisallowInterceptTouchEvent(true);
+ }
+ }
+ if (mIgnoreTouch) {
+ return false;
+ }
+ if (mPrimaryView.getNotificationInfo() == null) {
+ // The notification hasn't been populated yet.
+ return false;
+ }
+
+ mSwipeDetector.onTouchEvent(ev);
+ return mSwipeDetector.isDraggingOrSettling();
+ }
+
+ /**
+ * Returns true when we should handle the swipe.
+ */
+ public boolean onSwipeEvent(MotionEvent ev) {
+ if (mIgnoreTouch) {
+ return false;
+ }
+ if (mPrimaryView.getNotificationInfo() == null) {
+ // The notification hasn't been populated yet.
+ return false;
+ }
+ return mSwipeDetector.onTouchEvent(ev);
+ }
+
+ /**
+ * Applies the list of @param notificationInfos to this container.
+ */
+ public void applyNotificationInfos(final List<NotificationInfo> notificationInfos) {
+ mNotificationInfos.clear();
+ if (notificationInfos.isEmpty()) {
+ mPrimaryView.applyNotificationInfo(null);
+ mSecondaryView.applyNotificationInfo(null);
+ return;
+ }
+ mNotificationInfos.addAll(notificationInfos);
+
+ NotificationInfo mainNotification = notificationInfos.get(0);
+ mPrimaryView.applyNotificationInfo(mainNotification);
+ mSecondaryView.applyNotificationInfo(notificationInfos.size() > 1
+ ? notificationInfos.get(1)
+ : null);
+ }
+
+ /**
+ * Trims the notifications.
+ * @param notificationKeys List of all valid notification keys.
+ */
+ public void trimNotifications(final List<String> notificationKeys) {
+ Iterator<NotificationInfo> iterator = mNotificationInfos.iterator();
+ while (iterator.hasNext()) {
+ if (!notificationKeys.contains(iterator.next().notificationKey)) {
+ iterator.remove();
+ }
+ }
+
+ NotificationInfo primaryInfo = mNotificationInfos.size() > 0
+ ? mNotificationInfos.get(0)
+ : null;
+ NotificationInfo secondaryInfo = mNotificationInfos.size() > 1
+ ? mNotificationInfos.get(1)
+ : null;
+
+ mPrimaryView.applyNotificationInfo(primaryInfo);
+ mSecondaryView.applyNotificationInfo(secondaryInfo);
+
+ mPrimaryView.onPrimaryDrag(0);
+ mSecondaryView.onSecondaryDrag(0);
+ }
+
+ private void setDragTranslationX(float translationX) {
+ mDragTranslationX = translationX;
+
+ float progress = translationX / getWidth();
+ mPrimaryView.onPrimaryDrag(progress);
+ if (mSecondaryView.getNotificationInfo() == null) {
+ mSecondaryView.setAlpha(0f);
+ } else {
+ mSecondaryView.onSecondaryDrag(progress);
+ }
+ }
+
+ // SingleAxisSwipeDetector.Listener's
+ @Override
+ public void onDragStart(boolean start, float startDisplacement) {
+ mPopupContainer.showArrow(false);
+ }
+
+ @Override
+ public boolean onDrag(float displacement) {
+ if (!mPrimaryView.canChildBeDismissed()) {
+ displacement = OverScroll.dampedScroll(displacement, getWidth());
+ }
+
+ float progress = displacement / getWidth();
+ mPrimaryView.onPrimaryDrag(progress);
+ if (mSecondaryView.getNotificationInfo() == null) {
+ mSecondaryView.setAlpha(0f);
+ } else {
+ mSecondaryView.onSecondaryDrag(progress);
+ }
+ mContentTranslateAnimator.cancel();
+ return true;
+ }
+
+ @Override
+ public void onDragEnd(float velocity) {
+ final boolean willExit;
+ final float endTranslation;
+ final float startTranslation = mPrimaryView.getTranslationX();
+ final float width = getWidth();
+
+ if (!mPrimaryView.canChildBeDismissed()) {
+ willExit = false;
+ endTranslation = 0;
+ } else if (mSwipeDetector.isFling(velocity)) {
+ willExit = true;
+ endTranslation = velocity < 0 ? -width : width;
+ } else if (Math.abs(startTranslation) > width / 2f) {
+ willExit = true;
+ endTranslation = (startTranslation < 0 ? -width : width);
+ } else {
+ willExit = false;
+ endTranslation = 0;
+ }
+
+ long duration = BaseSwipeDetector.calculateDuration(velocity,
+ (endTranslation - startTranslation) / width);
+
+ mContentTranslateAnimator.removeAllListeners();
+ mContentTranslateAnimator.setDuration(duration)
+ .setInterpolator(scrollInterpolatorForVelocity(velocity));
+ mContentTranslateAnimator.setFloatValues(startTranslation, endTranslation);
+
+ NotificationMainView current = mPrimaryView;
+ mContentTranslateAnimator.addListener(new AnimationSuccessListener() {
+ @Override
+ public void onAnimationSuccess(Animator animator) {
+ mSwipeDetector.finishedScrolling();
+ if (willExit) {
+ current.onChildDismissed();
+ }
+ mPopupContainer.showArrow(true);
+ }
+ });
+ mContentTranslateAnimator.start();
+ }
+
+ /**
+ * Animates the background color to a new color.
+ * @param color The color to change to.
+ * @param animatorSetOut The AnimatorSet where we add the color animator to.
+ */
+ public void updateBackgroundColor(int color, AnimatorSet animatorSetOut) {
+ mPrimaryView.updateBackgroundColor(color, animatorSetOut);
+ mSecondaryView.updateBackgroundColor(color, animatorSetOut);
+ }
+
+ /**
+ * Updates the header with a new @param notificationCount.
+ */
+ public void updateHeader(int notificationCount) {
+ mPrimaryView.updateHeader(notificationCount);
+ mSecondaryView.updateHeader(notificationCount - 1);
+ }
+}
diff --git a/src/com/android/launcher3/notification/NotificationItemView.java b/src/com/android/launcher3/notification/NotificationItemView.java
deleted file mode 100644
index af943a6..0000000
--- a/src/com/android/launcher3/notification/NotificationItemView.java
+++ /dev/null
@@ -1,179 +0,0 @@
-/*
- * Copyright (C) 2017 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.notification;
-
-import android.animation.AnimatorSet;
-import android.content.Context;
-import android.graphics.Outline;
-import android.graphics.Rect;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewGroup.MarginLayoutParams;
-import android.view.ViewOutlineProvider;
-import android.widget.TextView;
-
-import com.android.launcher3.R;
-import com.android.launcher3.popup.PopupContainerWithArrow;
-import com.android.launcher3.util.Themes;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Utility class to manage notification UI
- */
-public class NotificationItemView {
-
- private static final Rect sTempRect = new Rect();
-
- private final Context mContext;
- private final PopupContainerWithArrow mPopupContainer;
- private final ViewGroup mRootView;
-
- private final TextView mHeaderCount;
- private final NotificationMainView mMainView;
-
- private final View mHeader;
-
- private View mGutter;
-
- private boolean mIgnoreTouch = false;
- private List<NotificationInfo> mNotificationInfos = new ArrayList<>();
-
- public NotificationItemView(PopupContainerWithArrow container, ViewGroup rootView) {
- mPopupContainer = container;
- mRootView = rootView;
- mContext = container.getContext();
-
- mHeaderCount = container.findViewById(R.id.notification_count);
- mMainView = container.findViewById(R.id.main_view);
-
- mHeader = container.findViewById(R.id.header);
-
- float radius = Themes.getDialogCornerRadius(mContext);
- rootView.setClipToOutline(true);
- rootView.setOutlineProvider(new ViewOutlineProvider() {
- @Override
- public void getOutline(View view, Outline outline) {
- outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), radius);
- }
- });
- }
-
- /**
- * Animates the background color to a new color.
- * @param color The color to change to.
- * @param animatorSetOut The AnimatorSet where we add the color animator to.
- */
- public void updateBackgroundColor(int color, AnimatorSet animatorSetOut) {
- mMainView.updateBackgroundColor(color, animatorSetOut);
- }
-
- public void addGutter() {
- if (mGutter == null) {
- mGutter = mPopupContainer.inflateAndAdd(R.layout.notification_gutter, mRootView);
- }
- }
-
- public void inverseGutterMargin() {
- MarginLayoutParams lp = (MarginLayoutParams) mGutter.getLayoutParams();
- int top = lp.topMargin;
- lp.topMargin = lp.bottomMargin;
- lp.bottomMargin = top;
- }
-
- public void removeAllViews() {
- mRootView.removeView(mMainView);
- mRootView.removeView(mHeader);
- if (mGutter != null) {
- mRootView.removeView(mGutter);
- }
- }
-
- /**
- * Updates the header text.
- * @param notificationCount The number of notifications.
- */
- public void updateHeader(int notificationCount) {
- final String text;
- final int visibility;
- if (notificationCount <= 1) {
- text = "";
- visibility = View.INVISIBLE;
- } else {
- text = String.valueOf(notificationCount);
- visibility = View.VISIBLE;
-
- }
- mHeaderCount.setText(text);
- mHeaderCount.setVisibility(visibility);
- }
-
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- if (ev.getAction() == MotionEvent.ACTION_DOWN) {
- sTempRect.set(mRootView.getLeft(), mRootView.getTop(),
- mRootView.getRight(), mRootView.getBottom());
- mIgnoreTouch = !sTempRect.contains((int) ev.getX(), (int) ev.getY());
- if (!mIgnoreTouch) {
- mPopupContainer.getParent().requestDisallowInterceptTouchEvent(true);
- }
- }
- if (mIgnoreTouch) {
- return false;
- }
- if (mMainView.getNotificationInfo() == null) {
- // The notification hasn't been populated yet.
- return false;
- }
-
- return false;
- }
-
- public void applyNotificationInfos(final List<NotificationInfo> notificationInfos) {
- mNotificationInfos.clear();
- if (notificationInfos.isEmpty()) {
- return;
- }
- mNotificationInfos.addAll(notificationInfos);
-
- NotificationInfo mainNotification = notificationInfos.get(0);
- mMainView.applyNotificationInfo(mainNotification, false);
- }
-
- public void trimNotifications(final List<String> notificationKeys) {
- NotificationInfo currentMainNotificationInfo = mMainView.getNotificationInfo();
- boolean shouldUpdateMainNotification = !notificationKeys.contains(
- currentMainNotificationInfo.notificationKey);
-
- if (shouldUpdateMainNotification) {
- int size = notificationKeys.size();
- NotificationInfo nextNotification = null;
- // We get the latest notification by finding the notification after the one that was
- // just dismissed.
- for (int i = 0; i < size; ++i) {
- if (currentMainNotificationInfo == mNotificationInfos.get(i) && i + 1 < size) {
- nextNotification = mNotificationInfos.get(i + 1);
- break;
- }
- }
- if (nextNotification != null) {
- mMainView.applyNotificationInfo(nextNotification, true);
- }
- }
- }
-}
diff --git a/src/com/android/launcher3/notification/NotificationMainView.java b/src/com/android/launcher3/notification/NotificationMainView.java
index b8aa824..f9ff8a6 100644
--- a/src/com/android/launcher3/notification/NotificationMainView.java
+++ b/src/com/android/launcher3/notification/NotificationMainView.java
@@ -16,62 +16,70 @@
package com.android.launcher3.notification;
+import static com.android.launcher3.Utilities.mapToRange;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_NOTIFICATION_DISMISSED;
import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.content.Context;
-import android.graphics.Color;
-import android.graphics.drawable.ColorDrawable;
+import android.graphics.Outline;
+import android.graphics.Rect;
+import android.graphics.drawable.GradientDrawable;
import android.os.Build;
import android.text.TextUtils;
import android.util.AttributeSet;
-import android.util.FloatProperty;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.FrameLayout;
+import android.view.ViewOutlineProvider;
+import android.widget.LinearLayout;
import android.widget.TextView;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.touch.SingleAxisSwipeDetector;
+import com.android.launcher3.util.Themes;
/**
* A {@link android.widget.FrameLayout} that contains a single notification,
* e.g. icon + title + text.
*/
@TargetApi(Build.VERSION_CODES.N)
-public class NotificationMainView extends FrameLayout {
-
- private static final FloatProperty<NotificationMainView> CONTENT_TRANSLATION =
- new FloatProperty<NotificationMainView>("contentTranslation") {
- @Override
- public void setValue(NotificationMainView view, float v) {
- view.setContentTranslation(v);
- }
-
- @Override
- public Float get(NotificationMainView view) {
- return view.mTextAndBackground.getTranslationX();
- }
- };
+public class NotificationMainView extends LinearLayout {
// This is used only to track the notification view, so that it can be properly logged.
public static final ItemInfo NOTIFICATION_ITEM_INFO = new ItemInfo();
+ // Value when the primary notification main view will be gone (zero alpha).
+ private static final float PRIMARY_GONE_PROGRESS = 0.7f;
+ private static final float PRIMARY_MIN_PROGRESS = 0.40f;
+ private static final float PRIMARY_MAX_PROGRESS = 0.60f;
+ private static final float SECONDARY_MIN_PROGRESS = 0.30f;
+ private static final float SECONDARY_MAX_PROGRESS = 0.50f;
+ private static final float SECONDARY_CONTENT_MAX_PROGRESS = 0.6f;
+
private NotificationInfo mNotificationInfo;
- private ViewGroup mTextAndBackground;
private int mBackgroundColor;
private TextView mTitleView;
private TextView mTextView;
private View mIconView;
- private SingleAxisSwipeDetector mSwipeDetector;
+ private View mHeader;
+ private View mMainView;
- private final ColorDrawable mColorDrawable;
+ private TextView mHeaderCount;
+ private final Rect mOutline = new Rect();
+
+ // Space between notifications during swipe
+ private final int mNotificationSpace;
+ private final int mMaxTransX;
+ private final int mMaxElevation;
+
+ private final GradientDrawable mBackground;
public NotificationMainView(Context context) {
this(context, null, 0);
@@ -82,28 +90,77 @@
}
public NotificationMainView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ this(context, attrs, defStyle, 0);
+ }
- mColorDrawable = new ColorDrawable(Color.TRANSPARENT);
+ public NotificationMainView(Context context, AttributeSet attrs, int defStyle, int defStylRes) {
+ super(context, attrs, defStyle, defStylRes);
+
+ float outlineRadius = Themes.getDialogCornerRadius(context);
+
+ mBackground = new GradientDrawable();
+ mBackground.setColor(Themes.getAttrColor(context, R.attr.popupColorPrimary));
+ mBackground.setCornerRadius(outlineRadius);
+ setBackground(mBackground);
+
+ mMaxElevation = getResources().getDimensionPixelSize(R.dimen.deep_shortcuts_elevation);
+ setElevation(mMaxElevation);
+
+ mMaxTransX = getResources().getDimensionPixelSize(R.dimen.notification_max_trans);
+ mNotificationSpace = getResources().getDimensionPixelSize(R.dimen.notification_space);
+
+ setClipToOutline(true);
+ setOutlineProvider(new ViewOutlineProvider() {
+ @Override
+ public void getOutline(View view, Outline outline) {
+ outline.setRoundRect(mOutline, outlineRadius);
+ }
+ });
+ }
+
+ /**
+ * Updates the header text.
+ * @param notificationCount The number of notifications.
+ */
+ public void updateHeader(int notificationCount) {
+ final String text;
+ final int visibility;
+ if (notificationCount <= 1) {
+ text = "";
+ visibility = View.INVISIBLE;
+ } else {
+ text = String.valueOf(notificationCount);
+ visibility = View.VISIBLE;
+
+ }
+ mHeaderCount.setText(text);
+ mHeaderCount.setVisibility(visibility);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- mTextAndBackground = findViewById(R.id.text_and_background);
- mTitleView = mTextAndBackground.findViewById(R.id.title);
- mTextView = mTextAndBackground.findViewById(R.id.text);
+ ViewGroup textAndBackground = findViewById(R.id.text_and_background);
+ mTitleView = textAndBackground.findViewById(R.id.title);
+ mTextView = textAndBackground.findViewById(R.id.text);
mIconView = findViewById(R.id.popup_item_icon);
+ mHeaderCount = findViewById(R.id.notification_count);
- ColorDrawable colorBackground = (ColorDrawable) mTextAndBackground.getBackground();
- updateBackgroundColor(colorBackground.getColor());
+ mHeader = findViewById(R.id.header);
+ mMainView = findViewById(R.id.main_view);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ mOutline.set(0, 0, getWidth(), getHeight());
+ invalidateOutline();
}
private void updateBackgroundColor(int color) {
mBackgroundColor = color;
- mColorDrawable.setColor(color);
- mTextAndBackground.setBackground(mColorDrawable);
+ mBackground.setColor(color);
if (mNotificationInfo != null) {
mIconView.setBackground(mNotificationInfo.getIconForBackground(getContext(),
mBackgroundColor));
@@ -128,8 +185,11 @@
/**
* Sets the content of this view, animating it after a new icon shifts up if necessary.
*/
- public void applyNotificationInfo(NotificationInfo mainNotification, boolean animate) {
- mNotificationInfo = mainNotification;
+ public void applyNotificationInfo(NotificationInfo notificationInfo) {
+ mNotificationInfo = notificationInfo;
+ if (notificationInfo == null) {
+ return;
+ }
NotificationListener listener = NotificationListener.getInstanceIfConnected();
if (listener != null) {
listener.setNotificationsShown(new String[] {mNotificationInfo.notificationKey});
@@ -149,25 +209,112 @@
if (mNotificationInfo.intent != null) {
setOnClickListener(mNotificationInfo);
}
- setContentTranslation(0);
+
// Add a stub ItemInfo so that logging populates the correct container and item types
// instead of DEFAULT_CONTAINERTYPE and DEFAULT_ITEMTYPE, respectively.
setTag(NOTIFICATION_ITEM_INFO);
- if (animate) {
- ObjectAnimator.ofFloat(mTextAndBackground, ALPHA, 0, 1).setDuration(150).start();
+ }
+
+ /**
+ * Sets the alpha of only the child views.
+ */
+ public void setContentAlpha(float alpha) {
+ mHeader.setAlpha(alpha);
+ mMainView.setAlpha(alpha);
+ }
+
+ /**
+ * Sets the translation of only the child views.
+ */
+ public void setContentTranslationX(float transX) {
+ mHeader.setTranslationX(transX);
+ mMainView.setTranslationX(transX);
+ }
+
+ /**
+ * Updates the alpha, content alpha, and elevation of this view.
+ *
+ * @param progress Range from [0, 1] or [-1, 0]
+ * When 0: Full alpha
+ * When 1/-1: zero alpha
+ */
+ public void onPrimaryDrag(float progress) {
+ float absProgress = Math.abs(progress);
+ final int width = getWidth();
+
+ float min = PRIMARY_MIN_PROGRESS;
+ float max = PRIMARY_MAX_PROGRESS;
+
+ if (absProgress < min) {
+ setAlpha(1f);
+ setContentAlpha(1);
+ setElevation(mMaxElevation);
+ } else if (absProgress < max) {
+ setAlpha(1f);
+ setContentAlpha(mapToRange(absProgress, min, max, 1f, 0f, LINEAR));
+ setElevation(Utilities.mapToRange(absProgress, min, max, mMaxElevation, 0, LINEAR));
+ } else {
+ setAlpha(mapToRange(absProgress, max, PRIMARY_GONE_PROGRESS, 1f, 0f, LINEAR));
+ setContentAlpha(0f);
+ setElevation(0f);
}
+
+ setTranslationX(width * progress);
}
- public void setContentTranslation(float translation) {
- mTextAndBackground.setTranslationX(translation);
- mIconView.setTranslationX(translation);
+ /**
+ * Updates the alpha, content alpha, elevation, and clipping of this view.
+ * @param progress Range from [0, 1] or [-1, 0]
+ * When 0: Smallest clipping, zero alpha
+ * When 1/-1: Full clip, full alpha
+ */
+ public void onSecondaryDrag(float progress) {
+ final float absProgress = Math.abs(progress);
+
+ float min = SECONDARY_MIN_PROGRESS;
+ float max = SECONDARY_MAX_PROGRESS;
+ float contentMax = SECONDARY_CONTENT_MAX_PROGRESS;
+
+ if (absProgress < min) {
+ setAlpha(0f);
+ setContentAlpha(0);
+ setElevation(0f);
+ } else if (absProgress < max) {
+ setAlpha(mapToRange(absProgress, min, max, 0, 1f, LINEAR));
+ setContentAlpha(0f);
+ setElevation(0f);
+ } else {
+ setAlpha(1f);
+ setContentAlpha(absProgress > contentMax
+ ? 1f
+ : mapToRange(absProgress, max, contentMax, 0, 1f, LINEAR));
+ setElevation(Utilities.mapToRange(absProgress, max, 1, 0, mMaxElevation, LINEAR));
+ }
+
+ final int width = getWidth();
+ int crop = (int) (width * absProgress);
+ int space = (int) (absProgress > PRIMARY_GONE_PROGRESS
+ ? mapToRange(absProgress, PRIMARY_GONE_PROGRESS, 1f, mNotificationSpace, 0, LINEAR)
+ : mNotificationSpace);
+ if (progress < 0) {
+ mOutline.left = Math.max(0, getWidth() - crop + space);
+ mOutline.right = getWidth();
+ } else {
+ mOutline.right = Math.min(getWidth(), crop - space);
+ mOutline.left = 0;
+ }
+
+ float contentTransX = mMaxTransX * (1f - absProgress);
+ setContentTranslationX(progress < 0
+ ? contentTransX
+ : -contentTransX);
+ invalidateOutline();
}
- public NotificationInfo getNotificationInfo() {
+ public @Nullable NotificationInfo getNotificationInfo() {
return mNotificationInfo;
}
-
public boolean canChildBeDismissed() {
return mNotificationInfo != null && mNotificationInfo.dismissable;
}
diff --git a/src/com/android/launcher3/popup/ArrowPopup.java b/src/com/android/launcher3/popup/ArrowPopup.java
index 3bb49f5..112a24e 100644
--- a/src/com/android/launcher3/popup/ArrowPopup.java
+++ b/src/com/android/launcher3/popup/ArrowPopup.java
@@ -467,6 +467,13 @@
return getMeasuredWidth() - mArrowOffsetHorizontal - mArrowWidth;
}
+ /**
+ * @param show If true, shows arrow (when applicable), otherwise hides arrow.
+ */
+ public void showArrow(boolean show) {
+ mArrow.setVisibility(show && shouldAddArrow() ? VISIBLE : INVISIBLE);
+ }
+
private void addArrow() {
getPopupContainer().addView(mArrow);
mArrow.setX(getX() + getArrowLeft());
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index 18f263a..bc3419a 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -58,8 +58,8 @@
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.notification.NotificationContainer;
import com.android.launcher3.notification.NotificationInfo;
-import com.android.launcher3.notification.NotificationItemView;
import com.android.launcher3.notification.NotificationKeyData;
import com.android.launcher3.popup.PopupDataProvider.PopupDataChangeListener;
import com.android.launcher3.shortcuts.DeepShortcutView;
@@ -92,9 +92,8 @@
private final int mStartDragThreshold;
private BubbleTextView mOriginalIcon;
- private NotificationItemView mNotificationItemView;
private int mNumNotifications;
- private ViewGroup mNotificationContainer;
+ private NotificationContainer mNotificationContainer;
private ViewGroup mWidgetContainer;
@@ -128,8 +127,8 @@
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
mInterceptTouchDown.set(ev.getX(), ev.getY());
}
- if (mNotificationItemView != null
- && mNotificationItemView.onInterceptTouchEvent(ev)) {
+ if (mNotificationContainer != null
+ && mNotificationContainer.onInterceptSwipeEvent(ev)) {
return true;
}
// Stop sending touch events to deep shortcut views if user moved beyond touch slop.
@@ -138,6 +137,14 @@
}
@Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ if (mNotificationContainer != null) {
+ return mNotificationContainer.onSwipeEvent(ev) || super.onTouchEvent(ev);
+ }
+ return super.onTouchEvent(ev);
+ }
+
+ @Override
protected boolean isOfType(int type) {
return (type & TYPE_ACTION_POPUP) != 0;
}
@@ -172,8 +179,8 @@
@Override
protected void setChildColor(View view, int color, AnimatorSet animatorSetOut) {
super.setChildColor(view, color, animatorSetOut);
- if (view.getId() == R.id.notification_container && mNotificationItemView != null) {
- mNotificationItemView.updateBackgroundColor(color, animatorSetOut);
+ if (view.getId() == R.id.notification_container && mNotificationContainer != null) {
+ mNotificationContainer.updateBackgroundColor(color, animatorSetOut);
}
}
@@ -232,13 +239,6 @@
mNotificationContainer);
}
- @Override
- protected void onInflationComplete(boolean isReversed) {
- if (isReversed && mNotificationItemView != null) {
- mNotificationItemView.inverseGutterMargin();
- }
- }
-
@TargetApi(Build.VERSION_CODES.P)
public void populateAndShow(final BubbleTextView originalIcon, int shortcutCount,
final List<NotificationKeyData> notificationKeys, List<SystemShortcut> systemShortcuts) {
@@ -261,9 +261,10 @@
if (mNotificationContainer == null) {
mNotificationContainer = findViewById(R.id.notification_container);
mNotificationContainer.setVisibility(VISIBLE);
+ mNotificationContainer.setPopupView(this);
+ } else {
+ mNotificationContainer.setVisibility(GONE);
}
- View.inflate(getContext(), R.layout.notification_content, mNotificationContainer);
- mNotificationItemView = new NotificationItemView(this, mNotificationContainer);
updateNotificationHeader();
}
int viewsToFlip = getChildCount();
@@ -274,10 +275,6 @@
if (hasDeepShortcuts) {
mDeepShortcutContainer.setVisibility(View.VISIBLE);
- if (mNotificationItemView != null) {
- mNotificationItemView.addGutter();
- }
-
for (int i = shortcutCount; i > 0; i--) {
DeepShortcutView v = inflateAndAdd(R.layout.deep_shortcut, mDeepShortcutContainer);
v.getLayoutParams().width = containerWidth;
@@ -309,10 +306,6 @@
} else {
mDeepShortcutContainer.setVisibility(View.GONE);
if (!systemShortcuts.isEmpty()) {
- if (mNotificationItemView != null) {
- mNotificationItemView.addGutter();
- }
-
for (SystemShortcut shortcut : systemShortcuts) {
initializeSystemShortcut(R.layout.system_shortcut, this, shortcut);
}
@@ -355,13 +348,13 @@
}
public void applyNotificationInfos(List<NotificationInfo> notificationInfos) {
- if (mNotificationItemView != null) {
- mNotificationItemView.applyNotificationInfos(notificationInfos);
+ if (mNotificationContainer != null) {
+ mNotificationContainer.applyNotificationInfos(notificationInfos);
}
}
private void updateHiddenShortcuts() {
- int allowedCount = mNotificationItemView != null
+ int allowedCount = mNotificationContainer != null
? MAX_SHORTCUTS_IF_NOTIFICATIONS : MAX_SHORTCUTS;
int total = mShortcuts.size();
@@ -447,8 +440,8 @@
private void updateNotificationHeader() {
ItemInfoWithIcon itemInfo = (ItemInfoWithIcon) mOriginalIcon.getTag();
DotInfo dotInfo = mLauncher.getDotInfoForItem(itemInfo);
- if (mNotificationItemView != null && dotInfo != null) {
- mNotificationItemView.updateHeader(dotInfo.getNotificationCount());
+ if (mNotificationContainer != null && dotInfo != null) {
+ mNotificationContainer.updateHeader(dotInfo.getNotificationCount());
}
}
@@ -590,20 +583,18 @@
@Override
public void trimNotifications(Map<PackageUserKey, DotInfo> updatedDots) {
- if (mNotificationItemView == null) {
+ if (mNotificationContainer == null) {
return;
}
ItemInfo originalInfo = (ItemInfo) mOriginalIcon.getTag();
DotInfo dotInfo = updatedDots.get(PackageUserKey.fromItemInfo(originalInfo));
if (dotInfo == null || dotInfo.getNotificationKeys().size() == 0) {
// No more notifications, remove the notification views and expand all shortcuts.
- mNotificationItemView.removeAllViews();
- mNotificationItemView = null;
mNotificationContainer.setVisibility(GONE);
updateHiddenShortcuts();
assignMarginsAndBackgrounds(PopupContainerWithArrow.this);
} else {
- mNotificationItemView.trimNotifications(
+ mNotificationContainer.trimNotifications(
NotificationKeyData.extractKeysOnly(dotInfo.getNotificationKeys()));
}
}
diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java
index 42a38b6..ed52e20 100644
--- a/src/com/android/launcher3/testing/TestProtocol.java
+++ b/src/com/android/launcher3/testing/TestProtocol.java
@@ -119,4 +119,5 @@
public static final String FALLBACK_ACTIVITY_NO_SET = "b/181019015";
public static final String TASK_VIEW_ID_CRASH = "b/195430732";
public static final String L3_SWIPE_TO_HOME = "b/192018189";
+ public static final String NO_DROP_TARGET = "b/195031154";
}
diff --git a/src/com/android/launcher3/widget/BaseWidgetSheet.java b/src/com/android/launcher3/widget/BaseWidgetSheet.java
index b6cec12..00a0050 100644
--- a/src/com/android/launcher3/widget/BaseWidgetSheet.java
+++ b/src/com/android/launcher3/widget/BaseWidgetSheet.java
@@ -19,6 +19,7 @@
import android.graphics.Point;
import android.graphics.Rect;
import android.util.AttributeSet;
+import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener;
@@ -108,6 +109,9 @@
@Override
public boolean onLongClick(View v) {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.NO_DROP_TARGET, "1");
+ }
TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "Widgets.onLongClick");
v.cancelLongPress();
if (!ItemLongClickListener.canStartDrag(mActivityContext)) return false;
@@ -178,6 +182,9 @@
}
private boolean beginDraggingWidget(WidgetCell v) {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.NO_DROP_TARGET, "2");
+ }
// Get the widget preview as the drag representation
WidgetImageView image = v.getWidgetView();
diff --git a/src/com/android/launcher3/widget/PendingItemDragHelper.java b/src/com/android/launcher3/widget/PendingItemDragHelper.java
index 2347d28..463f4ac 100644
--- a/src/com/android/launcher3/widget/PendingItemDragHelper.java
+++ b/src/com/android/launcher3/widget/PendingItemDragHelper.java
@@ -22,6 +22,7 @@
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.util.Log;
import android.util.Size;
import android.view.View;
import android.view.View.MeasureSpec;
@@ -41,6 +42,7 @@
import com.android.launcher3.icons.FastBitmapDrawable;
import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.icons.RoundDrawableWrapper;
+import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.widget.dragndrop.AppWidgetHostViewDragListener;
import com.android.launcher3.widget.util.WidgetSizes;
@@ -94,6 +96,9 @@
*/
public void startDrag(Rect previewBounds, int previewBitmapWidth, int previewViewWidth,
Point screenPos, DragSource source, DragOptions options) {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.NO_DROP_TARGET, "3");
+ }
final Launcher launcher = Launcher.getLauncher(mView.getContext());
LauncherAppState app = LauncherAppState.getInstance(launcher);