diff --git a/Android.bp b/Android.bp index 5acec37583..b80282eb54 100644 --- a/Android.bp +++ b/Android.bp @@ -24,6 +24,7 @@ android_library { srcs: [ "tests/tapl/**/*.java", "src/com/android/launcher3/util/SecureSettingsObserver.java", + "src/com/android/launcher3/ResourceUtils.java", "src/com/android/launcher3/TestProtocol.java", ], manifest: "tests/tapl/AndroidManifest.xml", diff --git a/go/quickstep/res/values-sw480dp/dimens.xml b/go/quickstep/res/values-sw480dp/dimens.xml index b48dafbafb..571b8a1b84 100644 --- a/go/quickstep/res/values-sw480dp/dimens.xml +++ b/go/quickstep/res/values-sw480dp/dimens.xml @@ -18,13 +18,13 @@ 480dp 90dp - 16dp - 20dp + 24dp + 24dp 4dp - 48dp + 52dp 28dp 28dp - 140dp + 160dp \ No newline at end of file diff --git a/go/quickstep/src/com/android/launcher3/GoLauncherAppTransitionManagerImpl.java b/go/quickstep/src/com/android/launcher3/GoLauncherAppTransitionManagerImpl.java index d189c504cd..bcb1f5c3b4 100644 --- a/go/quickstep/src/com/android/launcher3/GoLauncherAppTransitionManagerImpl.java +++ b/go/quickstep/src/com/android/launcher3/GoLauncherAppTransitionManagerImpl.java @@ -1,16 +1,20 @@ package com.android.launcher3; +import static com.android.launcher3.Utilities.postAsyncCallback; import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE; import static com.android.launcher3.anim.Interpolators.LINEAR; +import static com.android.quickstep.TaskUtils.taskIsATargetWithMode; import static com.android.quickstep.views.IconRecentsView.CONTENT_ALPHA; +import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; -import android.app.ActivityOptions; import android.content.Context; +import android.os.Handler; import android.view.View; import com.android.quickstep.views.IconRecentsView; +import com.android.systemui.shared.system.RemoteAnimationRunnerCompat; import com.android.systemui.shared.system.RemoteAnimationTargetCompat; /** @@ -28,6 +32,12 @@ public final class GoLauncherAppTransitionManagerImpl extends QuickstepAppTransi return mLauncher.getStateManager().getState().overviewUi; } + @Override + RemoteAnimationRunnerCompat getWallpaperOpenRunner(boolean fromUnlock) { + return new GoWallpaperOpenLauncherAnimationRunner(mHandler, + false /* startAtFrontOfQueue */, fromUnlock); + } + @Override protected void composeRecentsLaunchAnimator(AnimatorSet anim, View v, RemoteAnimationTargetCompat[] targets, boolean launcherClosing) { @@ -51,4 +61,34 @@ public final class GoLauncherAppTransitionManagerImpl extends QuickstepAppTransi return mLauncher.getStateManager()::reapplyState; } + + /** + * Remote animation runner for animation from app to Launcher. For Go, when going to recents, + * we need to ensure that the recents view is ready for remote animation before starting. + */ + private final class GoWallpaperOpenLauncherAnimationRunner extends + WallpaperOpenLauncherAnimationRunner { + public GoWallpaperOpenLauncherAnimationRunner(Handler handler, boolean startAtFrontOfQueue, + boolean fromUnlock) { + super(handler, startAtFrontOfQueue, fromUnlock); + } + + @Override + public void onCreateAnimation(RemoteAnimationTargetCompat[] targetCompats, + AnimationResult result) { + boolean isGoingToRecents = + taskIsATargetWithMode(targetCompats, mLauncher.getTaskId(), MODE_OPENING) + && (mLauncher.getStateManager().getState() == LauncherState.OVERVIEW); + if (isGoingToRecents) { + IconRecentsView recentsView = mLauncher.getOverviewPanel(); + if (!recentsView.isReadyForRemoteAnim()) { + recentsView.setOnReadyForRemoteAnimCallback(() -> + postAsyncCallback(mHandler, () -> onCreateAnimation(targetCompats, result)) + ); + return; + } + } + super.onCreateAnimation(targetCompats, result); + } + } } diff --git a/go/quickstep/src/com/android/launcher3/uioverrides/RecentsUiFactory.java b/go/quickstep/src/com/android/launcher3/uioverrides/RecentsUiFactory.java index d9b9686030..b5fefb46d6 100644 --- a/go/quickstep/src/com/android/launcher3/uioverrides/RecentsUiFactory.java +++ b/go/quickstep/src/com/android/launcher3/uioverrides/RecentsUiFactory.java @@ -21,10 +21,12 @@ import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; import android.view.View; +import com.android.launcher3.DeviceProfile; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherStateManager.StateHandler; import com.android.launcher3.Utilities; import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.graphics.RotationMode; import com.android.launcher3.uioverrides.touchcontrollers.LandscapeEdgeSwipeController; import com.android.launcher3.uioverrides.touchcontrollers.LandscapeStatesTouchController; import com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController; @@ -102,4 +104,8 @@ public abstract class RecentsUiFactory { * @param launcher the launcher activity */ public static void onLauncherStateOrResumeChanged(Launcher launcher) {} + + public static RotationMode getRotationMode(DeviceProfile dp) { + return RotationMode.NORMAL; + } } diff --git a/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java b/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java index c228bb94fe..fe159b5f02 100644 --- a/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java +++ b/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java @@ -15,30 +15,27 @@ */ package com.android.quickstep; -import static com.android.launcher3.anim.Interpolators.ACCEL_2; -import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL; +import static com.android.launcher3.Utilities.postAsyncCallback; import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN; -import static com.android.quickstep.util.RemoteAnimationProvider.getLayer; +import static com.android.quickstep.views.IconRecentsView.REMOTE_APP_TO_OVERVIEW_DURATION; +import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME; import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING; import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING; import android.animation.AnimatorSet; import android.animation.ValueAnimator; -import android.graphics.Matrix; -import android.graphics.Rect; +import android.app.ActivityOptions; +import android.os.Handler; import android.util.Log; -import android.view.View; - -import androidx.annotation.NonNull; import com.android.launcher3.BaseDraggingActivity; -import com.android.quickstep.util.MultiValueUpdateListener; +import com.android.launcher3.LauncherAnimationRunner; import com.android.quickstep.util.RemoteAnimationProvider; import com.android.quickstep.util.RemoteAnimationTargetSet; import com.android.quickstep.views.IconRecentsView; +import com.android.systemui.shared.system.ActivityOptionsCompat; +import com.android.systemui.shared.system.RemoteAnimationAdapterCompat; import com.android.systemui.shared.system.RemoteAnimationTargetCompat; -import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat; -import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams; /** * Provider for the atomic remote window animation from the app to the overview. @@ -47,20 +44,27 @@ import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat. */ final class AppToOverviewAnimationProvider implements RemoteAnimationProvider { - - private static final long APP_TO_THUMBNAIL_FADE_DURATION = 50; - private static final long APP_SCALE_DOWN_DURATION = 400; private static final String TAG = "AppToOverviewAnimationProvider"; private final ActivityControlHelper mHelper; private final int mTargetTaskId; private IconRecentsView mRecentsView; + private AppToOverviewAnimationListener mAnimationReadyListener; AppToOverviewAnimationProvider(ActivityControlHelper helper, int targetTaskId) { mHelper = helper; mTargetTaskId = targetTaskId; } + /** + * Set listener to various points in the animation preparing to animate. + * + * @param listener listener + */ + void setAnimationListener(AppToOverviewAnimationListener listener) { + mAnimationReadyListener = listener; + } + /** * Callback for when the activity is ready/initialized. * @@ -68,6 +72,9 @@ final class AppToOverviewAnimationProvider imple * @param wasVisible true if it was visible before */ boolean onActivityReady(T activity, Boolean wasVisible) { + if (mAnimationReadyListener != null) { + mAnimationReadyListener.onActivityReady(activity); + } ActivityControlHelper.AnimationFactory factory = mHelper.prepareRecentsUI(activity, wasVisible, false /* animate activity */, (controller) -> { @@ -92,6 +99,9 @@ final class AppToOverviewAnimationProvider imple */ @Override public AnimatorSet createWindowAnimation(RemoteAnimationTargetCompat[] targetCompats) { + if (mAnimationReadyListener != null) { + mAnimationReadyListener.onWindowAnimationCreated(); + } AnimatorSet anim = new AnimatorSet(); if (mRecentsView == null) { if (Log.isLoggable(TAG, Log.WARN)) { @@ -131,98 +141,36 @@ final class AppToOverviewAnimationProvider imple return anim; } - View thumbnailView = mRecentsView.getBottomThumbnailView(); - if (thumbnailView == null) { - // This can be null if there were previously 0 tasks and the recycler view has not had - // enough time to take in the data change, bind a new view, and lay out the new view. - // TODO: Have a fallback to animate to - if (Log.isLoggable(TAG, Log.WARN)) { - Log.w(TAG, "No thumbnail view for running task. Using stub animation."); - } - anim.play(ValueAnimator.ofInt(0, 1).setDuration(getRecentsLaunchDuration())); - return anim; + if (closingAppTarget.activityType == ACTIVITY_TYPE_HOME) { + mRecentsView.playRemoteHomeToRecentsAnimation(anim, closingAppTarget, recentsTarget); + } else { + mRecentsView.playRemoteAppToRecentsAnimation(anim, closingAppTarget, recentsTarget); } - playAppScaleDownAnim(anim, closingAppTarget, recentsTarget, thumbnailView); - return anim; } - /** - * Animate a closing app to scale down to the location of the thumbnail view in recents. - * - * @param anim animator set - * @param appTarget the app surface thats closing - * @param recentsTarget the surface containing recents - * @param thumbnailView the thumbnail view to animate to - */ - private void playAppScaleDownAnim(@NonNull AnimatorSet anim, - @NonNull RemoteAnimationTargetCompat appTarget, - @NonNull RemoteAnimationTargetCompat recentsTarget, @NonNull View thumbnailView) { - - // Identify where the entering remote app should animate to. - Rect endRect = new Rect(); - thumbnailView.getGlobalVisibleRect(endRect); - - Rect appBounds = appTarget.sourceContainerBounds; - - ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 1); - valueAnimator.setDuration(APP_SCALE_DOWN_DURATION); - - SyncRtSurfaceTransactionApplierCompat surfaceApplier = - new SyncRtSurfaceTransactionApplierCompat(thumbnailView); - - // Keep recents visible throughout the animation. - SurfaceParams[] params = new SurfaceParams[2]; - // Closing app should stay on top. - int boostedMode = MODE_CLOSING; - params[0] = new SurfaceParams(recentsTarget.leash, 1f, null /* matrix */, - null /* windowCrop */, getLayer(recentsTarget, boostedMode), 0 /* cornerRadius */); - - valueAnimator.addUpdateListener(new MultiValueUpdateListener() { - private final FloatProp mScaleX; - private final FloatProp mScaleY; - private final FloatProp mTranslationX; - private final FloatProp mTranslationY; - private final FloatProp mAlpha; - - { - // Scale down and move to view location. - float endScaleX = ((float) endRect.width()) / appBounds.width(); - mScaleX = new FloatProp(1f, endScaleX, 0, APP_SCALE_DOWN_DURATION, - ACCEL_DEACCEL); - float endScaleY = ((float) endRect.height()) / appBounds.height(); - mScaleY = new FloatProp(1f, endScaleY, 0, APP_SCALE_DOWN_DURATION, - ACCEL_DEACCEL); - float endTranslationX = endRect.left - - (appBounds.width() - thumbnailView.getWidth()) / 2.0f; - mTranslationX = new FloatProp(0, endTranslationX, 0, APP_SCALE_DOWN_DURATION, - ACCEL_DEACCEL); - float endTranslationY = endRect.top - - (appBounds.height() - thumbnailView.getHeight()) / 2.0f; - mTranslationY = new FloatProp(0, endTranslationY, 0, APP_SCALE_DOWN_DURATION, - ACCEL_DEACCEL); - - // Fade out quietly near the end to be replaced by the real view. - mAlpha = new FloatProp(1.0f, 0, - APP_SCALE_DOWN_DURATION - APP_TO_THUMBNAIL_FADE_DURATION, - APP_TO_THUMBNAIL_FADE_DURATION, ACCEL_2); - } + @Override + public ActivityOptions toActivityOptions(Handler handler, long duration) { + LauncherAnimationRunner runner = new LauncherAnimationRunner(handler, + false /* startAtFrontOfQueue */) { @Override - public void onUpdate(float percent) { - Matrix m = new Matrix(); - m.setScale(mScaleX.value, mScaleY.value, - appBounds.width() / 2.0f, appBounds.height() / 2.0f); - m.postTranslate(mTranslationX.value, mTranslationY.value); - - params[1] = new SurfaceParams(appTarget.leash, mAlpha.value, m, - null /* windowCrop */, getLayer(appTarget, boostedMode), - 0 /* cornerRadius */); - surfaceApplier.scheduleApply(params); + public void onCreateAnimation(RemoteAnimationTargetCompat[] targetCompats, + AnimationResult result) { + IconRecentsView recentsView = mRecentsView; + if (!recentsView.isReadyForRemoteAnim()) { + recentsView.setOnReadyForRemoteAnimCallback(() -> postAsyncCallback(handler, + () -> onCreateAnimation(targetCompats, result)) + ); + return; + } + result.setAnimation(createWindowAnimation(targetCompats)); } - }); - anim.play(valueAnimator); + }; + return ActivityOptionsCompat.makeRemoteAnimation( + new RemoteAnimationAdapterCompat(runner, duration, + 0 /* statusBarTransitionDelay */)); } /** @@ -231,6 +179,23 @@ final class AppToOverviewAnimationProvider imple * @return duration of animation */ long getRecentsLaunchDuration() { - return APP_SCALE_DOWN_DURATION; + return REMOTE_APP_TO_OVERVIEW_DURATION; + } + + /** + * Listener for various points in the app to overview animation preparing to animate. + */ + interface AppToOverviewAnimationListener { + /** + * Logic for when activity we're animating to is ready + * + * @param activity activity to animate to + */ + void onActivityReady(BaseDraggingActivity activity); + + /** + * Logic for when we've created the app to recents animation. + */ + void onWindowAnimationCreated(); } } diff --git a/go/quickstep/src/com/android/quickstep/ContentFillItemAnimator.java b/go/quickstep/src/com/android/quickstep/ContentFillItemAnimator.java index 9282345532..808cd7217a 100644 --- a/go/quickstep/src/com/android/quickstep/ContentFillItemAnimator.java +++ b/go/quickstep/src/com/android/quickstep/ContentFillItemAnimator.java @@ -180,6 +180,7 @@ public final class ContentFillItemAnimator extends SimpleItemAnimator { @Override public void onAnimationEnd(Animator animation) { + CONTENT_TRANSITION_PROGRESS.set(itemView, 1.0f); dispatchChangeFinished(viewHolder, true /* oldItem */); mRunningAnims.remove(anim); dispatchFinishedWhenDone(); @@ -215,46 +216,43 @@ public final class ContentFillItemAnimator extends SimpleItemAnimator { @Override public void endAnimation(@NonNull ViewHolder item) { for (int i = mPendingAnims.size() - 1; i >= 0; i--) { - PendingAnimation pendAnim = mPendingAnims.get(i); - if (pendAnim.viewHolder == item) { - mPendingAnims.remove(i); - switch (pendAnim.animType) { - case ANIM_TYPE_REMOVE: - dispatchRemoveFinished(item); - break; - case ANIM_TYPE_CHANGE: - dispatchChangeFinished(item, true /* oldItem */); - break; - default: - break; - } - } + endPendingAnimation(mPendingAnims.get(i)); + mPendingAnims.remove(i); } dispatchFinishedWhenDone(); } @Override public void endAnimations() { + if (!isRunning()) { + return; + } for (int i = mPendingAnims.size() - 1; i >= 0; i--) { - PendingAnimation pendAnim = mPendingAnims.get(i); - ViewHolder item = pendAnim.viewHolder; - switch (pendAnim.animType) { - case ANIM_TYPE_REMOVE: - dispatchRemoveFinished(item); - break; - case ANIM_TYPE_CHANGE: - dispatchChangeFinished(item, true /* oldItem */); - break; - default: - break; - } + endPendingAnimation(mPendingAnims.get(i)); mPendingAnims.remove(i); } - for (int i = 0; i < mRunningAnims.size(); i++) { + for (int i = mRunningAnims.size() - 1; i >= 0; i--) { ObjectAnimator anim = mRunningAnims.get(i); - anim.end(); + // This calls the on end animation callback which will set values to their end target. + anim.cancel(); + } + dispatchFinishedWhenDone(); + } + + private void endPendingAnimation(PendingAnimation pendAnim) { + ViewHolder item = pendAnim.viewHolder; + switch (pendAnim.animType) { + case ANIM_TYPE_REMOVE: + item.itemView.setAlpha(1.0f); + dispatchRemoveFinished(item); + break; + case ANIM_TYPE_CHANGE: + CONTENT_TRANSITION_PROGRESS.set(item.itemView, 1.0f); + dispatchChangeFinished(item, true /* oldItem */); + break; + default: + break; } - dispatchAnimationsFinished(); } @Override diff --git a/go/quickstep/src/com/android/quickstep/FallbackActivityControllerHelper.java b/go/quickstep/src/com/android/quickstep/FallbackActivityControllerHelper.java index bba08a40af..eb0c5b942f 100644 --- a/go/quickstep/src/com/android/quickstep/FallbackActivityControllerHelper.java +++ b/go/quickstep/src/com/android/quickstep/FallbackActivityControllerHelper.java @@ -50,6 +50,7 @@ public final class FallbackActivityControllerHelper extends } IconRecentsView rv = activity.getOverviewPanel(); + rv.setUsingRemoteAnimation(true); rv.setAlpha(0); return new AnimationFactory() { diff --git a/go/quickstep/src/com/android/quickstep/LauncherActivityControllerHelper.java b/go/quickstep/src/com/android/quickstep/LauncherActivityControllerHelper.java index 40db7ddbce..d5950077de 100644 --- a/go/quickstep/src/com/android/quickstep/LauncherActivityControllerHelper.java +++ b/go/quickstep/src/com/android/quickstep/LauncherActivityControllerHelper.java @@ -40,6 +40,7 @@ public final class LauncherActivityControllerHelper extends GoActivityControlHel boolean activityVisible, boolean animateActivity, Consumer callback) { LauncherState fromState = activity.getStateManager().getState(); + activity.getOverviewPanel().setUsingRemoteAnimation(true); //TODO: Implement this based off where the recents view needs to be for app => recents anim. return new AnimationFactory() { @Override @@ -87,6 +88,7 @@ public final class LauncherActivityControllerHelper extends GoActivityControlHel if (launcher == null) { return false; } + launcher.getOverviewPanel().setUsingRemoteAnimation(false); launcher.getUserEventDispatcher().logActionCommand( LauncherLogProto.Action.Command.RECENTS_BUTTON, getContainerType(), diff --git a/go/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/go/quickstep/src/com/android/quickstep/OverviewCommandHelper.java index 379cc100e8..0fa3d866f6 100644 --- a/go/quickstep/src/com/android/quickstep/OverviewCommandHelper.java +++ b/go/quickstep/src/com/android/quickstep/OverviewCommandHelper.java @@ -18,7 +18,6 @@ package com.android.quickstep; import static com.android.systemui.shared.system.ActivityManagerWrapper .CLOSE_SYSTEM_WINDOWS_REASON_RECENTS; -import android.animation.AnimatorSet; import android.annotation.TargetApi; import android.content.Context; import android.os.Build; @@ -30,10 +29,10 @@ import com.android.launcher3.MainThreadExecutor; import com.android.launcher3.logging.UserEventDispatcher; import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.quickstep.ActivityControlHelper.ActivityInitListener; +import com.android.quickstep.AppToOverviewAnimationProvider.AppToOverviewAnimationListener; import com.android.quickstep.views.IconRecentsView; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.LatencyTrackerCompat; -import com.android.systemui.shared.system.RemoteAnimationTargetCompat; /** * Helper class to handle various atomic commands for switching between Overview. @@ -105,7 +104,6 @@ public class OverviewCommandHelper { protected final ActivityControlHelper mHelper; private final long mCreateTime; - private final AppToOverviewAnimationProvider mAnimationProvider; private final long mToggleClickedTime = SystemClock.uptimeMillis(); private boolean mUserEventLogged; @@ -114,8 +112,6 @@ public class OverviewCommandHelper { public RecentsActivityCommand() { mHelper = mOverviewComponentObserver.getActivityControlHelper(); mCreateTime = SystemClock.elapsedRealtime(); - mAnimationProvider = - new AppToOverviewAnimationProvider<>(mHelper, RecentsModel.getRunningTaskId()); // Preload the plan mRecentsModel.getTasks(null); @@ -136,11 +132,37 @@ public class OverviewCommandHelper { return; } + AppToOverviewAnimationProvider provider = + new AppToOverviewAnimationProvider<>(mHelper, RecentsModel.getRunningTaskId()); + provider.setAnimationListener( + new AppToOverviewAnimationListener() { + @Override + public void onActivityReady(BaseDraggingActivity activity) { + if (!mUserEventLogged) { + activity.getUserEventDispatcher().logActionCommand( + LauncherLogProto.Action.Command.RECENTS_BUTTON, + mHelper.getContainerType(), + LauncherLogProto.ContainerType.TASKSWITCHER); + mUserEventLogged = true; + } + } + + @Override + public void onWindowAnimationCreated() { + if (LatencyTrackerCompat.isEnabled(mContext)) { + LatencyTrackerCompat.logToggleRecents( + (int) (SystemClock.uptimeMillis() - mToggleClickedTime)); + } + + mListener.unregister(); + } + }); + // Otherwise, start overview. - mListener = mHelper.createActivityInitListener(this::onActivityReady); + mListener = mHelper.createActivityInitListener(provider::onActivityReady); mListener.registerAndStartActivity(mOverviewComponentObserver.getOverviewIntent(), - this::createWindowAnimation, mContext, mMainThreadExecutor.getHandler(), - mAnimationProvider.getRecentsLaunchDuration()); + provider, mContext, mMainThreadExecutor.getHandler(), + provider.getRecentsLaunchDuration()); } protected boolean handleCommand(long elapsedTime) { @@ -155,27 +177,5 @@ public class OverviewCommandHelper { } return false; } - - private boolean onActivityReady(T activity, Boolean wasVisible) { - if (!mUserEventLogged) { - activity.getUserEventDispatcher().logActionCommand( - LauncherLogProto.Action.Command.RECENTS_BUTTON, - mHelper.getContainerType(), - LauncherLogProto.ContainerType.TASKSWITCHER); - mUserEventLogged = true; - } - return mAnimationProvider.onActivityReady(activity, wasVisible); - } - - private AnimatorSet createWindowAnimation(RemoteAnimationTargetCompat[] targetCompats) { - if (LatencyTrackerCompat.isEnabled(mContext)) { - LatencyTrackerCompat.logToggleRecents( - (int) (SystemClock.uptimeMillis() - mToggleClickedTime)); - } - - mListener.unregister(); - - return mAnimationProvider.createWindowAnimation(targetCompats); - } } } diff --git a/go/quickstep/src/com/android/quickstep/RecentsActivity.java b/go/quickstep/src/com/android/quickstep/RecentsActivity.java index 9fb80679e3..7f813ce2aa 100644 --- a/go/quickstep/src/com/android/quickstep/RecentsActivity.java +++ b/go/quickstep/src/com/android/quickstep/RecentsActivity.java @@ -42,7 +42,7 @@ public final class RecentsActivity extends BaseRecentsActivity { @Override protected void reapplyUi() { - //TODO: Implement this depending on how insets will affect the view. + // No-op. Insets are automatically re-applied in the root view. } @Override @@ -67,8 +67,8 @@ public final class RecentsActivity extends BaseRecentsActivity { } @Override - protected void onStart() { + protected void onResume() { mIconRecentsView.onBeginTransitionToOverview(); - super.onStart(); + super.onResume(); } } diff --git a/go/quickstep/src/com/android/quickstep/TaskActionController.java b/go/quickstep/src/com/android/quickstep/TaskActionController.java index 0e921c0db1..f49fa3ef4f 100644 --- a/go/quickstep/src/com/android/quickstep/TaskActionController.java +++ b/go/quickstep/src/com/android/quickstep/TaskActionController.java @@ -16,14 +16,17 @@ package com.android.quickstep; import static com.android.quickstep.TaskAdapter.TASKS_START_POSITION; +import static com.android.quickstep.TaskUtils.getLaunchComponentKeyForTask; import android.app.ActivityOptions; import android.view.View; import androidx.annotation.NonNull; +import com.android.launcher3.logging.StatsLogManager; import com.android.quickstep.views.TaskItemView; import com.android.systemui.shared.recents.model.Task; +import com.android.systemui.shared.recents.model.Task.TaskKey; import com.android.systemui.shared.system.ActivityManagerWrapper; /** @@ -34,10 +37,13 @@ public final class TaskActionController { private final TaskListLoader mLoader; private final TaskAdapter mAdapter; + private final StatsLogManager mStatsLogManager; - public TaskActionController(TaskListLoader loader, TaskAdapter adapter) { + public TaskActionController(TaskListLoader loader, TaskAdapter adapter, + StatsLogManager logManager) { mLoader = loader; mAdapter = adapter; + mStatsLogManager = logManager; } /** @@ -56,10 +62,11 @@ public final class TaskActionController { int width = v.getMeasuredWidth(); int height = v.getMeasuredHeight(); + TaskKey key = viewHolder.getTask().get().key; ActivityOptions opts = ActivityOptions.makeClipRevealAnimation(v, left, top, width, height); - ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync( - viewHolder.getTask().get().key, opts, null /* resultCallback */, - null /* resultCallbackHandler */); + ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(key, opts, + null /* resultCallback */, null /* resultCallbackHandler */); + mStatsLogManager.logTaskLaunch(null /* view */, getLaunchComponentKeyForTask(key)); } /** @@ -71,6 +78,7 @@ public final class TaskActionController { ActivityOptions opts = ActivityOptions.makeBasic(); ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(task.key, opts, null /* resultCallback */, null /* resultCallbackHandler */); + mStatsLogManager.logTaskLaunch(null /* view */, getLaunchComponentKeyForTask(task.key)); } /** @@ -87,6 +95,7 @@ public final class TaskActionController { ActivityManagerWrapper.getInstance().removeTask(task.key.id); mLoader.removeTask(task); mAdapter.notifyItemRemoved(position); + // TODO(b/131840601): Add logging point for removal. } /** diff --git a/go/quickstep/src/com/android/quickstep/fallback/GoRecentsActivityRootView.java b/go/quickstep/src/com/android/quickstep/fallback/GoRecentsActivityRootView.java index c0ebcb5a43..b550011f50 100644 --- a/go/quickstep/src/com/android/quickstep/fallback/GoRecentsActivityRootView.java +++ b/go/quickstep/src/com/android/quickstep/fallback/GoRecentsActivityRootView.java @@ -16,7 +16,10 @@ package com.android.quickstep.fallback; import android.content.Context; +import android.graphics.Insets; +import android.graphics.Rect; import android.util.AttributeSet; +import android.view.WindowInsets; import com.android.launcher3.util.TouchController; import com.android.launcher3.views.BaseDragLayer; @@ -30,5 +33,23 @@ public final class GoRecentsActivityRootView extends BaseDragLayer mLayingOutViews = new ArraySet<>(); private Rect mInsets; @@ -154,7 +184,8 @@ public final class IconRecentsView extends FrameLayout implements Insettable { mTaskLoader = new TaskListLoader(mContext); mTaskAdapter = new TaskAdapter(mTaskLoader); mTaskAdapter.setOnClearAllClickListener(view -> animateClearAllTasks()); - mTaskActionController = new TaskActionController(mTaskLoader, mTaskAdapter); + mTaskActionController = new TaskActionController(mTaskLoader, mTaskAdapter, + mActivity.getStatsLogManager()); mTaskAdapter.setActionController(mTaskActionController); mTaskLayoutManager = new LinearLayoutManager(mContext, VERTICAL, true /* reverseLayout */); RecentsModel.INSTANCE.get(context).addThumbnailChangeListener(listener); @@ -204,12 +235,8 @@ public final class IconRecentsView extends FrameLayout implements Insettable { case ITEM_TYPE_CLEAR_ALL: outRect.top = (int) res.getDimension( R.dimen.clear_all_item_view_top_margin); - int desiredBottomMargin = (int) res.getDimension( + outRect.bottom = (int) res.getDimension( R.dimen.clear_all_item_view_bottom_margin); - // Only add bottom margin if insets aren't enough. - if (mInsets.bottom < desiredBottomMargin) { - outRect.bottom = desiredBottomMargin - mInsets.bottom; - } break; case ITEM_TYPE_TASK: int desiredTopMargin = (int) res.getDimension( @@ -270,13 +297,16 @@ public final class IconRecentsView extends FrameLayout implements Insettable { * becomes visible. */ public void onBeginTransitionToOverview() { + mStartedEnterAnimation = false; if (mContext.getResources().getConfiguration().orientation == ORIENTATION_LANDSCAPE) { // Scroll to bottom of task in landscape mode. This is a non-issue in portrait mode as // all tasks should be visible to fill up the screen in portrait mode and the view will // not be scrollable. mTaskLayoutManager.scrollToPositionWithOffset(TASKS_START_POSITION, 0 /* offset */); } - scheduleFadeInLayoutAnimation(); + if (!mUsingRemoteAnimation) { + scheduleFadeInLayoutAnimation(); + } // Load any task changes if (!mTaskLoader.needsToLoad()) { return; @@ -292,16 +322,22 @@ public final class IconRecentsView extends FrameLayout implements Insettable { + "of items to animate to."); } // Possible that task list loads faster than adapter changes propagate to layout so - // only start content fill animation if there aren't any pending adapter changes. - if (!mTaskRecyclerView.hasPendingAdapterUpdates()) { + // only start content fill animation if there aren't any pending adapter changes and + // we've started the on enter layout animation. + boolean needsContentFillAnimation = + !mTaskRecyclerView.hasPendingAdapterUpdates() && mStartedEnterAnimation; + if (needsContentFillAnimation) { // Set item animator for content filling animation. The item animator will switch // back to the default on completion mTaskRecyclerView.setItemAnimator(mLoadingContentItemAnimator); + mTaskAdapter.notifyItemRangeRemoved(TASKS_START_POSITION + numActualItems, + numEmptyItems - numActualItems); + mTaskAdapter.notifyItemRangeChanged(TASKS_START_POSITION, numActualItems, + CHANGE_EVENT_TYPE_EMPTY_TO_CONTENT); + } else { + // Notify change without animating. + mTaskAdapter.notifyDataSetChanged(); } - mTaskAdapter.notifyItemRangeRemoved(TASKS_START_POSITION + numActualItems, - numEmptyItems - numActualItems); - mTaskAdapter.notifyItemRangeChanged(TASKS_START_POSITION, numActualItems, - CHANGE_EVENT_TYPE_EMPTY_TO_CONTENT); }); } @@ -314,6 +350,17 @@ public final class IconRecentsView extends FrameLayout implements Insettable { mTransitionedFromApp = transitionedFromApp; } + /** + * Set whether we're using a custom remote animation. If so, we will not do the default layout + * animation when entering recents and instead wait for the remote app surface to be ready to + * use. + * + * @param usingRemoteAnimation true if doing a remote animation, false o/w + */ + public void setUsingRemoteAnimation(boolean usingRemoteAnimation) { + mUsingRemoteAnimation = usingRemoteAnimation; + } + /** * Handles input from the overview button. Launch the most recent task unless we just came from * the app. In that case, we launch the next most recent. @@ -360,22 +407,61 @@ public final class IconRecentsView extends FrameLayout implements Insettable { * @param showStatusBarForegroundScrim true to show the scrim, false to hide */ public void setShowStatusBarForegroundScrim(boolean showStatusBarForegroundScrim) { - boolean shouldShow = mInsets.top != 0 && showStatusBarForegroundScrim; + mShowStatusBarForegroundScrim = showStatusBarForegroundScrim; + if (mShowStatusBarForegroundScrim != showStatusBarForegroundScrim) { + updateStatusBarScrim(); + } + } + + private void updateStatusBarScrim() { + boolean shouldShow = mInsets.top != 0 && mShowStatusBarForegroundScrim; mActivity.getDragLayer().setForeground(shouldShow ? mStatusBarForegroundScrim : null); } /** - * Get the bottom most thumbnail view to animate to. + * Get the bottom most task view to animate to. * - * @return the thumbnail view if laid out + * @return the task view */ - public @Nullable View getBottomThumbnailView() { - ArrayList taskViews = getTaskViews(); - if (taskViews.isEmpty()) { - return null; + private @Nullable TaskItemView getBottomTaskView() { + int childCount = mTaskRecyclerView.getChildCount(); + for (int i = 0; i < childCount; i++) { + View view = mTaskRecyclerView.getChildAt(i); + if (mTaskRecyclerView.getChildViewHolder(view).getItemViewType() == ITEM_TYPE_TASK) { + return (TaskItemView) view; + } } - TaskItemView view = taskViews.get(0); - return view.getThumbnailView(); + return null; + } + + /** + * Whether this view has processed all data changes and is ready to animate from the app to + * the overview. + * + * @return true if ready to animate app to overview, false otherwise + */ + public boolean isReadyForRemoteAnim() { + return !mTaskRecyclerView.hasPendingAdapterUpdates(); + } + + /** + * Set a callback for whenever this view is ready to do a remote animation from the app to + * overview. See {@link #isReadyForRemoteAnim()}. + * + * @param callback callback to run when view is ready to animate + */ + public void setOnReadyForRemoteAnimCallback(onReadyForRemoteAnimCallback callback) { + mTaskRecyclerView.getViewTreeObserver().addOnGlobalLayoutListener( + new ViewTreeObserver.OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + if (isReadyForRemoteAnim()) { + callback.onReadyForRemoteAnim(); + mTaskRecyclerView.getViewTreeObserver(). + removeOnGlobalLayoutListener(this); + } + } + }); } /** @@ -549,6 +635,255 @@ public final class IconRecentsView extends FrameLayout implements Insettable { } }); mLayoutAnimation.start(); + mStartedEnterAnimation = true; + } + + /** + * Play remote app to recents animation when the app is the home activity. We use a simple + * cross-fade here. Note this is only used if the home activity is a separate app than the + * recents activity. + * + * @param anim animator set + * @param homeTarget the home surface thats closing + * @param recentsTarget the surface containing recents + */ + public void playRemoteHomeToRecentsAnimation(@NonNull AnimatorSet anim, + @NonNull RemoteAnimationTargetCompat homeTarget, + @NonNull RemoteAnimationTargetCompat recentsTarget) { + SyncRtSurfaceTransactionApplierCompat surfaceApplier = + new SyncRtSurfaceTransactionApplierCompat(this); + + SurfaceParams[] params = new SurfaceParams[2]; + int boostedMode = MODE_CLOSING; + + ValueAnimator remoteHomeAnim = ValueAnimator.ofFloat(0, 1); + remoteHomeAnim.setDuration(REMOTE_APP_TO_OVERVIEW_DURATION); + + remoteHomeAnim.addUpdateListener(valueAnimator -> { + float val = (float) valueAnimator.getAnimatedValue(); + float alpha; + RemoteAnimationTargetCompat visibleTarget; + RemoteAnimationTargetCompat invisibleTarget; + if (val < .5f) { + visibleTarget = homeTarget; + invisibleTarget = recentsTarget; + alpha = 1 - (val * 2); + } else { + visibleTarget = recentsTarget; + invisibleTarget = homeTarget; + alpha = (val - .5f) * 2; + } + params[0] = new SurfaceParams(visibleTarget.leash, alpha, null /* matrix */, + null /* windowCrop */, getLayer(visibleTarget, boostedMode), + 0 /* cornerRadius */); + params[1] = new SurfaceParams(invisibleTarget.leash, 0.0f, null /* matrix */, + null /* windowCrop */, getLayer(invisibleTarget, boostedMode), + 0 /* cornerRadius */); + surfaceApplier.scheduleApply(params); + }); + anim.play(remoteHomeAnim); + animateFadeInLayoutAnimation(); + } + + /** + * Play remote animation from app to recents. This should scale the currently closing app down + * to the recents thumbnail. + * + * @param anim animator set + * @param appTarget the app surface thats closing + * @param recentsTarget the surface containing recents + */ + public void playRemoteAppToRecentsAnimation(@NonNull AnimatorSet anim, + @NonNull RemoteAnimationTargetCompat appTarget, + @NonNull RemoteAnimationTargetCompat recentsTarget) { + TaskItemView bottomView = getBottomTaskView(); + if (bottomView == null) { + // This can be null if there were previously 0 tasks and the recycler view has not had + // enough time to take in the data change, bind a new view, and lay out the new view. + // TODO: Have a fallback to animate to + anim.play(ValueAnimator.ofInt(0, 1).setDuration(REMOTE_APP_TO_OVERVIEW_DURATION)); + return; + } + final Matrix appMatrix = new Matrix(); + playRemoteTransYAnim(anim, appMatrix); + playRemoteAppScaleDownAnim(anim, appMatrix, appTarget, recentsTarget, + bottomView.getThumbnailView()); + playRemoteTaskListFadeIn(anim, bottomView); + mStartedEnterAnimation = true; + } + + /** + * Play translation Y animation for the remote app to recents animation. Animates over all task + * views as well as the closing app, easing them into their final vertical positions. + * + * @param anim animator set to play on + * @param appMatrix transformation matrix for the closing app surface + */ + private void playRemoteTransYAnim(@NonNull AnimatorSet anim, @NonNull Matrix appMatrix) { + final ArrayList views = getTaskViews(); + + // Start Y translation from about halfway through the tasks list to the bottom thumbnail. + float taskHeight = getResources().getDimension(R.dimen.task_item_height); + float totalTransY = -(MAX_TASKS_TO_DISPLAY / 2.0f - 1) * taskHeight; + for (int i = 0, size = views.size(); i < size; i++) { + views.get(i).setTranslationY(totalTransY); + } + + ValueAnimator transYAnim = ValueAnimator.ofFloat(totalTransY, 0); + transYAnim.setDuration(REMOTE_TO_RECENTS_VERTICAL_EASE_IN_DURATION); + transYAnim.setInterpolator(FAST_OUT_SLOW_IN_2); + transYAnim.addUpdateListener(valueAnimator -> { + float transY = (float) valueAnimator.getAnimatedValue(); + for (int i = 0, size = views.size(); i < size; i++) { + views.get(i).setTranslationY(transY); + } + appMatrix.postTranslate(0, transY - totalTransY); + }); + transYAnim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + for (int i = 0, size = views.size(); i < size; i++) { + views.get(i).setTranslationY(0); + } + } + }); + anim.play(transYAnim); + } + + /** + * Play the scale down animation for the remote app to recents animation where the app surface + * scales down to where the thumbnail is. + * + * @param anim animator set to play on + * @param appMatrix transformation matrix for the app surface + * @param appTarget closing app target + * @param recentsTarget opening recents target + * @param thumbnailView thumbnail view to animate to + */ + private void playRemoteAppScaleDownAnim(@NonNull AnimatorSet anim, @NonNull Matrix appMatrix, + @NonNull RemoteAnimationTargetCompat appTarget, + @NonNull RemoteAnimationTargetCompat recentsTarget, + @NonNull View thumbnailView) { + // Identify where the entering remote app should animate to. + Rect endRect = new Rect(); + thumbnailView.getGlobalVisibleRect(endRect); + Rect appBounds = appTarget.sourceContainerBounds; + + SyncRtSurfaceTransactionApplierCompat surfaceApplier = + new SyncRtSurfaceTransactionApplierCompat(this); + + // Keep recents visible throughout the animation. + SurfaceParams[] params = new SurfaceParams[2]; + // Closing app should stay on top. + int boostedMode = MODE_CLOSING; + params[0] = new SurfaceParams(recentsTarget.leash, 1f, null /* matrix */, + null /* windowCrop */, getLayer(recentsTarget, boostedMode), 0 /* cornerRadius */); + + ValueAnimator remoteAppAnim = ValueAnimator.ofInt(0, 1); + remoteAppAnim.setDuration(REMOTE_TO_RECENTS_VERTICAL_EASE_IN_DURATION); + remoteAppAnim.addUpdateListener(new MultiValueUpdateListener() { + private final FloatProp mScaleX; + private final FloatProp mScaleY; + private final FloatProp mTranslationX; + private final FloatProp mTranslationY; + private final FloatProp mAlpha; + + { + // Scale down and move to view location. + float endScaleX = ((float) endRect.width()) / appBounds.width(); + mScaleX = new FloatProp(1f, endScaleX, 0, REMOTE_TO_RECENTS_APP_SCALE_DOWN_DURATION, + FAST_OUT_SLOW_IN_1); + float endScaleY = ((float) endRect.height()) / appBounds.height(); + mScaleY = new FloatProp(1f, endScaleY, 0, REMOTE_TO_RECENTS_APP_SCALE_DOWN_DURATION, + FAST_OUT_SLOW_IN_1); + float endTranslationX = endRect.left - + (appBounds.width() - thumbnailView.getWidth()) / 2.0f; + mTranslationX = new FloatProp(0, endTranslationX, 0, + REMOTE_TO_RECENTS_APP_SCALE_DOWN_DURATION, FAST_OUT_SLOW_IN_1); + float endTranslationY = endRect.top - + (appBounds.height() - thumbnailView.getHeight()) / 2.0f; + mTranslationY = new FloatProp(0, endTranslationY, 0, + REMOTE_TO_RECENTS_APP_SCALE_DOWN_DURATION, FAST_OUT_SLOW_IN_2); + mAlpha = new FloatProp(1.0f, 0, 0, REMOTE_TO_RECENTS_APP_SCALE_DOWN_DURATION, + ACCEL_2); + } + + @Override + public void onUpdate(float percent) { + appMatrix.preScale(mScaleX.value, mScaleY.value, + appBounds.width() / 2.0f, appBounds.height() / 2.0f); + appMatrix.postTranslate(mTranslationX.value, mTranslationY.value); + + params[1] = new SurfaceParams(appTarget.leash, mAlpha.value, appMatrix, + null /* windowCrop */, getLayer(appTarget, boostedMode), + 0 /* cornerRadius */); + surfaceApplier.scheduleApply(params); + appMatrix.reset(); + } + }); + anim.play(remoteAppAnim); + } + + /** + * Play task list fade in animation as part of remote app to recents animation. This animation + * ensures that the task views in the recents list fade in from bottom to top. + * + * @param anim animator set to play on + * @param appTaskView the task view associated with the remote app closing + */ + private void playRemoteTaskListFadeIn(@NonNull AnimatorSet anim, + @NonNull TaskItemView appTaskView) { + long delay = REMOTE_TO_RECENTS_ITEM_FADE_START_DELAY; + int childCount = mTaskRecyclerView.getChildCount(); + for (int i = 0; i < childCount; i++) { + ValueAnimator fadeAnim = ValueAnimator.ofFloat(0, 1.0f); + fadeAnim.setDuration(REMOTE_TO_RECENTS_ITEM_FADE_DURATION).setInterpolator(OUT_SLOW_IN); + fadeAnim.setStartDelay(delay); + View view = mTaskRecyclerView.getChildAt(i); + if (Objects.equals(view, appTaskView)) { + // Only animate icon and text for the view with snapshot animating in + final View icon = appTaskView.getIconView(); + final View label = appTaskView.getLabelView(); + + icon.setAlpha(0.0f); + label.setAlpha(0.0f); + + fadeAnim.addUpdateListener(alphaVal -> { + float val = alphaVal.getAnimatedFraction(); + + icon.setAlpha(val); + label.setAlpha(val); + }); + fadeAnim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + icon.setAlpha(1.0f); + label.setAlpha(1.0f); + } + }); + } else { + // Otherwise, fade in the entire view. + view.setAlpha(0.0f); + fadeAnim.addUpdateListener(alphaVal -> { + float val = alphaVal.getAnimatedFraction(); + view.setAlpha(val); + }); + fadeAnim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + view.setAlpha(1.0f); + } + }); + } + anim.play(fadeAnim); + + int itemType = mTaskRecyclerView.getChildViewHolder(view).getItemViewType(); + if (itemType == ITEM_TYPE_CLEAR_ALL) { + // Don't add delay. Clear all should animate at same time as next view. + continue; + } + delay += REMOTE_TO_RECENTS_ITEM_FADE_BETWEEN_DELAY; + } } @Override @@ -556,5 +891,16 @@ public final class IconRecentsView extends FrameLayout implements Insettable { mInsets = insets; mTaskRecyclerView.setPadding(insets.left, insets.top, insets.right, insets.bottom); mTaskRecyclerView.invalidateItemDecorations(); + if (mInsets.top != 0) { + updateStatusBarScrim(); + } + } + + /** + * Callback for when this view is ready for a remote animation from app to overview. + */ + public interface onReadyForRemoteAnimCallback { + + void onReadyForRemoteAnim(); } } diff --git a/go/quickstep/src/com/android/quickstep/views/TaskItemView.java b/go/quickstep/src/com/android/quickstep/views/TaskItemView.java index 6db8013224..f184ad06a0 100644 --- a/go/quickstep/src/com/android/quickstep/views/TaskItemView.java +++ b/go/quickstep/src/com/android/quickstep/views/TaskItemView.java @@ -137,6 +137,14 @@ public final class TaskItemView extends LinearLayout { return mThumbnailView; } + public View getIconView() { + return mIconView; + } + + public View getLabelView() { + return mLabelView; + } + /** * Start a new animation from the current task content to the specified new content. The caller * is responsible for the actual animation control via the property diff --git a/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java b/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java index ab4b64cbfe..3c5585421a 100644 --- a/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java +++ b/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java @@ -78,7 +78,7 @@ public class BaseIconFactory implements AutoCloseable { public IconNormalizer getNormalizer() { if (mNormalizer == null) { - mNormalizer = new IconNormalizer(mContext, mIconBitmapSize); + mNormalizer = new IconNormalizer(mIconBitmapSize); } return mNormalizer; } diff --git a/iconloaderlib/src/com/android/launcher3/icons/DotRenderer.java b/iconloaderlib/src/com/android/launcher3/icons/DotRenderer.java index 6bc859aa64..af07aa3a99 100644 --- a/iconloaderlib/src/com/android/launcher3/icons/DotRenderer.java +++ b/iconloaderlib/src/com/android/launcher3/icons/DotRenderer.java @@ -23,8 +23,10 @@ import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; -import android.graphics.Point; +import android.graphics.Path; +import android.graphics.PathMeasure; import android.graphics.Rect; +import android.graphics.RectF; import android.util.Log; import android.view.ViewDebug; @@ -36,33 +38,51 @@ public class DotRenderer { private static final String TAG = "DotRenderer"; // The dot size is defined as a percentage of the app icon size. - private static final float SIZE_PERCENTAGE = 0.38f; + private static final float SIZE_PERCENTAGE = 0.228f; - // Extra scale down of the dot - private static final float DOT_SCALE = 0.6f; - - // Offset the dot slightly away from the icon if there's space. - private static final float OFFSET_PERCENTAGE = 0.02f; - - private final float mDotCenterOffset; - private final int mOffset; private final float mCircleRadius; private final Paint mCirclePaint = new Paint(ANTI_ALIAS_FLAG | FILTER_BITMAP_FLAG); private final Bitmap mBackgroundWithShadow; private final float mBitmapOffset; - public DotRenderer(int iconSizePx) { - mDotCenterOffset = SIZE_PERCENTAGE * iconSizePx; - mOffset = (int) (OFFSET_PERCENTAGE * iconSizePx); + // Stores the center x and y position as a percentage (0 to 1) of the icon size + private final float[] mRightDotPosition; + private final float[] mLeftDotPosition; - int size = (int) (DOT_SCALE * mDotCenterOffset); + public DotRenderer(int iconSizePx, Path iconShapePath, int pathSize) { + int size = Math.round(SIZE_PERCENTAGE * iconSizePx); ShadowGenerator.Builder builder = new ShadowGenerator.Builder(Color.TRANSPARENT); builder.ambientShadowAlpha = 88; mBackgroundWithShadow = builder.setupBlurForSize(size).createPill(size, size); mCircleRadius = builder.radius; mBitmapOffset = -mBackgroundWithShadow.getHeight() * 0.5f; // Same as width. + + // Find the points on the path that are closest to the top left and right corners. + mLeftDotPosition = getPathPoint(iconShapePath, pathSize, -1); + mRightDotPosition = getPathPoint(iconShapePath, pathSize, 1); + } + + private static float[] getPathPoint(Path path, float size, float direction) { + float halfSize = size / 2; + // Small delta so that we don't get a zero size triangle + float delta = 1; + + float x = halfSize + direction * halfSize; + Path trianglePath = new Path(); + trianglePath.moveTo(halfSize, halfSize); + trianglePath.lineTo(x + delta * direction, 0); + trianglePath.lineTo(x, -delta); + trianglePath.close(); + + trianglePath.op(path, Path.Op.INTERSECT); + float[] pos = new float[2]; + new PathMeasure(trianglePath, false).getPosTan(0, pos, null); + + pos[0] = pos[0] / size; + pos[1] = pos[1] / size; + return pos; } /** @@ -74,15 +94,21 @@ public class DotRenderer { return; } canvas.save(); - // We draw the dot relative to its center. - float dotCenterX = params.leftAlign - ? params.iconBounds.left + mDotCenterOffset / 2 - : params.iconBounds.right - mDotCenterOffset / 2; - float dotCenterY = params.iconBounds.top + mDotCenterOffset / 2; - int offsetX = Math.min(mOffset, params.spaceForOffset.x); - int offsetY = Math.min(mOffset, params.spaceForOffset.y); - canvas.translate(dotCenterX + offsetX, dotCenterY - offsetY); + Rect iconBounds = params.iconBounds; + float[] dotPosition = params.leftAlign ? mLeftDotPosition : mRightDotPosition; + float dotCenterX = iconBounds.left + iconBounds.width() * dotPosition[0]; + float dotCenterY = iconBounds.top + iconBounds.height() * dotPosition[1]; + + // Ensure dot fits entirely in canvas clip bounds. + Rect canvasBounds = canvas.getClipBounds(); + float offsetX = params.leftAlign + ? Math.max(0, canvasBounds.left - (dotCenterX + mBitmapOffset)) + : Math.min(0, canvasBounds.right - (dotCenterX - mBitmapOffset)); + float offsetY = Math.max(0, canvasBounds.top - (dotCenterY + mBitmapOffset)); + + // We draw the dot relative to its center. + canvas.translate(dotCenterX + offsetX, dotCenterY + offsetY); canvas.scale(params.scale, params.scale); mCirclePaint.setColor(Color.BLACK); @@ -102,9 +128,6 @@ public class DotRenderer { /** The progress of the animation, from 0 to 1. */ @ViewDebug.ExportedProperty(category = "notification dot") public float scale; - /** Overrides internally calculated offset if specified value is smaller. */ - @ViewDebug.ExportedProperty(category = "notification dot") - public Point spaceForOffset = new Point(); /** Whether the dot should align to the top left of the icon rather than the top right. */ @ViewDebug.ExportedProperty(category = "notification dot") public boolean leftAlign; diff --git a/iconloaderlib/src/com/android/launcher3/icons/GraphicsUtils.java b/iconloaderlib/src/com/android/launcher3/icons/GraphicsUtils.java index 11d5eef52c..3e818a5568 100644 --- a/iconloaderlib/src/com/android/launcher3/icons/GraphicsUtils.java +++ b/iconloaderlib/src/com/android/launcher3/icons/GraphicsUtils.java @@ -16,6 +16,9 @@ package com.android.launcher3.icons; import android.graphics.Bitmap; +import android.graphics.Rect; +import android.graphics.Region; +import android.graphics.RegionIterator; import android.util.Log; import java.io.ByteArrayOutputStream; @@ -60,4 +63,14 @@ public class GraphicsUtils { return null; } } + + public static int getArea(Region r) { + RegionIterator itr = new RegionIterator(r); + int area = 0; + Rect tempRect = new Rect(); + while (itr.next(tempRect)) { + area += tempRect.width() * tempRect.height(); + } + return area; + } } diff --git a/iconloaderlib/src/com/android/launcher3/icons/IconNormalizer.java b/iconloaderlib/src/com/android/launcher3/icons/IconNormalizer.java index 05908df99b..4a2a7cf9ff 100644 --- a/iconloaderlib/src/com/android/launcher3/icons/IconNormalizer.java +++ b/iconloaderlib/src/com/android/launcher3/icons/IconNormalizer.java @@ -16,14 +16,18 @@ package com.android.launcher3.icons; +import android.annotation.TargetApi; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; +import android.graphics.Path; import android.graphics.Rect; import android.graphics.RectF; +import android.graphics.Region; import android.graphics.drawable.AdaptiveIconDrawable; import android.graphics.drawable.Drawable; +import android.os.Build; import java.nio.ByteBuffer; @@ -57,7 +61,7 @@ public class IconNormalizer { private final Canvas mCanvas; private final byte[] mPixels; - private final Rect mAdaptiveIconBounds; + private final RectF mAdaptiveIconBounds; private float mAdaptiveIconScale; // for each y, stores the position of the leftmost x and the rightmost x @@ -66,7 +70,7 @@ public class IconNormalizer { private final Rect mBounds; /** package private **/ - IconNormalizer(Context context, int iconBitmapSize) { + IconNormalizer(int iconBitmapSize) { // Use twice the icon size as maximum size to avoid scaling down twice. mMaxSize = iconBitmapSize * 2; mBitmap = Bitmap.createBitmap(mMaxSize, mMaxSize, Bitmap.Config.ALPHA_8); @@ -75,11 +79,53 @@ public class IconNormalizer { mLeftBorder = new float[mMaxSize]; mRightBorder = new float[mMaxSize]; mBounds = new Rect(); - mAdaptiveIconBounds = new Rect(); + mAdaptiveIconBounds = new RectF(); mAdaptiveIconScale = SCALE_NOT_INITIALIZED; } + private static float getScale(float hullArea, float boundingArea, float fullArea) { + float hullByRect = hullArea / boundingArea; + float scaleRequired; + if (hullByRect < CIRCLE_AREA_BY_RECT) { + scaleRequired = MAX_CIRCLE_AREA_FACTOR; + } else { + scaleRequired = MAX_SQUARE_AREA_FACTOR + LINEAR_SCALE_SLOPE * (1 - hullByRect); + } + + float areaScale = hullArea / fullArea; + // Use sqrt of the final ratio as the images is scaled across both width and height. + return areaScale > scaleRequired ? (float) Math.sqrt(scaleRequired / areaScale) : 1; + } + + /** + * @param d Should be AdaptiveIconDrawable + * @param size Canvas size to use + */ + @TargetApi(Build.VERSION_CODES.O) + public static float normalizeAdaptiveIcon(Drawable d, int size, @Nullable RectF outBounds) { + Rect tmpBounds = new Rect(d.getBounds()); + d.setBounds(0, 0, size, size); + + Path path = ((AdaptiveIconDrawable) d).getIconMask(); + Region region = new Region(); + region.setPath(path, new Region(0, 0, size, size)); + + Rect hullBounds = region.getBounds(); + int hullArea = GraphicsUtils.getArea(region); + + if (outBounds != null) { + float sizeF = size; + outBounds.set( + hullBounds.left / sizeF, + hullBounds.top / sizeF, + 1 - (hullBounds.right / sizeF), + 1 - (hullBounds.bottom / sizeF)); + } + d.setBounds(tmpBounds); + return getScale(hullArea, hullArea, size * size); + } + /** * Returns the amount by which the {@param d} should be scaled (in both dimensions) so that it * matches the design guidelines for a launcher icon. @@ -96,12 +142,13 @@ public class IconNormalizer { */ public synchronized float getScale(@NonNull Drawable d, @Nullable RectF outBounds) { if (BaseIconFactory.ATLEAST_OREO && d instanceof AdaptiveIconDrawable) { - if (mAdaptiveIconScale != SCALE_NOT_INITIALIZED) { - if (outBounds != null) { - outBounds.set(mAdaptiveIconBounds); - } - return mAdaptiveIconScale; + if (mAdaptiveIconScale == SCALE_NOT_INITIALIZED) { + mAdaptiveIconScale = normalizeAdaptiveIcon(d, mMaxSize, mAdaptiveIconBounds); } + if (outBounds != null) { + outBounds.set(mAdaptiveIconBounds); + } + return mAdaptiveIconScale; } int width = d.getIntrinsicWidth(); int height = d.getIntrinsicHeight(); @@ -184,16 +231,6 @@ public class IconNormalizer { area += mRightBorder[y] - mLeftBorder[y] + 1; } - // Area of the rectangle required to fit the convex hull - float rectArea = (bottomY + 1 - topY) * (rightX + 1 - leftX); - float hullByRect = area / rectArea; - - float scaleRequired; - if (hullByRect < CIRCLE_AREA_BY_RECT) { - scaleRequired = MAX_CIRCLE_AREA_FACTOR; - } else { - scaleRequired = MAX_SQUARE_AREA_FACTOR + LINEAR_SCALE_SLOPE * (1 - hullByRect); - } mBounds.left = leftX; mBounds.right = rightX; @@ -205,15 +242,10 @@ public class IconNormalizer { 1 - ((float) mBounds.right) / width, 1 - ((float) mBounds.bottom) / height); } - float areaScale = area / (width * height); - // Use sqrt of the final ratio as the images is scaled across both width and height. - float scale = areaScale > scaleRequired ? (float) Math.sqrt(scaleRequired / areaScale) : 1; - if (BaseIconFactory.ATLEAST_OREO && d instanceof AdaptiveIconDrawable && - mAdaptiveIconScale == SCALE_NOT_INITIALIZED) { - mAdaptiveIconScale = scale; - mAdaptiveIconBounds.set(mBounds); - } - return scale; + + // Area of the rectangle required to fit the convex hull + float rectArea = (bottomY + 1 - topY) * (rightX + 1 - leftX); + return getScale(area, rectArea, width * height); } /** diff --git a/protos/launcher_log.proto b/protos/launcher_log.proto index 02c6b0f419..49fd43617e 100644 --- a/protos/launcher_log.proto +++ b/protos/launcher_log.proto @@ -56,6 +56,7 @@ message Target { optional int32 predictedRank = 15; optional TargetExtension extension = 16; optional TipType tip_type = 17; + optional int32 search_query_length = 18; } // Used to define what type of item a Target would represent. diff --git a/quickstep/recents_ui_overrides/res/layout/proactive_hints_container.xml b/quickstep/recents_ui_overrides/res/layout/proactive_hints_container.xml new file mode 100644 index 0000000000..be3f17acd8 --- /dev/null +++ b/quickstep/recents_ui_overrides/res/layout/proactive_hints_container.xml @@ -0,0 +1,7 @@ + + \ No newline at end of file diff --git a/quickstep/recents_ui_overrides/res/values/dimens.xml b/quickstep/recents_ui_overrides/res/values/dimens.xml index 61c576e82b..b316edd737 100644 --- a/quickstep/recents_ui_overrides/res/values/dimens.xml +++ b/quickstep/recents_ui_overrides/res/values/dimens.xml @@ -23,4 +23,8 @@ 80dp + + + 18dp + 10dp \ No newline at end of file diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionAppTracker.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionAppTracker.java index 0b8c1c57f9..fa2810630c 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionAppTracker.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionAppTracker.java @@ -158,10 +158,10 @@ public class PredictionAppTracker extends AppLaunchTracker { public void onStartShortcut(String packageName, String shortcutId, UserHandle user, String container) { // TODO: Use the full shortcut info - AppTarget target = new AppTarget.Builder(new AppTargetId("shortcut:" + shortcutId)) - .setTarget(packageName, user) - .setClassName(shortcutId) - .build(); + AppTarget target = new AppTarget + .Builder(new AppTargetId("shortcut:" + shortcutId), packageName, user) + .setClassName(shortcutId) + .build(); sendLaunch(target, container); } @@ -169,10 +169,10 @@ public class PredictionAppTracker extends AppLaunchTracker { @UiThread public void onStartApp(ComponentName cn, UserHandle user, String container) { if (cn != null) { - AppTarget target = new AppTarget.Builder(new AppTargetId("app:" + cn)) - .setTarget(cn.getPackageName(), user) - .setClassName(cn.getClassName()) - .build(); + AppTarget target = new AppTarget + .Builder(new AppTargetId("app:" + cn), cn.getPackageName(), user) + .setClassName(cn.getClassName()) + .build(); sendLaunch(target, container); } } diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java index 48a163d870..6dad9afe57 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java @@ -67,8 +67,8 @@ public class PredictionUiStateManager implements OnGlobalLayoutListener, ItemInf // TODO (b/129421797): Update the client constants public enum Client { - HOME("GEL"), - OVERVIEW("OVERVIEW_GEL"); + HOME("home"), + OVERVIEW("overview"); public final String id; diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java index 518a2a6851..3a2958d54e 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java @@ -20,6 +20,11 @@ import static android.view.View.VISIBLE; import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.LauncherState.OVERVIEW; +import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON; + +import android.content.Context; +import android.graphics.Rect; +import android.view.Gravity; import com.android.launcher3.DeviceProfile; import com.android.launcher3.Launcher; @@ -28,6 +33,7 @@ import com.android.launcher3.LauncherStateManager.StateHandler; import com.android.launcher3.Utilities; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.graphics.RotationMode; import com.android.launcher3.uioverrides.touchcontrollers.FlingAndHoldTouchController; import com.android.launcher3.uioverrides.touchcontrollers.LandscapeEdgeSwipeController; import com.android.launcher3.uioverrides.touchcontrollers.NavBarToHomeTouchController; @@ -58,12 +64,83 @@ public abstract class RecentsUiFactory { // Scale recents takes before animating in private static final float RECENTS_PREPARE_SCALE = 1.33f; + public static RotationMode ROTATION_LANDSCAPE = new RotationMode(-90) { + @Override + public void mapRect(int left, int top, int right, int bottom, Rect out) { + out.left = top; + out.top = right; + out.right = bottom; + out.bottom = left; + } + + @Override + public void mapInsets(Context context, Rect insets, Rect out) { + if (SysUINavigationMode.getMode(context) == NO_BUTTON) { + out.set(insets); + } else { + out.top = Math.max(insets.top, insets.left); + out.bottom = insets.right; + out.left = insets.bottom; + out.right = 0; + } + } + }; + + public static RotationMode ROTATION_SEASCAPE = new RotationMode(90) { + @Override + public void mapRect(int left, int top, int right, int bottom, Rect out) { + out.left = bottom; + out.top = left; + out.right = top; + out.bottom = right; + } + + @Override + public void mapInsets(Context context, Rect insets, Rect out) { + if (SysUINavigationMode.getMode(context) == NO_BUTTON) { + out.set(insets); + } else { + out.top = Math.max(insets.top, insets.right); + out.bottom = insets.left; + out.right = insets.bottom; + out.left = 0; + } + } + + @Override + public int toNaturalGravity(int absoluteGravity) { + int horizontalGravity = absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK; + int verticalGravity = absoluteGravity & Gravity.VERTICAL_GRAVITY_MASK; + + if (horizontalGravity == Gravity.RIGHT) { + horizontalGravity = Gravity.LEFT; + } else if (horizontalGravity == Gravity.LEFT) { + horizontalGravity = Gravity.RIGHT; + } + + if (verticalGravity == Gravity.TOP) { + verticalGravity = Gravity.BOTTOM; + } else if (verticalGravity == Gravity.BOTTOM) { + verticalGravity = Gravity.TOP; + } + + return ((absoluteGravity & ~Gravity.HORIZONTAL_GRAVITY_MASK) + & ~Gravity.VERTICAL_GRAVITY_MASK) + | horizontalGravity | verticalGravity; + } + }; + + public static RotationMode getRotationMode(DeviceProfile dp) { + return !dp.isVerticalBarLayout() ? RotationMode.NORMAL + : (dp.isSeascape() ? ROTATION_SEASCAPE : ROTATION_LANDSCAPE); + } + public static TouchController[] createTouchControllers(Launcher launcher) { Mode mode = SysUINavigationMode.getMode(launcher); ArrayList list = new ArrayList<>(); list.add(launcher.getDragController()); - if (mode == Mode.NO_BUTTON) { + if (mode == NO_BUTTON) { list.add(new QuickSwitchTouchController(launcher)); list.add(new NavBarToHomeTouchController(launcher)); list.add(new FlingAndHoldTouchController(launcher)); @@ -106,7 +183,7 @@ public abstract class RecentsUiFactory { * @param launcher the launcher activity */ public static void prepareToShowOverview(Launcher launcher) { - if (SysUINavigationMode.getMode(launcher) == Mode.NO_BUTTON) { + if (SysUINavigationMode.getMode(launcher) == NO_BUTTON) { // Overview lives on the side, so doesn't scale in from above. return; } diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java index 11a1885d1a..c3a7698874 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java @@ -31,6 +31,7 @@ import com.android.launcher3.LauncherState; import com.android.launcher3.LauncherStateManager.AnimationConfig; import com.android.launcher3.anim.AnimatorSetBuilder; import com.android.launcher3.anim.PropertySetter; +import com.android.quickstep.hints.ProactiveHintsContainer; import com.android.quickstep.views.ClearAllButton; import com.android.quickstep.views.LauncherRecentsView; import com.android.quickstep.views.RecentsView; @@ -53,6 +54,14 @@ public final class RecentsViewStateController extends if (state.overviewUi) { mRecentsView.updateEmptyMessage(); mRecentsView.resetTaskVisuals(); + mRecentsView.setHintVisibility(1f); + } else { + mRecentsView.setHintVisibility(0f); + ProactiveHintsContainer + proactiveHintsContainer = mRecentsView.getProactiveHintsContainer(); + if (proactiveHintsContainer != null) { + proactiveHintsContainer.removeAllViews(); + } } setAlphas(PropertySetter.NO_ANIM_PROPERTY_SETTER, state.getVisibleElements(mLauncher)); } @@ -64,6 +73,14 @@ public final class RecentsViewStateController extends if (!toState.overviewUi) { builder.addOnFinishRunnable(mRecentsView::resetTaskVisuals); + mRecentsView.setHintVisibility(0f); + builder.addOnFinishRunnable(() -> { + ProactiveHintsContainer + proactiveHintsContainer = mRecentsView.getProactiveHintsContainer(); + if (proactiveHintsContainer != null) { + proactiveHintsContainer.removeAllViews(); + } + }); } if (toState.overviewUi) { @@ -75,6 +92,7 @@ public final class RecentsViewStateController extends updateAnim.setDuration(config.duration); builder.play(updateAnim); mRecentsView.updateEmptyMessage(); + builder.addOnFinishRunnable(() -> mRecentsView.setHintVisibility(1f)); } setAlphas(config.getPropertySetter(builder), toState.getVisibleElements(mLauncher)); diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java index fa07e27a97..c26a1d0578 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java @@ -17,13 +17,10 @@ package com.android.launcher3.uioverrides.states; import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS; -import android.graphics.Rect; - import com.android.launcher3.Launcher; import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.quickstep.util.ClipAnimationHelper; import com.android.quickstep.views.RecentsView; -import com.android.quickstep.views.TaskThumbnailView; import com.android.quickstep.views.TaskView; /** @@ -45,18 +42,9 @@ public class QuickSwitchState extends OverviewState { if (recentsView.getTaskViewCount() == 0) { return super.getOverviewScaleAndTranslation(launcher); } - // Compute scale and translation y such that the most recent task view fills the screen. - TaskThumbnailView dummyThumbnail = recentsView.getTaskViewAt(0).getThumbnail(); + TaskView dummyTask = recentsView.getTaskViewAt(0); ClipAnimationHelper clipAnimationHelper = new ClipAnimationHelper(launcher); - clipAnimationHelper.fromTaskThumbnailView(dummyThumbnail, recentsView); - Rect targetRect = new Rect(); - recentsView.getTaskSize(targetRect); - clipAnimationHelper.updateTargetRect(targetRect); - float toScale = clipAnimationHelper.getSourceRect().width() - / clipAnimationHelper.getTargetRect().width(); - float toTranslationY = clipAnimationHelper.getSourceRect().centerY() - - clipAnimationHelper.getTargetRect().centerY(); - return new ScaleAndTranslation(toScale, 0, toTranslationY); + return clipAnimationHelper.getOverviewFullscreenScaleAndTranslation(dummyTask); } @Override diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java index 81090c1c90..a1a790c2b6 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java @@ -28,9 +28,13 @@ import static com.android.launcher3.anim.Interpolators.ACCEL_2; import static com.android.launcher3.anim.Interpolators.DEACCEL_2; import static com.android.launcher3.anim.Interpolators.INSTANT; import static com.android.launcher3.anim.Interpolators.LINEAR; +import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW; +import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD; import android.view.MotionEvent; +import androidx.annotation.Nullable; + import com.android.launcher3.Launcher; import com.android.launcher3.LauncherState; import com.android.launcher3.LauncherStateManager; @@ -45,8 +49,6 @@ import com.android.quickstep.SysUINavigationMode.Mode; import com.android.quickstep.views.RecentsView; import com.android.quickstep.views.TaskView; -import androidx.annotation.Nullable; - /** * Handles quick switching to a recent task from the home screen. */ @@ -122,12 +124,16 @@ public class QuickSwitchTouchController extends AbstractStateChangeTouchControll @Override protected void updateProgress(float progress) { super.updateProgress(progress); - updateFullscreenProgress(progress); + updateFullscreenProgress(Utilities.boundToRange(progress, 0, 1)); } private void updateFullscreenProgress(float progress) { if (mTaskToLaunch != null) { mTaskToLaunch.setFullscreenProgress(progress); + int sysuiFlags = progress > UPDATE_SYSUI_FLAGS_THRESHOLD + ? mTaskToLaunch.getThumbnail().getSysUiStatusNavFlags() + : 0; + mLauncher.getSystemUiController().updateUiState(UI_STATE_OVERVIEW, sysuiFlags); } } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java index 624b3dc779..5e77e0adee 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java @@ -68,7 +68,7 @@ final class AppToOverviewAnimationProvider imple * @param wasVisible true if it was visible before */ boolean onActivityReady(T activity, Boolean wasVisible) { - activity.getOverviewPanel().setCurrentTask(mTargetTaskId); + activity.getOverviewPanel().showCurrentTask(mTargetTaskId); AbstractFloatingView.closeAllOpenViews(activity, wasVisible); ActivityControlHelper.AnimationFactory factory = mHelper.prepareRecentsUI(activity, wasVisible, diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java index 50f25fbf11..c33d25ccf6 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java @@ -270,9 +270,9 @@ public final class LauncherActivityControllerHelper implements ActivityControlHe playScaleDownAnim(anim, activity, endState); anim.setDuration(transitionLength * 2); - activity.getStateManager().setCurrentAnimation(anim); AnimatorPlaybackController controller = AnimatorPlaybackController.wrap(anim, transitionLength * 2); + activity.getStateManager().setCurrentUserControlledAnimation(controller); // Since we are changing the start position of the UI, reapply the state, at the end controller.setEndAction(() -> { @@ -301,34 +301,19 @@ public final class LauncherActivityControllerHelper implements ActivityControlHe return; } - // Setup the clip animation helper source/target rects in the final transformed state - // of the recents view (a scale/translationY may be applied prior to this animation - // starting to line up the side pages during swipe up) - float prevRvScale = recentsView.getScaleX(); - float prevRvTransY = recentsView.getTranslationY(); - float targetRvScale = endState.getOverviewScaleAndTranslation(launcher).scale; - SCALE_PROPERTY.set(recentsView, targetRvScale); - recentsView.setTranslationY(0); ClipAnimationHelper clipHelper = new ClipAnimationHelper(launcher); - float tmpCurveScale = v.getCurveScale(); - v.setCurveScale(1f); - clipHelper.fromTaskThumbnailView(v.getThumbnail(), (RecentsView) v.getParent(), null); - v.setCurveScale(tmpCurveScale); - SCALE_PROPERTY.set(recentsView, prevRvScale); - recentsView.setTranslationY(prevRvTransY); + LauncherState.ScaleAndTranslation fromScaleAndTranslation + = clipHelper.getOverviewFullscreenScaleAndTranslation(v); + LauncherState.ScaleAndTranslation endScaleAndTranslation + = endState.getOverviewScaleAndTranslation(launcher); - if (!clipHelper.getSourceRect().isEmpty() && !clipHelper.getTargetRect().isEmpty()) { - float fromScale = clipHelper.getSourceRect().width() - / clipHelper.getTargetRect().width(); - float fromTranslationY = clipHelper.getSourceRect().centerY() - - clipHelper.getTargetRect().centerY(); - Animator scale = ObjectAnimator.ofFloat(recentsView, SCALE_PROPERTY, fromScale, 1); - Animator translateY = ObjectAnimator.ofFloat(recentsView, TRANSLATION_Y, - fromTranslationY, 0); - scale.setInterpolator(LINEAR); - translateY.setInterpolator(LINEAR); - anim.playTogether(scale, translateY); - } + Animator scale = ObjectAnimator.ofFloat(recentsView, SCALE_PROPERTY, + fromScaleAndTranslation.scale, endScaleAndTranslation.scale); + Animator translateY = ObjectAnimator.ofFloat(recentsView, TRANSLATION_Y, + fromScaleAndTranslation.translationY, endScaleAndTranslation.translationY); + scale.setInterpolator(LINEAR); + translateY.setInterpolator(LINEAR); + anim.playTogether(scale, translateY); } @Override diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsAnimationWrapper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsAnimationWrapper.java index ced9afab78..5eecf1713d 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsAnimationWrapper.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsAnimationWrapper.java @@ -24,6 +24,7 @@ import android.view.KeyEvent; import android.view.MotionEvent; import com.android.launcher3.util.Preconditions; +import com.android.quickstep.inputconsumers.InputConsumer; import com.android.quickstep.util.SwipeAnimationTargetSet; import com.android.systemui.shared.system.InputConsumerController; @@ -59,6 +60,10 @@ public class RecentsAnimationWrapper { mInputProxySupplier = inputProxySupplier; } + public boolean hasTargets() { + return targetSet != null && targetSet.hasTargets(); + } + @UiThread public synchronized void setController(SwipeAnimationTargetSet targetSet) { Preconditions.assertUIThread(); diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeSharedState.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeSharedState.java index f393387a7f..194d073359 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeSharedState.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeSharedState.java @@ -38,6 +38,8 @@ public class SwipeSharedState implements SwipeAnimationListener { public boolean canGestureBeContinued; public boolean goingToLauncher; + public boolean recentsAnimationFinishInterrupted; + public int nextRunningTaskId = -1; public SwipeSharedState(OverviewComponentObserver overviewComponentObserver) { mOverviewComponentObserver = overviewComponentObserver; @@ -114,9 +116,22 @@ public class SwipeSharedState implements SwipeAnimationListener { } } + /** + * Called when a recents animation has finished, but was interrupted before the next task was + * launched. The given {@param runningTaskId} should be used as the running task for the + * continuing input consumer. + */ + public void setRecentsAnimationFinishInterrupted(int runningTaskId) { + recentsAnimationFinishInterrupted = true; + nextRunningTaskId = runningTaskId; + mLastAnimationTarget = mLastAnimationTarget.cloneWithoutTargets(); + } + public void clearAllState() { clearListenerState(); canGestureBeContinued = false; + recentsAnimationFinishInterrupted = false; + nextRunningTaskId = -1; goingToLauncher = false; } } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskSystemShortcut.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskSystemShortcut.java index e3dcadcd54..2c919b3c17 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskSystemShortcut.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskSystemShortcut.java @@ -16,6 +16,7 @@ package com.android.quickstep; +import static android.view.Display.DEFAULT_DISPLAY; import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch.TAP; import android.app.Activity; @@ -116,21 +117,22 @@ public class TaskSystemShortcut extends SystemShortcut mHandler = new Handler(Looper.getMainLooper()); } - protected abstract boolean isAvailable(BaseDraggingActivity activity); + protected abstract boolean isAvailable(BaseDraggingActivity activity, int displayId); protected abstract ActivityOptions makeLaunchOptions(Activity activity); protected abstract boolean onActivityStarted(BaseDraggingActivity activity); @Override public View.OnClickListener getOnClickListener( BaseDraggingActivity activity, TaskView taskView) { - if (!isAvailable(activity)) { - return null; - } final Task task = taskView.getTask(); final int taskId = task.key.id; + final int displayId = task.key.displayId; if (!task.isDockable) { return null; } + if (!isAvailable(activity, displayId)) { + return null; + } final RecentsView recentsView = activity.getOverviewPanel(); final TaskThumbnailView thumbnailView = taskView.getThumbnail(); @@ -218,9 +220,13 @@ public class TaskSystemShortcut extends SystemShortcut } @Override - protected boolean isAvailable(BaseDraggingActivity activity) { - // Don't show menu-item if already in multi-window - return !activity.getDeviceProfile().isMultiWindowMode; + protected boolean isAvailable(BaseDraggingActivity activity, int displayId) { + // Don't show menu-item if already in multi-window and the task is from + // the secondary display. + // TODO(b/118266305): Temporarily disable splitscreen for secondary display while new + // implementation is enabled + return !activity.getDeviceProfile().isMultiWindowMode + && displayId == DEFAULT_DISPLAY; } @Override @@ -256,7 +262,7 @@ public class TaskSystemShortcut extends SystemShortcut } @Override - protected boolean isAvailable(BaseDraggingActivity activity) { + protected boolean isAvailable(BaseDraggingActivity activity, int displayId) { return ActivityManagerWrapper.getInstance().supportsFreeformMultiWindow(activity); } @@ -290,10 +296,6 @@ public class TaskSystemShortcut extends SystemShortcut if (sysUiProxy == null) { return null; } - if (SysUINavigationMode.getMode(activity) == SysUINavigationMode.Mode.NO_BUTTON) { - // TODO(b/130225926): Temporarily disable pinning while gesture nav is enabled - return null; - } if (!ActivityManagerWrapper.getInstance().isScreenPinningEnabled()) { return null; } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java index f95f9c27e5..d0ea73a6fe 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java @@ -150,6 +150,7 @@ public final class TaskViewUtils { @Override public void onUpdate(float percent) { + // TODO: Take into account the current fullscreen progress for animating the insets params.setProgress(1 - percent); RectF taskBounds = inOutHelper.applyTransform(targetSet, params); if (!skipViewChanges) { diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java index b25865e640..128fd45fe5 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java @@ -26,6 +26,7 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_N import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED; import android.annotation.TargetApi; +import android.app.ActivityManager; import android.app.ActivityManager.RunningTaskInfo; import android.app.KeyguardManager; import android.app.Service; @@ -34,7 +35,6 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.content.res.Resources; import android.graphics.Point; import android.graphics.RectF; import android.graphics.Region; @@ -57,6 +57,7 @@ import android.view.WindowManager; import com.android.launcher3.MainThreadExecutor; import com.android.launcher3.R; +import com.android.launcher3.ResourceUtils; import com.android.launcher3.Utilities; import com.android.launcher3.compat.UserManagerCompat; import com.android.launcher3.logging.EventLogArray; @@ -65,6 +66,13 @@ import com.android.launcher3.util.LooperExecutor; import com.android.launcher3.util.UiThreadHelper; import com.android.quickstep.SysUINavigationMode.Mode; import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener; +import com.android.quickstep.inputconsumers.AccessibilityInputConsumer; +import com.android.quickstep.inputconsumers.AssistantTouchConsumer; +import com.android.quickstep.inputconsumers.DeviceLockedInputConsumer; +import com.android.quickstep.inputconsumers.InputConsumer; +import com.android.quickstep.inputconsumers.OtherActivityInputConsumer; +import com.android.quickstep.inputconsumers.OverviewInputConsumer; +import com.android.quickstep.inputconsumers.ScreenPinnedInputConsumer; import com.android.systemui.shared.recents.IOverviewProxy; import com.android.systemui.shared.recents.ISystemUiProxy; import com.android.systemui.shared.system.ActivityManagerWrapper; @@ -89,9 +97,6 @@ public class TouchInteractionService extends Service implements public static final LooperExecutor BACKGROUND_EXECUTOR = new LooperExecutor(UiThreadHelper.getBackgroundLooper()); - private static final String NAVBAR_VERTICAL_SIZE = "navigation_bar_frame_height"; - private static final String NAVBAR_HORIZONTAL_SIZE = "navigation_bar_width"; - public static final EventLogArray TOUCH_INTERACTION_LOG = new EventLogArray("touch_interaction_log", 40); @@ -290,15 +295,7 @@ public class TouchInteractionService extends Service implements } private int getNavbarSize(String resName) { - int frameSize; - Resources res = getResources(); - int frameSizeResID = res.getIdentifier(resName, "dimen", "android"); - if (frameSizeResID != 0) { - frameSize = res.getDimensionPixelSize(frameSizeResID); - } else { - frameSize = Utilities.pxFromDp(48, res.getDisplayMetrics()); - } - return frameSize; + return ResourceUtils.getNavbarSize(resName, getResources()); } private void initTouchBounds() { @@ -311,20 +308,21 @@ public class TouchInteractionService extends Service implements defaultDisplay.getRealSize(realSize); mSwipeTouchRegion.set(0, 0, realSize.x, realSize.y); if (mMode == Mode.NO_BUTTON) { - mSwipeTouchRegion.top = mSwipeTouchRegion.bottom - getNavbarSize(NAVBAR_VERTICAL_SIZE); + mSwipeTouchRegion.top = mSwipeTouchRegion.bottom - getNavbarSize( + ResourceUtils.NAVBAR_VERTICAL_SIZE); } else { switch (defaultDisplay.getRotation()) { case Surface.ROTATION_90: mSwipeTouchRegion.left = mSwipeTouchRegion.right - - getNavbarSize(NAVBAR_HORIZONTAL_SIZE); + - getNavbarSize(ResourceUtils.NAVBAR_HORIZONTAL_SIZE); break; case Surface.ROTATION_270: mSwipeTouchRegion.right = mSwipeTouchRegion.left - + getNavbarSize(NAVBAR_HORIZONTAL_SIZE); + + getNavbarSize(ResourceUtils.NAVBAR_HORIZONTAL_SIZE); break; default: mSwipeTouchRegion.top = mSwipeTouchRegion.bottom - - getNavbarSize(NAVBAR_VERTICAL_SIZE); + - getNavbarSize(ResourceUtils.NAVBAR_VERTICAL_SIZE); } } } @@ -428,8 +426,7 @@ public class TouchInteractionService extends Service implements MotionEvent event = (MotionEvent) ev; TOUCH_INTERACTION_LOG.addLog("onMotionEvent", event.getActionMasked()); if (event.getAction() == ACTION_DOWN) { - if (isInValidSystemUiState() - && mSwipeTouchRegion.contains(event.getX(), event.getY())) { + if (mSwipeTouchRegion.contains(event.getX(), event.getY())) { boolean useSharedState = mConsumer.useSharedSwipeState(); mConsumer.onConsumerAboutToBeSwitched(); mConsumer = newConsumer(useSharedState, event); @@ -442,59 +439,86 @@ public class TouchInteractionService extends Service implements mUncheckedConsumer.onMotionEvent(event); } - private boolean isInValidSystemUiState() { - return (mSystemUiStateFlags & SYSUI_STATE_NAV_BAR_HIDDEN) == 0 - && (mSystemUiStateFlags & SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED) == 0 - && !ActivityManagerWrapper.getInstance().isLockToAppActive(); - } private InputConsumer newConsumer(boolean useSharedState, MotionEvent event) { - // TODO: this makes a binder call every touch down. we should move to a listener pattern. - if (!mIsUserUnlocked || mKM.isDeviceLocked()) { - // This handles apps launched in direct boot mode (e.g. dialer) as well as apps launched - // while device is locked even after exiting direct boot mode (e.g. camera). - return new DeviceLockedInputConsumer(this); - } - - RunningTaskInfo runningTaskInfo = mAM.getRunningTask(0); - if (!useSharedState) { - mSwipeSharedState.clearAllState(); - } - - final ActivityControlHelper activityControl = - mOverviewComponentObserver.getActivityControlHelper(); - - InputConsumer base; - if (runningTaskInfo == null && !mSwipeSharedState.goingToLauncher) { - base = InputConsumer.NO_OP; - } else if (mSwipeSharedState.goingToLauncher || activityControl.isResumed()) { - base = OverviewInputConsumer.newInstance(activityControl, mInputMonitorCompat, false); - } else if (ENABLE_QUICKSTEP_LIVE_TILE.get() && - activityControl.isInLiveTileMode()) { - base = OverviewInputConsumer.newInstance(activityControl, mInputMonitorCompat, false); - } else if (mGestureBlockingActivity != null && runningTaskInfo != null - && mGestureBlockingActivity.equals(runningTaskInfo.topActivity)) { - base = InputConsumer.NO_OP; - } else { - base = createOtherActivityInputConsumer(event, runningTaskInfo); + boolean validSystemUIFlags = (mSystemUiStateFlags & SYSUI_STATE_NAV_BAR_HIDDEN) == 0 + && (mSystemUiStateFlags & SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED) == 0; + boolean topTaskLocked = ActivityManagerWrapper.getInstance().isLockToAppActive(); + boolean isInValidSystemUiState = validSystemUIFlags && !topTaskLocked; + + if (!mIsUserUnlocked) { + if (isInValidSystemUiState) { + // This handles apps launched in direct boot mode (e.g. dialer) as well as apps + // launched while device is locked even after exiting direct boot mode (e.g. camera). + return new DeviceLockedInputConsumer(this); + } else { + return InputConsumer.NO_OP; + } } + InputConsumer base = isInValidSystemUiState + ? newBaseConsumer(useSharedState, event) : InputConsumer.NO_OP; if (mMode == Mode.NO_BUTTON) { - if (mAssistantAvailable && AssistantTouchConsumer.withinTouchRegion(this, event)) { + final ActivityControlHelper activityControl = + mOverviewComponentObserver.getActivityControlHelper(); + if (mAssistantAvailable && !topTaskLocked + && AssistantTouchConsumer.withinTouchRegion(this, event)) { base = new AssistantTouchConsumer(this, mISystemUiProxy, activityControl, base, mInputMonitorCompat); } + if (ActivityManagerWrapper.getInstance().isScreenPinningActive()) { + // Note: we only allow accessibility to wrap this, and it replaces the previous + // base input consumer (which should be NO_OP anyway since topTaskLocked == true). + base = new ScreenPinnedInputConsumer(this, mISystemUiProxy, activityControl); + } + if ((mSystemUiStateFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0) { base = new AccessibilityInputConsumer(this, mISystemUiProxy, (mSystemUiStateFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0, base, mInputMonitorCompat); } } - return base; } + private InputConsumer newBaseConsumer(boolean useSharedState, MotionEvent event) { + if (mKM.isDeviceLocked()) { + // This handles apps launched in direct boot mode (e.g. dialer) as well as apps launched + // while device is locked even after exiting direct boot mode (e.g. camera). + return new DeviceLockedInputConsumer(this); + } + + final RunningTaskInfo runningTaskInfo = mAM.getRunningTask(0); + if (!useSharedState) { + mSwipeSharedState.clearAllState(); + } + + final ActivityControlHelper activityControl = + mOverviewComponentObserver.getActivityControlHelper(); + + if (runningTaskInfo == null && !mSwipeSharedState.goingToLauncher + && !mSwipeSharedState.recentsAnimationFinishInterrupted) { + return InputConsumer.NO_OP; + } else if (mSwipeSharedState.recentsAnimationFinishInterrupted) { + // If the finish animation was interrupted, then continue using the other activity input + // consumer but with the next task as the running task + RunningTaskInfo info = new ActivityManager.RunningTaskInfo(); + info.id = mSwipeSharedState.nextRunningTaskId; + return createOtherActivityInputConsumer(event, info); + } else if (mSwipeSharedState.goingToLauncher || activityControl.isResumed()) { + return OverviewInputConsumer.newInstance(activityControl, mInputMonitorCompat, false); + } else if (ENABLE_QUICKSTEP_LIVE_TILE.get() && + activityControl.isInLiveTileMode()) { + return OverviewInputConsumer.newInstance(activityControl, mInputMonitorCompat, false); + } else if (mGestureBlockingActivity != null && runningTaskInfo != null + && mGestureBlockingActivity.equals(runningTaskInfo.topActivity)) { + return InputConsumer.NO_OP; + } else { + return createOtherActivityInputConsumer(event, runningTaskInfo); + } + } + private OtherActivityInputConsumer createOtherActivityInputConsumer(MotionEvent event, RunningTaskInfo runningTaskInfo) { final ActivityControlHelper activityControl = diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java index 9c6102e22c..404cfe62ff 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java @@ -27,6 +27,7 @@ import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TI import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS; import static com.android.launcher3.util.RaceConditionTracker.ENTER; import static com.android.launcher3.util.RaceConditionTracker.EXIT; +import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW; import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION; import static com.android.quickstep.ActivityControlHelper.AnimationFactory.ShelfAnimState.HIDE; import static com.android.quickstep.ActivityControlHelper.AnimationFactory.ShelfAnimState.PEEK; @@ -66,6 +67,7 @@ import android.view.WindowManager; import android.view.animation.Interpolator; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.annotation.UiThread; import com.android.launcher3.AbstractFloatingView; @@ -90,6 +92,8 @@ import com.android.quickstep.ActivityControlHelper.AnimationFactory; import com.android.quickstep.ActivityControlHelper.AnimationFactory.ShelfAnimState; import com.android.quickstep.ActivityControlHelper.HomeAnimationFactory; import com.android.quickstep.SysUINavigationMode.Mode; +import com.android.quickstep.inputconsumers.InputConsumer; +import com.android.quickstep.inputconsumers.OverviewInputConsumer; import com.android.quickstep.util.ClipAnimationHelper; import com.android.quickstep.util.RectFSpringAnim; import com.android.quickstep.util.RemoteAnimationTargetSet; @@ -165,7 +169,7 @@ public class WindowTransformSwipeHandler private static final int LAUNCHER_UI_STATES = STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN | STATE_LAUNCHER_STARTED; - enum GestureEndTarget { + public enum GestureEndTarget { HOME(1, STATE_SCALED_CONTROLLER_HOME, true, false, ContainerType.WORKSPACE, false), RECENTS(1, STATE_SCALED_CONTROLLER_RECENTS | STATE_CAPTURE_SCREENSHOT @@ -219,8 +223,8 @@ public class WindowTransformSwipeHandler private final ClipAnimationHelper mClipAnimationHelper; private final ClipAnimationHelper.TransformParams mTransformParams; - protected Runnable mGestureEndCallback; - protected GestureEndTarget mGestureEndTarget; + private Runnable mGestureEndCallback; + private GestureEndTarget mGestureEndTarget; // Either RectFSpringAnim (if animating home) or ObjectAnimator (from mCurrentShift) otherwise private RunningWindowAnim mRunningWindowAnim; private boolean mIsShelfPeeking; @@ -256,7 +260,9 @@ public class WindowTransformSwipeHandler private AnimationFactory mAnimationFactory = (t) -> { }; private LiveTileOverlay mLiveTileOverlay = new LiveTileOverlay(); + private boolean mCanceled; private boolean mWasLauncherAlreadyVisible; + private int mFinishingRecentsAnimationForNewTaskId = -1; private boolean mPassedOverviewThreshold; private boolean mGestureStarted; @@ -270,7 +276,7 @@ public class WindowTransformSwipeHandler private final long mTouchTimeMs; private long mLauncherFrameDrawnTime; - WindowTransformSwipeHandler(RunningTaskInfo runningTaskInfo, Context context, + public WindowTransformSwipeHandler(RunningTaskInfo runningTaskInfo, Context context, long touchTimeMs, ActivityControlHelper controller, boolean continuingLastGesture, InputConsumerController inputConsumer) { mContext = context; @@ -412,7 +418,6 @@ public class WindowTransformSwipeHandler mRecentsAnimationWrapper.runOnInit(() -> mRecentsAnimationWrapper.targetSet.addDependentTransactionApplier(applier)); }); - mRecentsView.setEnableFreeScroll(false); mRecentsView.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> { if (mGestureEndTarget != HOME) { @@ -500,10 +505,7 @@ public class WindowTransformSwipeHandler if (mContinuingLastGesture) { return; } - mRecentsView.setEnableDrawingLiveTile(false); - mRecentsView.showTask(mRunningTaskId); - mRecentsView.setRunningTaskHidden(true); - mRecentsView.setRunningTaskIconScaledDown(true); + mRecentsView.onGestureAnimationStart(mRunningTaskId); } private void launcherFrameDrawn() { @@ -656,8 +658,7 @@ public class WindowTransformSwipeHandler mTransformParams.setProgress(shift).setOffsetX(offsetX).setOffsetScale(offsetScale); mClipAnimationHelper.applyTransform(mRecentsAnimationWrapper.targetSet, mTransformParams); - mRecentsAnimationWrapper.setWindowThresholdCrossed( - shift > 1 - UPDATE_SYSUI_FLAGS_THRESHOLD); + updateSysUiFlags(shift); } if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { @@ -679,7 +680,7 @@ public class WindowTransformSwipeHandler int runningTaskIndex = mRecentsView == null ? -1 : mRecentsView.getRunningTaskIndex(); if (runningTaskIndex >= 0) { for (int i = 0; i < mRecentsView.getTaskViewCount(); i++) { - if (i != runningTaskIndex) { + if (i != runningTaskIndex || !mRecentsAnimationWrapper.hasTargets()) { mRecentsView.getTaskViewAt(i).setFullscreenProgress(1 - mCurrentShift.value); } } @@ -702,6 +703,18 @@ public class WindowTransformSwipeHandler ? 0 : (progress - mShiftAtGestureStart) / (1 - mShiftAtGestureStart)); } + private void updateSysUiFlags(float windowProgress) { + if (mRecentsView != null) { + // We will handle the sysui flags based on the centermost task view. + mRecentsAnimationWrapper.setWindowThresholdCrossed(true); + int sysuiFlags = windowProgress > 1 - UPDATE_SYSUI_FLAGS_THRESHOLD + ? 0 + : mRecentsView.getTaskViewAt(mRecentsView.getPageNearestToCenterOfScreen()) + .getThumbnail().getSysUiStatusNavFlags(); + mActivity.getSystemUiController().updateUiState(UI_STATE_OVERVIEW, sysuiFlags); + } + } + @Override public void onRecentsAnimationStart(SwipeAnimationTargetSet targetSet) { DeviceProfile dp = InvariantDeviceProfile.INSTANCE.get(mContext).getDeviceProfile(mContext); @@ -838,9 +851,15 @@ public class WindowTransformSwipeHandler Interpolator interpolator = DEACCEL; final boolean goingToNewTask; if (mRecentsView != null) { - final int runningTaskIndex = mRecentsView.getRunningTaskIndex(); - final int taskToLaunch = mRecentsView.getNextPage(); - goingToNewTask = runningTaskIndex >= 0 && taskToLaunch != runningTaskIndex; + if (!mRecentsAnimationWrapper.hasTargets()) { + // If there are no running tasks, then we can assume that this is a continuation of + // the last gesture, but after the recents animation has finished + goingToNewTask = true; + } else { + final int runningTaskIndex = mRecentsView.getRunningTaskIndex(); + final int taskToLaunch = mRecentsView.getNextPage(); + goingToNewTask = runningTaskIndex >= 0 && taskToLaunch != runningTaskIndex; + } } else { goingToNewTask = false; } @@ -1058,10 +1077,11 @@ public class WindowTransformSwipeHandler final View floatingView = homeAnimationFactory.getFloatingView(); final boolean isFloatingIconView = floatingView instanceof FloatingIconView; - - RectFSpringAnim anim = new RectFSpringAnim(startRect, targetRect); + RectFSpringAnim anim = new RectFSpringAnim(startRect, targetRect, mActivity.getResources()); if (isFloatingIconView) { - anim.addAnimatorListener((FloatingIconView) floatingView); + FloatingIconView fiv = (FloatingIconView) floatingView; + anim.addAnimatorListener(fiv); + fiv.setOnTargetChangeListener(anim::onTargetPositionChanged); } AnimatorPlaybackController homeAnim = homeAnimationFactory.createActivityAnimationToHome(); @@ -1074,17 +1094,19 @@ public class WindowTransformSwipeHandler homeAnim.setPlayFraction(progress); - float iconAlpha = Utilities.mapToRange(interpolatedProgress, 0, - windowAlphaThreshold, 0f, 1f, Interpolators.LINEAR); - mTransformParams.setCurrentRectAndTargetAlpha(currentRect, 1f - iconAlpha); + float windowAlpha = Utilities.mapToRange(interpolatedProgress, 0, + windowAlphaThreshold, 1f, 0f, Interpolators.LINEAR); + mTransformParams.setProgress(progress) + .setCurrentRectAndTargetAlpha(currentRect, windowAlpha); mClipAnimationHelper.applyTransform(targetSet, mTransformParams, false /* launcherOnTop */); if (isFloatingIconView) { - ((FloatingIconView) floatingView).update(currentRect, iconAlpha, progress, + ((FloatingIconView) floatingView).update(currentRect, 1f, progress, windowAlphaThreshold, mClipAnimationHelper.getCurrentCornerRadius(), false); } + updateSysUiFlags(Math.max(progress, mCurrentShift.value)); }); anim.addAnimatorListener(new AnimationSuccessListener() { @Override @@ -1106,6 +1128,13 @@ public class WindowTransformSwipeHandler return anim; } + /** + * @return The GestureEndTarget if the gesture has ended, else null. + */ + public @Nullable GestureEndTarget getGestureEndTarget() { + return mGestureEndTarget; + } + @UiThread private void resumeLastTask() { mRecentsAnimationWrapper.finish(false /* toRecents */, null); @@ -1122,14 +1151,22 @@ public class WindowTransformSwipeHandler mRecentsView.getTaskViewAt(mRecentsView.getNextPage()).launchTask(false /* animate */, true /* freezeTaskList */); } else { + int taskId = mRecentsView.getTaskViewAt(mRecentsView.getNextPage()).getTask().key.id; + mFinishingRecentsAnimationForNewTaskId = taskId; mRecentsAnimationWrapper.finish(true /* toRecents */, () -> { - mRecentsView.getTaskViewAt(mRecentsView.getNextPage()).launchTask( - false /* animate */, true /* freezeTaskList */); + if (!mCanceled) { + TaskView nextTask = mRecentsView.getTaskView(taskId); + if (nextTask != null) { + nextTask.launchTask(false /* animate */, true /* freezeTaskList */); + doLogGesture(NEW_TASK); + } + reset(); + } + mCanceled = false; + mFinishingRecentsAnimationForNewTaskId = -1; }); } TOUCH_INTERACTION_LOG.addLog("finishRecentsAnimation", true); - doLogGesture(NEW_TASK); - reset(); } public void reset() { @@ -1140,12 +1177,26 @@ public class WindowTransformSwipeHandler * Cancels any running animation so that the active target can be overriden by a new swipe * handle (in case of quick switch). */ - public void cancelCurrentAnimation() { + public void cancelCurrentAnimation(SwipeSharedState sharedState) { + mCanceled = true; mCurrentShift.cancelAnimation(); if (mLauncherTransitionController != null && mLauncherTransitionController .getAnimationPlayer().isStarted()) { mLauncherTransitionController.getAnimationPlayer().cancel(); } + + if (mFinishingRecentsAnimationForNewTaskId != -1) { + // If we are canceling mid-starting a new task, switch to the screenshot since the + // recents animation has finished + switchToScreenshot(); + TaskView newRunningTaskView = mRecentsView.getTaskView( + mFinishingRecentsAnimationForNewTaskId); + int newRunningTaskId = newRunningTaskView != null + ? newRunningTaskView.getTask().key.id + : -1; + mRecentsView.setCurrentTask(newRunningTaskId); + sharedState.setRecentsAnimationFinishInterrupted(newRunningTaskId); + } } private void invalidateHandler() { @@ -1167,11 +1218,7 @@ public class WindowTransformSwipeHandler mLauncherTransitionController = null; } - mRecentsView.setEnableFreeScroll(true); - mRecentsView.setRunningTaskIconScaledDown(false); - mRecentsView.setOnScrollChangeListener(null); - mRecentsView.setRunningTaskHidden(false); - mRecentsView.setEnableDrawingLiveTile(true); + mRecentsView.onGestureAnimationEnd(); mActivity.getRootView().setOnApplyWindowInsetsListener(null); mActivity.getRootView().getOverlay().remove(mLiveTileOverlay); @@ -1192,6 +1239,9 @@ public class WindowTransformSwipeHandler private void switchToScreenshot() { if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { setStateOnUiThread(STATE_SCREENSHOT_CAPTURED); + } else if (!mRecentsAnimationWrapper.hasTargets()) { + // If there are no targets, then we don't need to capture anything + setStateOnUiThread(STATE_SCREENSHOT_CAPTURED); } else { boolean finishTransitionPosted = false; SwipeAnimationTargetSet controller = mRecentsAnimationWrapper.getController(); @@ -1239,6 +1289,9 @@ public class WindowTransformSwipeHandler private void finishCurrentTransitionToRecents() { if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { setStateOnUiThread(STATE_CURRENT_TASK_FINISHED); + } else if (!mRecentsAnimationWrapper.hasTargets()) { + // If there are no targets, then there is nothing to finish + setStateOnUiThread(STATE_CURRENT_TASK_FINISHED); } else { synchronized (mRecentsAnimationWrapper) { mRecentsAnimationWrapper.finish(true /* toRecents */, @@ -1265,10 +1318,7 @@ public class WindowTransformSwipeHandler } mActivityControlHelper.onSwipeUpComplete(mActivity); mRecentsAnimationWrapper.setCancelWithDeferredScreenshot(true); - - // Animate the first icon. - mRecentsView.animateUpRunningTaskIconScale(mLiveTileOverlay.cancelIconAnimation()); - mRecentsView.setSwipeDownShouldLaunchApp(true); + mRecentsView.onSwipeUpAnimationSuccess(); RecentsModel.INSTANCE.get(mContext).onOverviewShown(false, TAG); diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/hints/ProactiveHintsContainer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/hints/ProactiveHintsContainer.java new file mode 100644 index 0000000000..74a48517b2 --- /dev/null +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/hints/ProactiveHintsContainer.java @@ -0,0 +1,55 @@ +package com.android.quickstep.hints; + +import android.content.Context; +import android.util.AttributeSet; +import android.util.FloatProperty; +import android.view.View; +import android.widget.FrameLayout; + +public class ProactiveHintsContainer extends FrameLayout { + + public static final FloatProperty HINT_VISIBILITY = + new FloatProperty("hint_visibility") { + @Override + public void setValue(ProactiveHintsContainer proactiveHintsContainer, float v) { + proactiveHintsContainer.setHintVisibility(v); + } + + @Override + public Float get(ProactiveHintsContainer proactiveHintsContainer) { + return proactiveHintsContainer.mHintVisibility; + } + }; + + private float mHintVisibility; + + public ProactiveHintsContainer(Context context) { + super(context); + } + + public ProactiveHintsContainer(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public ProactiveHintsContainer(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public ProactiveHintsContainer(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + public void setView(View v) { + removeAllViews(); + addView(v); + } + + public void setHintVisibility(float v) { + if (v == 1) { + setVisibility(VISIBLE); + } else { + setVisibility(GONE); + } + mHintVisibility = v; + } +} diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/AccessibilityInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AccessibilityInputConsumer.java similarity index 99% rename from quickstep/recents_ui_overrides/src/com/android/quickstep/AccessibilityInputConsumer.java rename to quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AccessibilityInputConsumer.java index 8f8cd18fea..f8475ca182 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/AccessibilityInputConsumer.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AccessibilityInputConsumer.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.quickstep; +package com.android.quickstep.inputconsumers; import static android.view.MotionEvent.ACTION_CANCEL; import static android.view.MotionEvent.ACTION_DOWN; diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/AssistantTouchConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AssistantTouchConsumer.java similarity index 73% rename from quickstep/recents_ui_overrides/src/com/android/quickstep/AssistantTouchConsumer.java rename to quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AssistantTouchConsumer.java index 829e4783d8..0448fd14ac 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/AssistantTouchConsumer.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AssistantTouchConsumer.java @@ -14,16 +14,16 @@ * limitations under the License. */ -package com.android.quickstep; +package com.android.quickstep.inputconsumers; import static android.view.MotionEvent.ACTION_CANCEL; import static android.view.MotionEvent.ACTION_DOWN; import static android.view.MotionEvent.ACTION_MOVE; import static android.view.MotionEvent.ACTION_POINTER_UP; import static android.view.MotionEvent.ACTION_UP; - import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction.UPLEFT; import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction.UPRIGHT; +import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch.FLING; import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch.SWIPE; import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch.SWIPE_NOOP; import static com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType.NAVBAR; @@ -38,11 +38,12 @@ import android.os.SystemClock; import android.util.Log; import android.view.HapticFeedbackConstants; import android.view.MotionEvent; - import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.R; import com.android.launcher3.anim.Interpolators; import com.android.launcher3.logging.UserEventDispatcher; +import com.android.launcher3.touch.SwipeDetector; +import com.android.quickstep.ActivityControlHelper; import com.android.systemui.shared.recents.ISystemUiProxy; import com.android.systemui.shared.system.InputMonitorCompat; import com.android.systemui.shared.system.QuickStepContract; @@ -50,12 +51,15 @@ import com.android.systemui.shared.system.QuickStepContract; /** * Touch consumer for handling events to launch assistant from launcher */ -public class AssistantTouchConsumer extends DelegateInputConsumer { +public class AssistantTouchConsumer extends DelegateInputConsumer + implements SwipeDetector.Listener { + private static final String TAG = "AssistantTouchConsumer"; private static final long RETRACT_ANIMATION_DURATION_MS = 300; private static final String INVOCATION_TYPE_KEY = "invocation_type"; private static final int INVOCATION_TYPE_GESTURE = 1; + private static final int INVOCATION_TYPE_FLING = 6; private final PointF mDownPos = new PointF(); private final PointF mLastPos = new PointF(); @@ -77,6 +81,7 @@ public class AssistantTouchConsumer extends DelegateInputConsumer { private final float mSlop; private final ISystemUiProxy mSysUiProxy; private final Context mContext; + private final SwipeDetector mSwipeDetector; public AssistantTouchConsumer(Context context, ISystemUiProxy systemUiProxy, ActivityControlHelper activityControlHelper, InputConsumer delegate, @@ -90,6 +95,8 @@ public class AssistantTouchConsumer extends DelegateInputConsumer { mAngleThreshold = res.getInteger(R.integer.assistant_gesture_corner_deg_threshold); mSlop = QuickStepContract.getQuickStepDragSlopPx(); mActivityControlHelper = activityControlHelper; + mSwipeDetector = new SwipeDetector(mContext, this, SwipeDetector.VERTICAL); + mSwipeDetector.setDetectableScrollConditions(SwipeDetector.DIRECTION_POSITIVE, false); } @Override @@ -100,6 +107,7 @@ public class AssistantTouchConsumer extends DelegateInputConsumer { @Override public void onMotionEvent(MotionEvent ev) { // TODO add logging + mSwipeDetector.onTouchEvent(ev); switch (ev.getActionMasked()) { case ACTION_DOWN: { @@ -146,7 +154,7 @@ public class AssistantTouchConsumer extends DelegateInputConsumer { // Determine if angle is larger than threshold for assistant detection float angle = (float) Math.toDegrees( - Math.atan2(mDownPos.y - mLastPos.y, mDownPos.x - mLastPos.x)); + Math.atan2(mDownPos.y - mLastPos.y, mDownPos.x - mLastPos.x)); mDirection = angle > 90 ? UPLEFT : UPRIGHT; angle = angle > 90 ? 180 - angle : angle; @@ -159,7 +167,7 @@ public class AssistantTouchConsumer extends DelegateInputConsumer { } else { // Movement mDistance = (float) Math.hypot(mLastPos.x - mStartDragPos.x, - mLastPos.y - mStartDragPos.y); + mLastPos.y - mStartDragPos.y); if (mDistance >= 0) { final long diff = SystemClock.uptimeMillis() - mDragTime; mTimeFraction = Math.min(diff * 1f / mTimeThreshold, 1); @@ -172,18 +180,18 @@ public class AssistantTouchConsumer extends DelegateInputConsumer { case ACTION_UP: if (mState != STATE_DELEGATE_ACTIVE && !mLaunchedAssistant) { ValueAnimator animator = ValueAnimator.ofFloat(mLastProgress, 0) - .setDuration(RETRACT_ANIMATION_DURATION_MS); + .setDuration(RETRACT_ANIMATION_DURATION_MS); UserEventDispatcher.newInstance(mContext).logActionOnContainer( - SWIPE_NOOP, mDirection, NAVBAR); + SWIPE_NOOP, mDirection, NAVBAR); animator.addUpdateListener(valueAnimator -> { - float progress = (float) valueAnimator.getAnimatedValue(); - try { + float progress = (float) valueAnimator.getAnimatedValue(); + try { - mSysUiProxy.onAssistantProgress(progress); - } catch (RemoteException e) { - Log.w(TAG, "Failed to send SysUI start/send assistant progress: " - + progress, e); - } + mSysUiProxy.onAssistantProgress(progress); + } catch (RemoteException e) { + Log.w(TAG, "Failed to send SysUI start/send assistant progress: " + + progress, e); + } }); animator.setInterpolator(Interpolators.DEACCEL_2); animator.start(); @@ -200,38 +208,58 @@ public class AssistantTouchConsumer extends DelegateInputConsumer { private void updateAssistantProgress() { if (!mLaunchedAssistant) { - float progress = Math.min(mDistance * 1f / mDistThreshold, 1) * mTimeFraction; - mLastProgress = progress; - try { - mSysUiProxy.onAssistantProgress(progress); - if (mDistance >= mDistThreshold && mTimeFraction >= 1) { - UserEventDispatcher.newInstance(mContext).logActionOnContainer( - SWIPE, mDirection, NAVBAR); - Bundle args = new Bundle(); - args.putInt(INVOCATION_TYPE_KEY, INVOCATION_TYPE_GESTURE); - - BaseDraggingActivity launcherActivity = - mActivityControlHelper.getCreatedActivity(); - if (launcherActivity != null) { - launcherActivity.getRootView(). - performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, - HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING); - } - - mSysUiProxy.startAssistant(args); - mLaunchedAssistant = true; - } - } catch (RemoteException e) { - Log.w(TAG, "Failed to send SysUI start/send assistant progress: " + progress, e); - } + mLastProgress = Math.min(mDistance * 1f / mDistThreshold, 1) * mTimeFraction; + updateAssistant(SWIPE); } } - static boolean withinTouchRegion(Context context, MotionEvent ev) { + private void updateAssistant(int gestureType) { + try { + mSysUiProxy.onAssistantProgress(mLastProgress); + if (gestureType == FLING || (mDistance >= mDistThreshold && mTimeFraction >= 1)) { + UserEventDispatcher.newInstance(mContext) + .logActionOnContainer(gestureType, mDirection, NAVBAR); + Bundle args = new Bundle(); + args.putInt(INVOCATION_TYPE_KEY, INVOCATION_TYPE_GESTURE); + + BaseDraggingActivity launcherActivity = mActivityControlHelper.getCreatedActivity(); + if (launcherActivity != null) { + launcherActivity.getRootView().performHapticFeedback( + 13, // HapticFeedbackConstants.GESTURE_END + HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING); + } + + mSysUiProxy.startAssistant(args); + mLaunchedAssistant = true; + } + } catch (RemoteException e) { + Log.w(TAG, "Failed to send SysUI start/send assistant progress: " + mLastProgress, e); + } + } + + public static boolean withinTouchRegion(Context context, MotionEvent ev) { final Resources res = context.getResources(); final int width = res.getDisplayMetrics().widthPixels; final int height = res.getDisplayMetrics().heightPixels; final int size = res.getDimensionPixelSize(R.dimen.gestures_assistant_size); return (ev.getX() > width - size || ev.getX() < size) && ev.getY() > height - size; } + + @Override + public void onDragStart(boolean start) { + // do nothing + } + + @Override + public boolean onDrag(float displacement) { + return false; + } + + @Override + public void onDragEnd(float velocity, boolean fling) { + if (fling) { + mLastProgress = 1; + updateAssistant(FLING); + } + } } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/DelegateInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java similarity index 96% rename from quickstep/recents_ui_overrides/src/com/android/quickstep/DelegateInputConsumer.java rename to quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java index d36162f8be..311ddd27ca 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/DelegateInputConsumer.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java @@ -1,4 +1,4 @@ -package com.android.quickstep; +package com.android.quickstep.inputconsumers; import android.view.MotionEvent; diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/DeviceLockedInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java similarity index 97% rename from quickstep/recents_ui_overrides/src/com/android/quickstep/DeviceLockedInputConsumer.java rename to quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java index 7fd09f7dee..b1d175df85 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/DeviceLockedInputConsumer.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.quickstep; +package com.android.quickstep.inputconsumers; import android.content.Context; import android.content.Intent; diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/InputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/InputConsumer.java similarity index 95% rename from quickstep/recents_ui_overrides/src/com/android/quickstep/InputConsumer.java rename to quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/InputConsumer.java index 37b728871c..2e8880dde5 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/InputConsumer.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/InputConsumer.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.quickstep; +package com.android.quickstep.inputconsumers; import android.annotation.TargetApi; import android.os.Build; @@ -30,6 +30,7 @@ public interface InputConsumer { int TYPE_ASSISTANT = 1 << 3; int TYPE_DEVICE_LOCKED = 1 << 4; int TYPE_ACCESSIBILITY = 1 << 5; + int TYPE_SCREEN_PINNED = 1 << 6; InputConsumer NO_OP = () -> TYPE_NO_OP; diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java similarity index 93% rename from quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java rename to quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java index 7fc5d502f9..35b96ccc1b 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.quickstep; +package com.android.quickstep.inputconsumers; import static android.view.MotionEvent.ACTION_CANCEL; import static android.view.MotionEvent.ACTION_DOWN; @@ -21,8 +21,9 @@ import static android.view.MotionEvent.ACTION_MOVE; import static android.view.MotionEvent.ACTION_POINTER_UP; import static android.view.MotionEvent.ACTION_UP; import static android.view.MotionEvent.INVALID_POINTER_ID; - import static com.android.launcher3.Utilities.EDGE_NAV_BAR; +import static com.android.launcher3.uioverrides.RecentsUiFactory.ROTATION_LANDSCAPE; +import static com.android.launcher3.uioverrides.RecentsUiFactory.ROTATION_SEASCAPE; import static com.android.launcher3.util.RaceConditionTracker.ENTER; import static com.android.launcher3.util.RaceConditionTracker.EXIT; import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON; @@ -44,12 +45,20 @@ import android.view.VelocityTracker; import android.view.ViewConfiguration; import android.view.WindowManager; +import androidx.annotation.UiThread; + import com.android.launcher3.R; import com.android.launcher3.graphics.RotationMode; import com.android.launcher3.util.Preconditions; import com.android.launcher3.util.RaceConditionTracker; import com.android.launcher3.util.TraceHelper; +import com.android.quickstep.ActivityControlHelper; +import com.android.quickstep.OverviewCallbacks; +import com.android.quickstep.RecentsModel; +import com.android.quickstep.SwipeSharedState; +import com.android.quickstep.SysUINavigationMode; import com.android.quickstep.SysUINavigationMode.Mode; +import com.android.quickstep.WindowTransformSwipeHandler; import com.android.quickstep.WindowTransformSwipeHandler.GestureEndTarget; import com.android.quickstep.util.CachedEventDispatcher; import com.android.quickstep.util.MotionPauseDetector; @@ -62,8 +71,6 @@ import com.android.systemui.shared.system.QuickStepContract; import java.util.function.Consumer; -import androidx.annotation.UiThread; - /** * Input consumer for handling events originating from an activity other than Launcher */ @@ -108,7 +115,6 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC // Might be displacement in X or Y, depending on the direction we are swiping from the nav bar. private float mStartDisplacement; - private float mStartDisplacementX; private Handler mMainThreadHandler; private Runnable mCancelRecentsAnimationRunnable = () -> { @@ -167,8 +173,8 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC && !mRecentsViewDispatcher.hasConsumer()) { mRecentsViewDispatcher.setConsumer(mInteractionHandler.getRecentsViewDispatcher( isNavBarOnLeft() - ? RotationMode.SEASCAPE - : (isNavBarOnRight() ? RotationMode.LANDSCAPE : RotationMode.NORMAL))); + ? ROTATION_SEASCAPE + : (isNavBarOnRight() ? ROTATION_LANDSCAPE : RotationMode.NORMAL))); } int edgeFlags = ev.getEdgeFlags(); ev.setEdgeFlags(edgeFlags | EDGE_NAV_BAR); @@ -227,7 +233,6 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC if (Math.abs(displacement) > mDragSlop) { mPassedDragSlop = true; mStartDisplacement = displacement; - mStartDisplacementX = displacementX; } } } @@ -244,19 +249,20 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC if (!mPassedDragSlop) { mPassedDragSlop = true; mStartDisplacement = displacement; - mStartDisplacementX = displacementX; } notifyGestureStarted(); } } - if (mPassedDragSlop && mInteractionHandler != null) { - // Move - mInteractionHandler.updateDisplacement(displacement - mStartDisplacement); + if (mInteractionHandler != null) { + if (mPassedDragSlop) { + // Move + mInteractionHandler.updateDisplacement(displacement - mStartDisplacement); + } if (mMode == Mode.NO_BUTTON) { - float horizontalDist = Math.abs(displacementX - mStartDisplacementX); - float upDist = -(displacement - mStartDisplacement); + float horizontalDist = Math.abs(displacementX); + float upDist = -displacement; boolean isLikelyToStartNewTask = horizontalDist > upDist; mMotionPauseDetector.setDisallowPause(upDist < mMotionPauseMinDisplacement || isLikelyToStartNewTask); @@ -378,11 +384,11 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC // The consumer is being switched while we are active. Set up the shared state to be // used by the next animation removeListener(); - GestureEndTarget endTarget = mInteractionHandler.mGestureEndTarget; + GestureEndTarget endTarget = mInteractionHandler.getGestureEndTarget(); mSwipeSharedState.canGestureBeContinued = endTarget != null && endTarget.canBeContinued; mSwipeSharedState.goingToLauncher = endTarget != null && endTarget.isLauncher; if (mSwipeSharedState.canGestureBeContinued) { - mInteractionHandler.cancelCurrentAnimation(); + mInteractionHandler.cancelCurrentAnimation(mSwipeSharedState); } else { mInteractionHandler.reset(); } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java similarity index 96% rename from quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewInputConsumer.java rename to quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java index 2bb82e113a..5931da0b2c 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewInputConsumer.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.quickstep; +package com.android.quickstep.inputconsumers; import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; import static com.android.quickstep.TouchInteractionService.TOUCH_INTERACTION_LOG; @@ -27,6 +27,8 @@ import androidx.annotation.Nullable; import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.Utilities; import com.android.launcher3.views.BaseDragLayer; +import com.android.quickstep.ActivityControlHelper; +import com.android.quickstep.OverviewCallbacks; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.InputMonitorCompat; diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/ScreenPinnedInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/ScreenPinnedInputConsumer.java new file mode 100644 index 0000000000..a0e20f2cd8 --- /dev/null +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/ScreenPinnedInputConsumer.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2019 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.inputconsumers; + +import android.content.Context; +import android.os.RemoteException; +import android.util.Log; +import android.view.HapticFeedbackConstants; +import android.view.MotionEvent; + +import com.android.launcher3.BaseDraggingActivity; +import com.android.launcher3.R; +import com.android.quickstep.ActivityControlHelper; +import com.android.quickstep.util.MotionPauseDetector; +import com.android.systemui.shared.recents.ISystemUiProxy; + +/** + * An input consumer that detects swipe up and hold to exit screen pinning mode. + */ +public class ScreenPinnedInputConsumer implements InputConsumer { + + private static final String TAG = "ScreenPinnedConsumer"; + + private final float mMotionPauseMinDisplacement; + private final MotionPauseDetector mMotionPauseDetector; + + private float mTouchDownY; + + public ScreenPinnedInputConsumer(Context context, ISystemUiProxy sysuiProxy, + ActivityControlHelper activityControl) { + mMotionPauseMinDisplacement = context.getResources().getDimension( + R.dimen.motion_pause_detector_min_displacement_from_app); + mMotionPauseDetector = new MotionPauseDetector(context, true /* makePauseHarderToTrigger*/); + mMotionPauseDetector.setOnMotionPauseListener(isPaused -> { + if (isPaused) { + try { + sysuiProxy.stopScreenPinning(); + BaseDraggingActivity launcherActivity = activityControl.getCreatedActivity(); + if (launcherActivity != null) { + launcherActivity.getRootView().performHapticFeedback( + HapticFeedbackConstants.LONG_PRESS, + HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING); + } + mMotionPauseDetector.clear(); + } catch (RemoteException e) { + Log.e(TAG, "Unable to stop screen pinning ", e); + } + } + }); + } + + @Override + public int getType() { + return TYPE_SCREEN_PINNED; + } + + @Override + public void onMotionEvent(MotionEvent ev) { + float y = ev.getY(); + switch (ev.getAction()) { + case MotionEvent.ACTION_DOWN: + mTouchDownY = y; + break; + case MotionEvent.ACTION_MOVE: + float displacement = mTouchDownY - y; + mMotionPauseDetector.setDisallowPause(displacement < mMotionPauseMinDisplacement); + mMotionPauseDetector.addPosition(y, ev.getEventTime()); + break; + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + mMotionPauseDetector.clear(); + break; + } + } +} diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ClipAnimationHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ClipAnimationHelper.java index c9f4ab4378..3121dc379a 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ClipAnimationHelper.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ClipAnimationHelper.java @@ -35,12 +35,14 @@ import androidx.annotation.Nullable; import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.DeviceProfile; +import com.android.launcher3.LauncherState; import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.views.BaseDragLayer; import com.android.quickstep.RecentsModel; import com.android.quickstep.views.RecentsView; import com.android.quickstep.views.TaskThumbnailView; +import com.android.quickstep.views.TaskView; import com.android.systemui.shared.recents.ISystemUiProxy; import com.android.systemui.shared.recents.utilities.RectFEvaluator; import com.android.systemui.shared.system.RemoteAnimationTargetCompat; @@ -280,6 +282,21 @@ public class ClipAnimationHelper { } } + /** + * Compute scale and translation y such that the specified task view fills the screen. + */ + public LauncherState.ScaleAndTranslation getOverviewFullscreenScaleAndTranslation(TaskView v) { + TaskThumbnailView thumbnailView = v.getThumbnail(); + RecentsView recentsView = v.getRecentsView(); + fromTaskThumbnailView(thumbnailView, recentsView); + Rect taskSize = new Rect(); + recentsView.getTaskSize(taskSize); + updateTargetRect(taskSize); + float scale = mSourceRect.width() / mTargetRect.width(); + float translationY = mSourceRect.centerY() - mSourceRect.top - mTargetRect.centerY(); + return new LauncherState.ScaleAndTranslation(scale, 0, translationY); + } + private void updateStackBoundsToMultiWindowTaskSize(BaseDraggingActivity activity) { ISystemUiProxy sysUiProxy = RecentsModel.INSTANCE.get(activity).getSystemUiProxy(); if (sysUiProxy != null) { diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java index 7159e7c721..3f4ad58ab8 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java @@ -19,6 +19,7 @@ import android.animation.Animator; import android.animation.ObjectAnimator; import android.animation.PropertyValuesHolder; import android.animation.ValueAnimator; +import android.content.res.Resources; import android.graphics.PointF; import android.graphics.RectF; import android.util.FloatProperty; @@ -26,6 +27,7 @@ import android.util.FloatProperty; import androidx.dynamicanimation.animation.DynamicAnimation.OnAnimationEndListener; import androidx.dynamicanimation.animation.FloatPropertyCompat; +import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.anim.AnimationSuccessListener; import com.android.launcher3.anim.FlingSpringAnim; @@ -33,6 +35,8 @@ import com.android.launcher3.anim.FlingSpringAnim; import java.util.ArrayList; import java.util.List; +import static com.android.launcher3.anim.Interpolators.DEACCEL; + /** * Applies spring forces to animate from a starting rect to a target rect, * while providing update callbacks to the caller. @@ -45,7 +49,7 @@ public class RectFSpringAnim { * can be done in parallel at a fixed duration. Update callbacks are sent based on the progress * of this animation, while the end callback is sent after all animations finish. */ - private static final long RECT_SCALE_DURATION = 180; + private static final long RECT_SCALE_DURATION = 250; private static final FloatPropertyCompat RECT_CENTER_X = new FloatPropertyCompat("rectCenterXSpring") { @@ -61,16 +65,16 @@ public class RectFSpringAnim { } }; - private static final FloatPropertyCompat RECT_CENTER_Y = - new FloatPropertyCompat("rectCenterYSpring") { + private static final FloatPropertyCompat RECT_Y = + new FloatPropertyCompat("rectYSpring") { @Override public float getValue(RectFSpringAnim anim) { - return anim.mCurrentCenterY; + return anim.mCurrentY; } @Override - public void setValue(RectFSpringAnim anim, float currentCenterY) { - anim.mCurrentCenterY = currentCenterY; + public void setValue(RectFSpringAnim anim, float y) { + anim.mCurrentY = y; anim.onUpdate(); } }; @@ -96,7 +100,9 @@ public class RectFSpringAnim { private final List mAnimatorListeners = new ArrayList<>(); private float mCurrentCenterX; - private float mCurrentCenterY; + private float mCurrentY; + // If true, tracking the bottom of the rects, else tracking the top. + private boolean mTrackingBottomY; private float mCurrentScaleProgress; private FlingSpringAnim mRectXAnim; private FlingSpringAnim mRectYAnim; @@ -106,11 +112,33 @@ public class RectFSpringAnim { private boolean mRectYAnimEnded; private boolean mRectScaleAnimEnded; - public RectFSpringAnim(RectF startRect, RectF targetRect) { + private float mMinVisChange; + private float mYOvershoot; + + public RectFSpringAnim(RectF startRect, RectF targetRect, Resources resources) { mStartRect = startRect; mTargetRect = targetRect; mCurrentCenterX = mStartRect.centerX(); - mCurrentCenterY = mStartRect.centerY(); + + mTrackingBottomY = startRect.bottom < targetRect.bottom; + mCurrentY = mTrackingBottomY ? mStartRect.bottom : mStartRect.top; + + mMinVisChange = resources.getDimensionPixelSize(R.dimen.swipe_up_fling_min_visible_change); + mYOvershoot = resources.getDimensionPixelSize(R.dimen.swipe_up_y_overshoot); + } + + public void onTargetPositionChanged() { + if (mRectXAnim != null && mRectXAnim.getTargetPosition() != mTargetRect.centerX()) { + mRectXAnim.updatePosition(mCurrentCenterX, mTargetRect.centerX()); + } + + if (mRectYAnim != null) { + if (mTrackingBottomY && mRectYAnim.getTargetPosition() != mTargetRect.bottom) { + mRectYAnim.updatePosition(mCurrentY, mTargetRect.bottom); + } else if (!mTrackingBottomY && mRectYAnim.getTargetPosition() != mTargetRect.top) { + mRectYAnim.updatePosition(mCurrentY, mTargetRect.top); + } + } } public void addOnUpdateListener(OnUpdateListener onUpdateListener) { @@ -131,14 +159,28 @@ public class RectFSpringAnim { mRectYAnimEnded = true; maybeOnEnd(); }); - mRectXAnim = new FlingSpringAnim(this, RECT_CENTER_X, mCurrentCenterX, - mTargetRect.centerX(), velocityPxPerMs.x * 1000, onXEndListener); - mRectYAnim = new FlingSpringAnim(this, RECT_CENTER_Y, mCurrentCenterY, - mTargetRect.centerY(), velocityPxPerMs.y * 1000, onYEndListener); + + float startX = mCurrentCenterX; + float endX = mTargetRect.centerX(); + float minXValue = Math.min(startX, endX); + float maxXValue = Math.max(startX, endX); + mRectXAnim = new FlingSpringAnim(this, RECT_CENTER_X, startX, endX, + velocityPxPerMs.x * 1000, mMinVisChange, minXValue, maxXValue, 1f, onXEndListener); + + float startVelocityY = velocityPxPerMs.y * 1000; + // Scale the Y velocity based on the initial velocity to tune the curves. + float springVelocityFactor = 0.1f + 0.9f * Math.abs(startVelocityY) / 20000.0f; + float startY = mCurrentY; + float endY = mTrackingBottomY ? mTargetRect.bottom : mTargetRect.top; + float minYValue = Math.min(startY, endY - mYOvershoot); + float maxYValue = Math.max(startY, endY); + mRectYAnim = new FlingSpringAnim(this, RECT_Y, startY, endY, startVelocityY, + mMinVisChange, minYValue, maxYValue, springVelocityFactor, onYEndListener); mRectScaleAnim = ObjectAnimator.ofPropertyValuesHolder(this, PropertyValuesHolder.ofFloat(RECT_SCALE_PROGRESS, 1)) .setDuration(RECT_SCALE_DURATION); + mRectScaleAnim.setInterpolator(DEACCEL); mRectScaleAnim.addListener(new AnimationSuccessListener() { @Override public void onAnimationSuccess(Animator animator) { @@ -170,8 +212,13 @@ public class RectFSpringAnim { mTargetRect.width()); float currentHeight = Utilities.mapRange(mCurrentScaleProgress, mStartRect.height(), mTargetRect.height()); - mCurrentRect.set(mCurrentCenterX - currentWidth / 2, mCurrentCenterY - currentHeight / 2, - mCurrentCenterX + currentWidth / 2, mCurrentCenterY + currentHeight / 2); + if (mTrackingBottomY) { + mCurrentRect.set(mCurrentCenterX - currentWidth / 2, mCurrentY - currentHeight, + mCurrentCenterX + currentWidth / 2, mCurrentY); + } else { + mCurrentRect.set(mCurrentCenterX - currentWidth / 2, mCurrentY, + mCurrentCenterX + currentWidth / 2, mCurrentY + currentHeight); + } for (OnUpdateListener onUpdateListener : mOnUpdateListeners) { onUpdateListener.onUpdate(mCurrentRect, mCurrentScaleProgress); } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/SwipeAnimationTargetSet.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/SwipeAnimationTargetSet.java index 83973fa7ec..f5a9e8a058 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/SwipeAnimationTargetSet.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/SwipeAnimationTargetSet.java @@ -53,6 +53,20 @@ public class SwipeAnimationTargetSet extends RemoteAnimationTargetSet { this.mOnFinishListener = onFinishListener; } + public boolean hasTargets() { + return unfilteredApps.length != 0; + } + + /** + * Clones the target set without any actual targets. Used only when continuing a gesture after + * the actual recents animation has finished. + */ + public SwipeAnimationTargetSet cloneWithoutTargets() { + return new SwipeAnimationTargetSet(controller, new RemoteAnimationTargetCompat[0], + homeContentInsets, minimizedHomeBounds, mShouldMinimizeSplitScreen, + mOnFinishListener); + } + public void finishController(boolean toRecents, Runnable callback, boolean sendUserLeaveHint) { mOnFinishListener.accept(this); BACKGROUND_EXECUTOR.execute(() -> { diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java index d6f2235031..bdac750de7 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java @@ -34,6 +34,8 @@ import android.os.Build; import android.util.AttributeSet; import android.view.View; +import androidx.annotation.Nullable; + import com.android.launcher3.DeviceProfile; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherState; @@ -41,8 +43,11 @@ import com.android.launcher3.R; import com.android.launcher3.anim.Interpolators; import com.android.launcher3.appprediction.PredictionUiStateManager; import com.android.launcher3.appprediction.PredictionUiStateManager.Client; +import com.android.launcher3.util.PendingAnimation; +import com.android.launcher3.views.BaseDragLayer; import com.android.launcher3.views.ScrimView; import com.android.quickstep.SysUINavigationMode; +import com.android.quickstep.hints.ProactiveHintsContainer; import com.android.quickstep.util.ClipAnimationHelper; import com.android.quickstep.util.ClipAnimationHelper.TransformParams; import com.android.quickstep.util.LayoutUtils; @@ -54,6 +59,8 @@ import com.android.quickstep.util.LayoutUtils; public class LauncherRecentsView extends RecentsView { private final TransformParams mTransformParams = new TransformParams(); + private final int mChipOverhang; + @Nullable private ProactiveHintsContainer mProactiveHintsContainer; public LauncherRecentsView(Context context) { this(context, null); @@ -66,6 +73,16 @@ public class LauncherRecentsView extends RecentsView { public LauncherRecentsView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); setContentAlpha(0); + mChipOverhang = (int) context.getResources().getDimension(R.dimen.chip_hint_overhang); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + View hintContainer = mActivity.findViewById(R.id.hints); + mProactiveHintsContainer = + hintContainer instanceof ProactiveHintsContainer + ? (ProactiveHintsContainer) hintContainer : null; } @Override @@ -84,6 +101,11 @@ public class LauncherRecentsView extends RecentsView { } } + @Nullable + public ProactiveHintsContainer getProactiveHintsContainer() { + return mProactiveHintsContainer; + } + @Override public void draw(Canvas canvas) { maybeDrawEmptyMessage(canvas); @@ -137,6 +159,48 @@ public class LauncherRecentsView extends RecentsView { @Override protected void getTaskSize(DeviceProfile dp, Rect outRect) { LayoutUtils.calculateLauncherTaskSize(getContext(), dp, outRect); + if (mProactiveHintsContainer != null) { + BaseDragLayer.LayoutParams params = (BaseDragLayer.LayoutParams) mProactiveHintsContainer.getLayoutParams(); + params.bottomMargin = getHeight() - outRect.bottom - mChipOverhang; + params.width = outRect.width(); + } + } + + @Override + public PendingAnimation createTaskLauncherAnimation(TaskView tv, long duration) { + PendingAnimation anim = super.createTaskLauncherAnimation(tv, duration); + + if (mProactiveHintsContainer != null) { + anim.anim.play(ObjectAnimator.ofFloat( + mProactiveHintsContainer, ProactiveHintsContainer.HINT_VISIBILITY, 0)); + } + + return anim; + } + + @Override + public PendingAnimation createTaskDismissAnimation(TaskView taskView, boolean animateTaskView, + boolean shouldRemoveTask, long duration) { + PendingAnimation anim = super.createTaskDismissAnimation(taskView, animateTaskView, + shouldRemoveTask, duration); + + if (mProactiveHintsContainer != null) { + anim.anim.play(ObjectAnimator.ofFloat( + mProactiveHintsContainer, ProactiveHintsContainer.HINT_VISIBILITY, 0)); + anim.addEndListener(onEndListener -> { + if (!onEndListener.isSuccess) { + mProactiveHintsContainer.setHintVisibility(1); + } + }); + } + + return anim; + } + + public void setHintVisibility(float v) { + if (mProactiveHintsContainer != null) { + mProactiveHintsContainer.setHintVisibility(v); + } } @Override diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java index b5f90a5550..525ead836a 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java @@ -744,9 +744,7 @@ public abstract class RecentsView extends PagedView impl public abstract void startHome(); public void reset() { - setRunningTaskViewShowScreenshot(false); - mRunningTaskId = -1; - mRunningTaskTileHidden = false; + setCurrentTask(-1); mIgnoreResetTaskId = -1; mTaskListChangeId = -1; @@ -757,6 +755,15 @@ public abstract class RecentsView extends PagedView impl setCurrentPage(0); } + public @Nullable TaskView getRunningTaskView() { + return getTaskView(mRunningTaskId); + } + + public int getRunningTaskIndex() { + TaskView tv = getRunningTaskView(); + return tv == null ? -1 : indexOfChild(tv); + } + /** * Reloads the view if anything in recents changed. */ @@ -767,14 +774,50 @@ public abstract class RecentsView extends PagedView impl } /** - * Ensures that the first task in the view represents {@param task} and reloads the view - * if needed. This allows the swipe-up gesture to assume that the first tile always - * corresponds to the correct task. - * All subsequent calls to reload will keep the task as the first item until {@link #reset()} - * is called. - * Also scrolls the view to this task + * Called when a gesture from an app is starting. */ - public void showTask(int runningTaskId) { + public void onGestureAnimationStart(int runningTaskId) { + // This needs to be called before the other states are set since it can create the task view + showCurrentTask(runningTaskId); + setEnableFreeScroll(false); + setEnableDrawingLiveTile(false); + setRunningTaskHidden(true); + setRunningTaskIconScaledDown(true); + } + + /** + * Called only when a swipe-up gesture from an app has completed. Only called after + * {@link #onGestureAnimationStart} and {@link #onGestureAnimationEnd()}. + */ + public void onSwipeUpAnimationSuccess() { + if (getRunningTaskView() != null) { + float startProgress = ENABLE_QUICKSTEP_LIVE_TILE.get() + ? mLiveTileOverlay.cancelIconAnimation() + : 0f; + animateUpRunningTaskIconScale(startProgress); + } + setSwipeDownShouldLaunchApp(true); + } + + /** + * Called when a gesture from an app has finished. + */ + public void onGestureAnimationEnd() { + setEnableFreeScroll(true); + setEnableDrawingLiveTile(true); + setOnScrollChangeListener(null); + setRunningTaskViewShowScreenshot(true); + setRunningTaskHidden(false); + animateUpRunningTaskIconScale(); + } + + /** + * Creates a task view (if necessary) to represent the task with the {@param runningTaskId}. + * + * All subsequent calls to reload will keep the task as the first item until {@link #reset()} + * is called. Also scrolls the view to this task. + */ + public void showCurrentTask(int runningTaskId) { if (getChildCount() == 0) { // Add an empty view for now until the task plan is loaded and applied final TaskView taskView = mTaskViewPool.getView(); @@ -789,16 +832,33 @@ public abstract class RecentsView extends PagedView impl new ComponentName("", ""), false); taskView.bind(mTmpRunningTask); } + + boolean runningTaskTileHidden = mRunningTaskTileHidden; setCurrentTask(runningTaskId); + setCurrentPage(getRunningTaskIndex()); + setRunningTaskViewShowScreenshot(false); + setRunningTaskHidden(runningTaskTileHidden); + + // Reload the task list + mTaskListChangeId = mModel.getTasks(this::applyLoadPlan); } - public @Nullable TaskView getRunningTaskView() { - return getTaskView(mRunningTaskId); - } + /** + * Sets the running task id, cleaning up the old running task if necessary. + * @param runningTaskId + */ + public void setCurrentTask(int runningTaskId) { + if (mRunningTaskId == runningTaskId) { + return; + } - public int getRunningTaskIndex() { - TaskView tv = getRunningTaskView(); - return tv == null ? -1 : indexOfChild(tv); + if (mRunningTaskId != -1) { + // Reset the state on the old running task view + setRunningTaskIconScaledDown(false); + setRunningTaskViewShowScreenshot(true); + setRunningTaskHidden(false); + } + mRunningTaskId = runningTaskId; } /** @@ -812,27 +872,6 @@ public abstract class RecentsView extends PagedView impl } } - /** - * Similar to {@link #showTask(int)} but does not put any restrictions on the first tile. - */ - public void setCurrentTask(int runningTaskId) { - boolean runningTaskTileHidden = mRunningTaskTileHidden; - boolean runningTaskIconScaledDown = mRunningTaskIconScaledDown; - - setRunningTaskIconScaledDown(false); - setRunningTaskHidden(false); - setRunningTaskViewShowScreenshot(true); - mRunningTaskId = runningTaskId; - setRunningTaskViewShowScreenshot(false); - setRunningTaskIconScaledDown(runningTaskIconScaledDown); - setRunningTaskHidden(runningTaskTileHidden); - - setCurrentPage(getRunningTaskIndex()); - - // Load the tasks (if the loading is already - mTaskListChangeId = mModel.getTasks(this::applyLoadPlan); - } - private void setRunningTaskViewShowScreenshot(boolean showScreenshot) { if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { TaskView runningTaskView = getRunningTaskView(); @@ -1520,7 +1559,9 @@ public abstract class RecentsView extends PagedView impl public abstract boolean shouldUseMultiWindowTaskSizeStrategy(); protected void onTaskLaunched(boolean success) { - resetTaskVisuals(); + if (success) { + resetTaskVisuals(); + } } @Override diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java index 7e15d523d3..a9184ecefb 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java @@ -33,6 +33,7 @@ import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.Rect; +import android.graphics.RectF; import android.graphics.Shader; import android.util.AttributeSet; import android.util.FloatProperty; @@ -59,7 +60,7 @@ public class TaskThumbnailView extends View { private final static ColorMatrix COLOR_MATRIX = new ColorMatrix(); private final static ColorMatrix SATURATION_COLOR_MATRIX = new ColorMatrix(); - private final static Rect EMPTY_RECT = new Rect(); + private final static RectF EMPTY_RECT_F = new RectF(); public static final Property DIM_ALPHA = new FloatProperty("dimAlpha") { @@ -87,10 +88,9 @@ public class TaskThumbnailView extends View { private final Matrix mMatrix = new Matrix(); private float mClipBottom = -1; - private Rect mScaledInsets = new Rect(); - private Rect mCurrentDrawnInsets = new Rect(); - private float mCurrentDrawnCornerRadius; - private boolean mIsRotated; + // Contains the portion of the thumbnail that is clipped when fullscreen progress = 0. + private RectF mClippedInsets = new RectF(); + private TaskView.FullscreenDrawParams mFullscreenParams; private Task mTask; private ThumbnailData mThumbnailData; @@ -118,7 +118,7 @@ public class TaskThumbnailView extends View { mDimmingPaintAfterClearing.setColor(Color.BLACK); mActivity = BaseActivity.fromContext(context); mIsDarkTextTheme = Themes.getAttrBoolean(mActivity, R.attr.isWorkspaceDarkText); - setCurrentDrawnInsetsAndRadius(EMPTY_RECT, mCornerRadius); + mFullscreenParams = new TaskView.FullscreenDrawParams(mCornerRadius); } public void bind(Task task) { @@ -201,23 +201,27 @@ public class TaskThumbnailView extends View { @Override protected void onDraw(Canvas canvas) { + RectF currentDrawnInsets = mFullscreenParams.mCurrentDrawnInsets; + canvas.save(); + canvas.translate(currentDrawnInsets.left, currentDrawnInsets.top); + canvas.scale(mFullscreenParams.mScale, mFullscreenParams.mScale); // Draw the insets if we're being drawn fullscreen (we do this for quick switch). drawOnCanvas(canvas, - -mCurrentDrawnInsets.left, - -mCurrentDrawnInsets.top, - getMeasuredWidth() + mCurrentDrawnInsets.right, - getMeasuredHeight() + mCurrentDrawnInsets.bottom, - mCurrentDrawnCornerRadius); + -currentDrawnInsets.left, + -currentDrawnInsets.top, + getMeasuredWidth() + currentDrawnInsets.right, + getMeasuredHeight() + currentDrawnInsets.bottom, + mFullscreenParams.mCurrentDrawnCornerRadius); + canvas.restore(); } - public Rect getInsetsToDrawInFullscreen(boolean isMultiWindowMode) { - // Don't show insets in the wrong orientation or in multi window mode. - return mIsRotated || isMultiWindowMode ? EMPTY_RECT : mScaledInsets; + public RectF getInsetsToDrawInFullscreen(boolean isMultiWindowMode) { + // Don't show insets in multi window mode. + return isMultiWindowMode ? EMPTY_RECT_F : mClippedInsets; } - public void setCurrentDrawnInsetsAndRadius(Rect insets, float radius) { - mCurrentDrawnInsets.set(insets); - mCurrentDrawnCornerRadius = radius; + public void setFullscreenParams(TaskView.FullscreenDrawParams fullscreenParams) { + mFullscreenParams = fullscreenParams; invalidate(); } @@ -275,7 +279,7 @@ public class TaskThumbnailView extends View { } private void updateThumbnailMatrix() { - mIsRotated = false; + boolean isRotated = false; mClipBottom = -1; if (mBitmapShader != null && mThumbnailData != null) { float scale = mThumbnailData.scale; @@ -296,30 +300,28 @@ public class TaskThumbnailView extends View { final Configuration configuration = getContext().getResources().getConfiguration(); // Rotate the screenshot if not in multi-window mode - mIsRotated = FeatureFlags.OVERVIEW_USE_SCREENSHOT_ORIENTATION && + isRotated = FeatureFlags.OVERVIEW_USE_SCREENSHOT_ORIENTATION && configuration.orientation != mThumbnailData.orientation && !mActivity.isInMultiWindowMode() && mThumbnailData.windowingMode == WINDOWING_MODE_FULLSCREEN; // Scale the screenshot to always fit the width of the card. - thumbnailScale = mIsRotated + thumbnailScale = isRotated ? getMeasuredWidth() / thumbnailHeight : getMeasuredWidth() / thumbnailWidth; } - mScaledInsets.set(thumbnailInsets); - Utilities.scaleRect(mScaledInsets, thumbnailScale); - - if (mIsRotated) { + if (isRotated) { int rotationDir = profile.isVerticalBarLayout() && !profile.isSeascape() ? -1 : 1; mMatrix.setRotate(90 * rotationDir); int newLeftInset = rotationDir == 1 ? thumbnailInsets.bottom : thumbnailInsets.top; int newTopInset = rotationDir == 1 ? thumbnailInsets.left : thumbnailInsets.right; - mMatrix.postTranslate(-newLeftInset * scale, -newTopInset * scale); + mClippedInsets.offsetTo(newLeftInset * scale, newTopInset * scale); if (rotationDir == -1) { // Crop the right/bottom side of the screenshot rather than left/top float excessHeight = thumbnailWidth * thumbnailScale - getMeasuredHeight(); - mMatrix.postTranslate(0, -excessHeight); + mClippedInsets.offset(0, excessHeight); } + mMatrix.postTranslate(-mClippedInsets.left, -mClippedInsets.top); // Move the screenshot to the thumbnail window (rotation moved it out). if (rotationDir == 1) { mMatrix.postTranslate(mThumbnailData.thumbnail.getHeight(), 0); @@ -327,13 +329,28 @@ public class TaskThumbnailView extends View { mMatrix.postTranslate(0, mThumbnailData.thumbnail.getWidth()); } } else { - mMatrix.setTranslate(-mThumbnailData.insets.left * scale, - -mThumbnailData.insets.top * scale); + mClippedInsets.offsetTo(thumbnailInsets.left * scale, thumbnailInsets.top * scale); + mMatrix.setTranslate(-mClippedInsets.left, -mClippedInsets.top); } + + final float widthWithInsets; + final float heightWithInsets; + if (isRotated) { + widthWithInsets = mThumbnailData.thumbnail.getHeight() * thumbnailScale; + heightWithInsets = mThumbnailData.thumbnail.getWidth() * thumbnailScale; + } else { + widthWithInsets = mThumbnailData.thumbnail.getWidth() * thumbnailScale; + heightWithInsets = mThumbnailData.thumbnail.getHeight() * thumbnailScale; + } + mClippedInsets.left *= thumbnailScale; + mClippedInsets.top *= thumbnailScale; + mClippedInsets.right = widthWithInsets - mClippedInsets.left - getMeasuredWidth(); + mClippedInsets.bottom = heightWithInsets - mClippedInsets.top - getMeasuredHeight(); + mMatrix.postScale(thumbnailScale, thumbnailScale); mBitmapShader.setLocalMatrix(mMatrix); - float bitmapHeight = Math.max((mIsRotated ? thumbnailWidth : thumbnailHeight) + float bitmapHeight = Math.max((isRotated ? thumbnailWidth : thumbnailHeight) * thumbnailScale, 0); if (Math.round(bitmapHeight) < getMeasuredHeight()) { mClipBottom = bitmapHeight; @@ -341,7 +358,7 @@ public class TaskThumbnailView extends View { mPaint.setShader(mBitmapShader); } - if (mIsRotated) { + if (isRotated) { // The overlay doesn't really work when the screenshot is rotated, so don't add it. mOverlay.reset(); } else { diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java index 6cd46d94a1..2b86f5e109 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java @@ -32,6 +32,7 @@ import android.content.Context; import android.content.res.Resources; import android.graphics.Outline; import android.graphics.Rect; +import android.graphics.RectF; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.Handler; @@ -166,7 +167,7 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { private float mCurveScale; private float mZoomScale; private float mFullscreenProgress; - private final Rect mCurrentDrawnInsets = new Rect(); + private final FullscreenDrawParams mCurrentFullscreenParams; private final float mCornerRadius; private final float mWindowCornerRadius; private final BaseDraggingActivity mActivity; @@ -214,7 +215,8 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { }); mCornerRadius = TaskCornerRadius.get(context); mWindowCornerRadius = QuickStepContract.getWindowCornerRadius(context.getResources()); - mOutlineProvider = new TaskOutlineProvider(getResources(), mCornerRadius); + mCurrentFullscreenParams = new FullscreenDrawParams(mCornerRadius); + mOutlineProvider = new TaskOutlineProvider(getResources(), mCurrentFullscreenParams); setOutlineProvider(mOutlineProvider); } @@ -540,26 +542,26 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { private static final class TaskOutlineProvider extends ViewOutlineProvider { private final int mMarginTop; - private final Rect mInsets = new Rect(); - private float mRadius; + private FullscreenDrawParams mFullscreenParams; - TaskOutlineProvider(Resources res, float radius) { + TaskOutlineProvider(Resources res, FullscreenDrawParams fullscreenParams) { mMarginTop = res.getDimensionPixelSize(R.dimen.task_thumbnail_top_margin); - mRadius = radius; + mFullscreenParams = fullscreenParams; } - public void setCurrentDrawnInsetsAndRadius(Rect insets, float radius) { - mInsets.set(insets); - mRadius = radius; + public void setFullscreenParams(FullscreenDrawParams params) { + mFullscreenParams = params; } @Override public void getOutline(View view, Outline outline) { - outline.setRoundRect(-mInsets.left, - mMarginTop - mInsets.top, - view.getWidth() + mInsets.right, - view.getHeight() + mInsets.bottom, - mRadius); + RectF insets = mFullscreenParams.mCurrentDrawnInsets; + float scale = mFullscreenParams.mScale; + outline.setRoundRect(0, + (int) (mMarginTop * scale), + (int) ((insets.left + view.getWidth() + insets.right) * scale), + (int) ((insets.top + view.getHeight() + insets.bottom) * scale), + mFullscreenParams.mCurrentDrawnCornerRadius); } } @@ -658,17 +660,25 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { TaskThumbnailView thumbnail = getThumbnail(); boolean isMultiWindowMode = mActivity.getDeviceProfile().isMultiWindowMode; - Rect insets = thumbnail.getInsetsToDrawInFullscreen(isMultiWindowMode); - mCurrentDrawnInsets.set((int) (insets.left * mFullscreenProgress), - (int) (insets.top * mFullscreenProgress), - (int) (insets.right * mFullscreenProgress), - (int) (insets.bottom * mFullscreenProgress)); + RectF insets = thumbnail.getInsetsToDrawInFullscreen(isMultiWindowMode); + float currentInsetsLeft = insets.left * mFullscreenProgress; + float currentInsetsRight = insets.right * mFullscreenProgress; + mCurrentFullscreenParams.setInsets(currentInsetsLeft, + insets.top * mFullscreenProgress, + currentInsetsRight, + insets.bottom * mFullscreenProgress); float fullscreenCornerRadius = isMultiWindowMode ? 0 : mWindowCornerRadius; - float cornerRadius = Utilities.mapRange(mFullscreenProgress, mCornerRadius, - fullscreenCornerRadius) / getRecentsView().getScaleX(); + mCurrentFullscreenParams.setCornerRadius(Utilities.mapRange(mFullscreenProgress, + mCornerRadius, fullscreenCornerRadius) / getRecentsView().getScaleX()); + // We scaled the thumbnail to fit the content (excluding insets) within task view width. + // Now that we are drawing left/right insets again, we need to scale down to fit them. + if (getWidth() > 0) { + mCurrentFullscreenParams.setScale(getWidth() + / (getWidth() + currentInsetsLeft + currentInsetsRight)); + } - thumbnail.setCurrentDrawnInsetsAndRadius(mCurrentDrawnInsets, cornerRadius); - mOutlineProvider.setCurrentDrawnInsetsAndRadius(mCurrentDrawnInsets, cornerRadius); + thumbnail.setFullscreenParams(mCurrentFullscreenParams); + mOutlineProvider.setFullscreenParams(mCurrentFullscreenParams); invalidateOutline(); } @@ -686,4 +696,30 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { } return mShowScreenshot; } + + /** + * We update and subsequently draw these in {@link #setFullscreenProgress(float)}. + */ + static class FullscreenDrawParams { + RectF mCurrentDrawnInsets = new RectF(); + float mCurrentDrawnCornerRadius; + /** The current scale we apply to the thumbnail to adjust for new left/right insets. */ + float mScale = 1; + + public FullscreenDrawParams(float cornerRadius) { + setCornerRadius(cornerRadius); + } + + public void setInsets(float left, float top, float right, float bottom) { + mCurrentDrawnInsets.set(left, top, right, bottom); + } + + public void setCornerRadius(float cornerRadius) { + mCurrentDrawnCornerRadius = cornerRadius; + } + + public void setScale(float scale) { + mScale = scale; + } + } } diff --git a/quickstep/res/layout/task.xml b/quickstep/res/layout/task.xml index 1d1c2723fb..d1ef631075 100644 --- a/quickstep/res/layout/task.xml +++ b/quickstep/res/layout/task.xml @@ -61,4 +61,14 @@ android:gravity="center_vertical" /> + + \ No newline at end of file diff --git a/quickstep/res/values-af/strings.xml b/quickstep/res/values-af/strings.xml index 88954b2270..64b8e2c3b1 100644 --- a/quickstep/res/values-af/strings.xml +++ b/quickstep/res/values-af/strings.xml @@ -31,4 +31,7 @@ "%1$s, %2$s" "< 1 minuut" "%1$s oor vandag" + "Programvoorstelle" + "Alle programme" + "Jou voorspelde programme" diff --git a/quickstep/res/values-am/strings.xml b/quickstep/res/values-am/strings.xml index 34fb3be414..3daa92226c 100644 --- a/quickstep/res/values-am/strings.xml +++ b/quickstep/res/values-am/strings.xml @@ -31,4 +31,7 @@ "%1$s%2$s" "< 1 ደቂቃ" "ዛሬ %1$s ቀርቷል" + "የመተግበሪያ ጥቆማዎች" + "ሁሉም መተግበሪያዎች" + "የእርስዎ የሚገመቱ መተግበሪያዎች" diff --git a/quickstep/res/values-ar/strings.xml b/quickstep/res/values-ar/strings.xml index ebdcf7363a..73c7c5c033 100644 --- a/quickstep/res/values-ar/strings.xml +++ b/quickstep/res/values-ar/strings.xml @@ -31,4 +31,7 @@ "%1$s، %2$s" "أقل من دقيقة" "يتبقى اليوم %1$s." + "اقتراحات التطبيقات" + "جميع التطبيقات" + "تطبيقاتك المتوقّعة" diff --git a/quickstep/res/values-az/strings.xml b/quickstep/res/values-az/strings.xml index 02312f49e4..aa8fa536ac 100644 --- a/quickstep/res/values-az/strings.xml +++ b/quickstep/res/values-az/strings.xml @@ -31,4 +31,7 @@ "%1$s, %2$s" "< 1 dəq" "Bu gün %1$s qaldı" + "Tətbiq təklifləri" + "Bütün tətbiqlər" + "Təklif edilən tətbiqlər" diff --git a/quickstep/res/values-b+sr+Latn/strings.xml b/quickstep/res/values-b+sr+Latn/strings.xml index baab4a12f2..fbbe9d2dc5 100644 --- a/quickstep/res/values-b+sr+Latn/strings.xml +++ b/quickstep/res/values-b+sr+Latn/strings.xml @@ -31,4 +31,7 @@ "%1$s, %2$s" "< 1 min" "Još %1$s danas" + "Predlozi aplikacija" + "Sve aplikacije" + "Predviđene aplikacije" diff --git a/quickstep/res/values-be/strings.xml b/quickstep/res/values-be/strings.xml index b28f3771a6..c4a277267c 100644 --- a/quickstep/res/values-be/strings.xml +++ b/quickstep/res/values-be/strings.xml @@ -31,4 +31,7 @@ "%1$s, %2$s" "< 1 хв" "Сёння засталося %1$s" + "Прапановы праграм" + "Усе праграмы" + "Вашы праграмы з падказак" diff --git a/quickstep/res/values-bg/strings.xml b/quickstep/res/values-bg/strings.xml index 0475c0d1f6..9e8c54a9c7 100644 --- a/quickstep/res/values-bg/strings.xml +++ b/quickstep/res/values-bg/strings.xml @@ -31,4 +31,7 @@ "%1$s, %2$s" "< 1 мин" "Оставащо време днес: %1$s" + "Предложения за приложения" + "Всички приложения" + "Предвидени приложения" diff --git a/quickstep/res/values-bn/strings.xml b/quickstep/res/values-bn/strings.xml index e6764e0161..57f92e5a0c 100644 --- a/quickstep/res/values-bn/strings.xml +++ b/quickstep/res/values-bn/strings.xml @@ -31,4 +31,7 @@ "%1$s, %2$s" "< ১ মি." "আজকে %1$s বাকি আছে" + "অ্যাপের সাজেশন" + "সব অ্যাপ" + "আপনার প্রয়োজন হতে পারে এমন অ্যাপ" diff --git a/quickstep/res/values-bs/strings.xml b/quickstep/res/values-bs/strings.xml index 77b4c46073..7968f7cc68 100644 --- a/quickstep/res/values-bs/strings.xml +++ b/quickstep/res/values-bs/strings.xml @@ -31,4 +31,7 @@ "%1$s, %2$s" "< 1 min" "Preostalo vrijeme: %1$s" + "Prijedlozi za aplikacije" + "Sve aplikacije" + "Predviđene aplikacije" diff --git a/quickstep/res/values-ca/strings.xml b/quickstep/res/values-ca/strings.xml index 484f445db7..6420aa8cb8 100644 --- a/quickstep/res/values-ca/strings.xml +++ b/quickstep/res/values-ca/strings.xml @@ -31,4 +31,7 @@ "%1$s; %2$s" "< 1 minut" "temps restant avui: %1$s" + "Suggeriments d\'aplicacions" + "Totes les aplicacions" + "Prediccions d\'aplicacions" diff --git a/quickstep/res/values-cs/strings.xml b/quickstep/res/values-cs/strings.xml index a698d49dee..194ff87dc5 100644 --- a/quickstep/res/values-cs/strings.xml +++ b/quickstep/res/values-cs/strings.xml @@ -31,4 +31,7 @@ "%1$s, %2$s" "< 1 minuta" "dnes zbývá: %1$s" + "Návrhy aplikací" + "Všechny aplikace" + "Vaše předpovídané aplikace" diff --git a/quickstep/res/values-da/strings.xml b/quickstep/res/values-da/strings.xml index b3e8524f8d..b43a76eb94 100644 --- a/quickstep/res/values-da/strings.xml +++ b/quickstep/res/values-da/strings.xml @@ -31,4 +31,7 @@ "%1$s, %2$s" "< 1 min" "%1$s tilbage i dag" + "Appforslag" + "Alle apps" + "Dine foreslåede apps" diff --git a/quickstep/res/values-de/strings.xml b/quickstep/res/values-de/strings.xml index 10e4fd7ce6..7f4e56d260 100644 --- a/quickstep/res/values-de/strings.xml +++ b/quickstep/res/values-de/strings.xml @@ -31,4 +31,7 @@ "%1$s, %2$s" "< 1 min" "Heute noch %1$s" + "App-Vorschläge" + "Alle Apps" + "App-Vorschläge für dich" diff --git a/quickstep/res/values-el/strings.xml b/quickstep/res/values-el/strings.xml index 6ef1e9426c..87268df749 100644 --- a/quickstep/res/values-el/strings.xml +++ b/quickstep/res/values-el/strings.xml @@ -31,4 +31,7 @@ "%1$s, %2$s" "< 1 λ." "Απομένουν %1$s σήμερα" + "Προτάσεις εφαρμογών" + "Όλες οι εφαρμογές" + "Προβλέψεις εφαρμογών" diff --git a/quickstep/res/values-en-rAU/strings.xml b/quickstep/res/values-en-rAU/strings.xml index d640b63ea6..2d1418e5f4 100644 --- a/quickstep/res/values-en-rAU/strings.xml +++ b/quickstep/res/values-en-rAU/strings.xml @@ -31,4 +31,7 @@ "%1$s, %2$s" "< 1 minute" "%1$s left today" + "App suggestions" + "All apps" + "Your predicted apps" diff --git a/quickstep/res/values-en-rGB/strings.xml b/quickstep/res/values-en-rGB/strings.xml index d640b63ea6..2d1418e5f4 100644 --- a/quickstep/res/values-en-rGB/strings.xml +++ b/quickstep/res/values-en-rGB/strings.xml @@ -31,4 +31,7 @@ "%1$s, %2$s" "< 1 minute" "%1$s left today" + "App suggestions" + "All apps" + "Your predicted apps" diff --git a/quickstep/res/values-en-rIN/strings.xml b/quickstep/res/values-en-rIN/strings.xml index d640b63ea6..2d1418e5f4 100644 --- a/quickstep/res/values-en-rIN/strings.xml +++ b/quickstep/res/values-en-rIN/strings.xml @@ -31,4 +31,7 @@ "%1$s, %2$s" "< 1 minute" "%1$s left today" + "App suggestions" + "All apps" + "Your predicted apps" diff --git a/quickstep/res/values-es-rUS/strings.xml b/quickstep/res/values-es-rUS/strings.xml index c93e8fc4ad..5f5d0bdfe9 100644 --- a/quickstep/res/values-es-rUS/strings.xml +++ b/quickstep/res/values-es-rUS/strings.xml @@ -31,4 +31,7 @@ "%1$s (%2$s)" "< 1 minuto" "Tiempo restante: %1$s" + "Sugerencias de apps" + "Todas las apps" + "Predicción de tus apps" diff --git a/quickstep/res/values-es/strings.xml b/quickstep/res/values-es/strings.xml index 3a588e5fc0..329286b3bf 100644 --- a/quickstep/res/values-es/strings.xml +++ b/quickstep/res/values-es/strings.xml @@ -31,4 +31,7 @@ "%1$s (%2$s)" "<1 minuto" "tiempo restante: %1$s" + "Sugerencias de aplicaciones" + "Todas las aplicaciones" + "Predicción de aplicaciones" diff --git a/quickstep/res/values-et/strings.xml b/quickstep/res/values-et/strings.xml index 7032765bb0..0577b0f562 100644 --- a/quickstep/res/values-et/strings.xml +++ b/quickstep/res/values-et/strings.xml @@ -31,4 +31,7 @@ "%1$s %2$s" "< 1 minut" "Tääna jäänud %1$s" + "Rakenduste soovitused" + "Kõik rakendused" + "Teie ennustatud rakendused" diff --git a/quickstep/res/values-eu/strings.xml b/quickstep/res/values-eu/strings.xml index 66e08b9f10..c2d149e362 100644 --- a/quickstep/res/values-eu/strings.xml +++ b/quickstep/res/values-eu/strings.xml @@ -31,4 +31,7 @@ "%1$s (%2$s)" "< 1 min" "%1$s gelditzen dira gaur" + "Iradokitako aplikazioak" + "Aplikazio guztiak" + "Lagungarri izan dakizkizukeen aplikazioak" diff --git a/quickstep/res/values-fa/strings.xml b/quickstep/res/values-fa/strings.xml index 112d04cdef..cc26695a60 100644 --- a/quickstep/res/values-fa/strings.xml +++ b/quickstep/res/values-fa/strings.xml @@ -31,4 +31,7 @@ "%1$s، %2$s" "< ۱ دقیقه" "%1$s باقی‌مانده برای امروز" + "برنامه‌های پیشنهادی" + "همه برنامه‌ها" + "برنامه‌های پیش‌بینی‌شده" diff --git a/quickstep/res/values-fi/strings.xml b/quickstep/res/values-fi/strings.xml index 6a0a359305..f43433e3a1 100644 --- a/quickstep/res/values-fi/strings.xml +++ b/quickstep/res/values-fi/strings.xml @@ -31,4 +31,7 @@ "%1$s, %2$s" "< 1 min" "%1$s jäljellä tänään" + "Sovellusehdotukset" + "Kaikki sovellukset" + "Sovellusennusteet" diff --git a/quickstep/res/values-fr-rCA/strings.xml b/quickstep/res/values-fr-rCA/strings.xml index 248a5da5ee..a9a1cffb6d 100644 --- a/quickstep/res/values-fr-rCA/strings.xml +++ b/quickstep/res/values-fr-rCA/strings.xml @@ -31,4 +31,7 @@ "%1$s : %2$s" "< 1 min" "Il reste %1$s aujourd\'hui" + "Suggestions d\'applications" + "Toutes les applications" + "Vos prédictions d\'applications" diff --git a/quickstep/res/values-fr/strings.xml b/quickstep/res/values-fr/strings.xml index 338d9baff7..5394f495e9 100644 --- a/quickstep/res/values-fr/strings.xml +++ b/quickstep/res/values-fr/strings.xml @@ -31,4 +31,7 @@ "%1$s, %2$s" "< 1 min" "Encore %1$s aujourd\'hui" + "Suggestions d\'applications" + "Toutes les applications" + "Vos applications prévues" diff --git a/quickstep/res/values-gl/strings.xml b/quickstep/res/values-gl/strings.xml index d6ddf3cd68..c6698bb480 100644 --- a/quickstep/res/values-gl/strings.xml +++ b/quickstep/res/values-gl/strings.xml @@ -31,4 +31,7 @@ "%1$s (%2$s)" "<1 min" "Tempo restante hoxe %1$s" + "Suxestións de aplicacións" + "Todas as aplicacións" + "As túas aplicacións preditas" diff --git a/quickstep/res/values-gu/strings.xml b/quickstep/res/values-gu/strings.xml index 4493e3b5dd..660ad87acc 100644 --- a/quickstep/res/values-gu/strings.xml +++ b/quickstep/res/values-gu/strings.xml @@ -31,4 +31,7 @@ "%1$s, %2$s" "< 1 મિનિટ" "%1$s આજે બાકી" + "ઍપ સૂચનો" + "બધી ઍપ" + "તમારી પૂર્વાનુમાનિત ઍપ" diff --git a/quickstep/res/values-hi/strings.xml b/quickstep/res/values-hi/strings.xml index 3c53cce45a..0467af4b2d 100644 --- a/quickstep/res/values-hi/strings.xml +++ b/quickstep/res/values-hi/strings.xml @@ -31,4 +31,7 @@ "%1$s, %2$s" "<1 मिनट" "आज %1$s और चलेगा" + "ऐप्लिकेशन के सुझाव" + "सभी ऐप्लिकेशन" + "आपके काम के ऐप्लिकेशन" diff --git a/quickstep/res/values-hr/strings.xml b/quickstep/res/values-hr/strings.xml index 103710ff91..ab56e57b50 100644 --- a/quickstep/res/values-hr/strings.xml +++ b/quickstep/res/values-hr/strings.xml @@ -31,4 +31,7 @@ "%1$s, %2$s" "< 1 min" "Još %1$s danas" + "Predložene aplikacije" + "Sve aplikacije" + "Vaše predviđene aplikacije" diff --git a/quickstep/res/values-hu/strings.xml b/quickstep/res/values-hu/strings.xml index 22b2380f26..dec6ea0158 100644 --- a/quickstep/res/values-hu/strings.xml +++ b/quickstep/res/values-hu/strings.xml @@ -31,4 +31,7 @@ "%1$s, %2$s" "< 1 perc" "Ma még %1$s van hátra" + "Alkalmazásjavaslatok" + "Az összes alkalmazás" + "Várható alkalmazások" diff --git a/quickstep/res/values-hy/strings.xml b/quickstep/res/values-hy/strings.xml index 910265acc3..1656a1444d 100644 --- a/quickstep/res/values-hy/strings.xml +++ b/quickstep/res/values-hy/strings.xml @@ -31,4 +31,7 @@ "%1$s, %2$s" "< 1 ր" "Այսօր մնացել է՝ %1$s" + "Առաջարկվող հավելվածներ" + "Բոլոր հավելվածները" + "Ձեր կանխատեսված հավելվածները" diff --git a/quickstep/res/values-in/strings.xml b/quickstep/res/values-in/strings.xml index a7749df883..6824d16b40 100644 --- a/quickstep/res/values-in/strings.xml +++ b/quickstep/res/values-in/strings.xml @@ -31,4 +31,7 @@ "%1$s, %2$s" "< 1 menit" "%1$s tersisa hari ini" + "Saran aplikasi" + "Semua aplikasi" + "Aplikasi yang diprediksi" diff --git a/quickstep/res/values-is/strings.xml b/quickstep/res/values-is/strings.xml index ba0c6723fa..f60a2c6802 100644 --- a/quickstep/res/values-is/strings.xml +++ b/quickstep/res/values-is/strings.xml @@ -31,4 +31,7 @@ "%1$s, %2$s" "< 1 mín." "%1$s eftir í dag" + "Tillögur að forritum" + "Öll forrit" + "Spáð forrit" diff --git a/quickstep/res/values-it/strings.xml b/quickstep/res/values-it/strings.xml index 746443eabd..559fdb4b6e 100644 --- a/quickstep/res/values-it/strings.xml +++ b/quickstep/res/values-it/strings.xml @@ -31,4 +31,7 @@ "%1$s, %2$s" "< 1 min" "Rimanente oggi: %1$s" + "App suggerite" + "Tutte le app" + "Le app previste" diff --git a/quickstep/res/values-iw/strings.xml b/quickstep/res/values-iw/strings.xml index 96a8adcaf3..58cab4e0a9 100644 --- a/quickstep/res/values-iw/strings.xml +++ b/quickstep/res/values-iw/strings.xml @@ -31,4 +31,7 @@ "%1$s, %2$s" "‏< דקה" "הזמן שנותר להיום: %1$s" + "הצעות לאפליקציות" + "כל האפליקציות" + "האפליקציות החזויות שלך" diff --git a/quickstep/res/values-ja/strings.xml b/quickstep/res/values-ja/strings.xml index 5484ae1fcd..d3fecde208 100644 --- a/quickstep/res/values-ja/strings.xml +++ b/quickstep/res/values-ja/strings.xml @@ -31,4 +31,7 @@ "%1$s%2$s" "1 分未満" "今日はあと %1$sです" + "アプリの候補" + "すべてのアプリ" + "予測されたアプリ" diff --git a/quickstep/res/values-ka/strings.xml b/quickstep/res/values-ka/strings.xml index 9218fb8b8a..67b03a754f 100644 --- a/quickstep/res/values-ka/strings.xml +++ b/quickstep/res/values-ka/strings.xml @@ -31,4 +31,7 @@ "%1$s, %2$s" "< 1 წუთი" "დღეს დარჩენილია %1$s" + "აპების შემოთავაზებები" + "ყველა აპი" + "თქვენი პროგნოზირებული აპები" diff --git a/quickstep/res/values-kk/strings.xml b/quickstep/res/values-kk/strings.xml index 0766150226..a9fcbedb20 100644 --- a/quickstep/res/values-kk/strings.xml +++ b/quickstep/res/values-kk/strings.xml @@ -31,4 +31,7 @@ "%1$s, %2$s" "< 1 мин" "Бүгін %1$s қалды" + "Қолданба ұсыныстары" + "Барлық қолданбалар" + "Ұсынылатын қолданбалар" diff --git a/quickstep/res/values-km/strings.xml b/quickstep/res/values-km/strings.xml index 8737ae8303..c422041e7a 100644 --- a/quickstep/res/values-km/strings.xml +++ b/quickstep/res/values-km/strings.xml @@ -31,4 +31,7 @@ "%1$s, %2$s" "< 1 នាទី" "នៅសល់ %1$s ទៀត​នៅថ្ងៃនេះ" + "ការណែនាំកម្មវិធី" + "កម្មវិធី​ទាំងអស់" + "កម្មវិធី​ដែលបាន​ព្យាករ​របស់អ្នក" diff --git a/quickstep/res/values-kn/strings.xml b/quickstep/res/values-kn/strings.xml index 099957cb71..52782610b9 100644 --- a/quickstep/res/values-kn/strings.xml +++ b/quickstep/res/values-kn/strings.xml @@ -31,4 +31,7 @@ "%1$s, %2$s" "< 1 ನಿ" "ಇಂದು %1$s ಸಮಯ ಉಳಿದಿದೆ" + "ಆ್ಯಪ್ ಸಲಹೆಗಳು" + "ಎಲ್ಲಾ ಆ್ಯಪ್‌ಗಳು" + "ನಿಮ್ಮ ಸಂಭವನೀಯ ಆ್ಯಪ್‌ಗಳು" diff --git a/quickstep/res/values-ko/strings.xml b/quickstep/res/values-ko/strings.xml index 9543e7987f..7a8e6a1f9a 100644 --- a/quickstep/res/values-ko/strings.xml +++ b/quickstep/res/values-ko/strings.xml @@ -31,4 +31,7 @@ "%1$s, %2$s" "< 1분" "오늘 %1$s 남음" + "앱 추천" + "모든 앱" + "추천 앱" diff --git a/quickstep/res/values-ky/strings.xml b/quickstep/res/values-ky/strings.xml index d1d2d201d3..4018e57dcc 100644 --- a/quickstep/res/values-ky/strings.xml +++ b/quickstep/res/values-ky/strings.xml @@ -31,4 +31,7 @@ "%1$s, %2$s" "< 1 мүнөт" "Бүгүн %1$s мүнөт калды" + "Колдонмо сунуштары" + "Бардык колдонмолор" + "Божомолдонгон колдонмолоруңуз" diff --git a/quickstep/res/values-lo/strings.xml b/quickstep/res/values-lo/strings.xml index aba4156d06..e406b7083d 100644 --- a/quickstep/res/values-lo/strings.xml +++ b/quickstep/res/values-lo/strings.xml @@ -31,4 +31,7 @@ "%1$s, %2$s" "< 1 ນາທີ" "ເຫຼືອ %1$s ມື້ນີ້" + "ການແນະນຳແອັບ" + "ແອັບທັງໝົດ" + "ແອັບທີ່ຄາດເດົາໄວ້ແລ້ວຂອງທ່ານ" diff --git a/quickstep/res/values-lt/strings.xml b/quickstep/res/values-lt/strings.xml index 933b3f094f..ed1fc373e2 100644 --- a/quickstep/res/values-lt/strings.xml +++ b/quickstep/res/values-lt/strings.xml @@ -31,4 +31,7 @@ "%1$s, %2$s" "< 1 min." "Šiandien liko: %1$s" + "Programų pasiūlymai" + "Visos programos" + "Numatomos programos" diff --git a/quickstep/res/values-lv/strings.xml b/quickstep/res/values-lv/strings.xml index 1e2ed00f1a..85ce0e017d 100644 --- a/quickstep/res/values-lv/strings.xml +++ b/quickstep/res/values-lv/strings.xml @@ -31,4 +31,7 @@ "%1$s, %2$s" "<1 minūte" "Šodien atlicis: %1$s" + "Ieteicamās lietotnes" + "Visas lietotnes" + "Jūsu prognozētās lietotnes" diff --git a/quickstep/res/values-mk/strings.xml b/quickstep/res/values-mk/strings.xml index 7a6c094b53..9f11521df3 100644 --- a/quickstep/res/values-mk/strings.xml +++ b/quickstep/res/values-mk/strings.xml @@ -31,4 +31,7 @@ "%1$s, %2$s" "< 1 минута" "Уште %1$s за денес" + "Предлози за апликации" + "Сите апликации" + "Вашите предвидени апликации" diff --git a/quickstep/res/values-ml/strings.xml b/quickstep/res/values-ml/strings.xml index b5eac1dc32..2e02e80fa8 100644 --- a/quickstep/res/values-ml/strings.xml +++ b/quickstep/res/values-ml/strings.xml @@ -31,4 +31,7 @@ "%1$s, %2$s" "< 1 മിനിറ്റ്" "ഇന്ന് %1$s ശേഷിക്കുന്നു" + "ആപ്പ് നിർദ്ദേശങ്ങൾ" + "എല്ലാ ആപ്പുകളും" + "നിങ്ങളുടെ പ്രവചിക്കപ്പെട്ട ആപ്പുകൾ" diff --git a/quickstep/res/values-mn/strings.xml b/quickstep/res/values-mn/strings.xml index a105ef1609..5de8602022 100644 --- a/quickstep/res/values-mn/strings.xml +++ b/quickstep/res/values-mn/strings.xml @@ -31,4 +31,7 @@ "%1$s, %2$s" "< 1 минут" "Өнөөдөр %1$s үлдсэн" + "Аппын зөвлөмж" + "Бүх апп" + "Таны таамагласан аппууд" diff --git a/quickstep/res/values-mr/strings.xml b/quickstep/res/values-mr/strings.xml index bf725e3378..1ca558a240 100644 --- a/quickstep/res/values-mr/strings.xml +++ b/quickstep/res/values-mr/strings.xml @@ -31,4 +31,7 @@ "%1$s, %2$s" "१मिहून कमी" "आज %1$sशिल्लक आहे" + "अ‍ॅप सूचना" + "सर्व अ‍ॅप्स" + "तुमची पूर्वानुमानीत अ‍ॅप्स" diff --git a/quickstep/res/values-ms/strings.xml b/quickstep/res/values-ms/strings.xml index 2e3f236b1a..2542963769 100644 --- a/quickstep/res/values-ms/strings.xml +++ b/quickstep/res/values-ms/strings.xml @@ -31,4 +31,7 @@ "%1$s, %2$s" "< 1 minit" "%1$s lagi hari ini" + "Cadangan apl" + "Semua apl" + "Apl ramalan anda" diff --git a/quickstep/res/values-my/strings.xml b/quickstep/res/values-my/strings.xml index 7b931257b3..7683e05990 100644 --- a/quickstep/res/values-my/strings.xml +++ b/quickstep/res/values-my/strings.xml @@ -31,4 +31,7 @@ "%1$s%2$s" "< ၁ မိနစ်" "ယနေ့ %1$s ခု ကျန်သည်" + "အက်ပ်အကြံပြုချက်များ" + "အက်ပ်အားလုံး" + "သင်၏ ခန့်မှန်းအက်ပ်များ" diff --git a/quickstep/res/values-nb/strings.xml b/quickstep/res/values-nb/strings.xml index 74f43d2e24..01bbb6a75d 100644 --- a/quickstep/res/values-nb/strings.xml +++ b/quickstep/res/values-nb/strings.xml @@ -31,4 +31,7 @@ "%1$s, %2$s" "< 1 minutt" "%1$s gjenstår i dag" + "Appanbefalinger" + "Alle apper" + "Forslag til apper" diff --git a/quickstep/res/values-ne/strings.xml b/quickstep/res/values-ne/strings.xml index 6053def168..60e9bd5313 100644 --- a/quickstep/res/values-ne/strings.xml +++ b/quickstep/res/values-ne/strings.xml @@ -31,4 +31,7 @@ "%1$s, %2$s" "< १ मिनेट" "आज: %1$s बाँकी" + "अनुप्रयोगसम्बन्धी सुझावहरू" + "सबै अनुप्रयोगहरू" + "तपाईंका पूर्वानुमानित अनुप्रयोगहरू" diff --git a/quickstep/res/values-nl/strings.xml b/quickstep/res/values-nl/strings.xml index 4e3a34c254..8032567e1c 100644 --- a/quickstep/res/values-nl/strings.xml +++ b/quickstep/res/values-nl/strings.xml @@ -31,4 +31,7 @@ "%1$s, %2$s" "< 1 minuut" "Nog %1$s vandaag" + "App-suggesties" + "Alle apps" + "Je voorspelde apps" diff --git a/quickstep/res/values-pa/strings.xml b/quickstep/res/values-pa/strings.xml index 5aeeae6b3a..58c0d2afa4 100644 --- a/quickstep/res/values-pa/strings.xml +++ b/quickstep/res/values-pa/strings.xml @@ -31,4 +31,7 @@ "%1$s, %2$s" "< 1 ਮਿੰਟ" "ਅੱਜ %1$s ਬਾਕੀ" + "ਐਪ ਸੰਬੰਧੀ ਸੁਝਾਅ" + "ਸਾਰੀਆਂ ਐਪਾਂ" + "ਤੁਹਾਡੀਆਂ ਪੂਰਵ ਅਨੁਮਾਨਿਤ ਐਪਾਂ" diff --git a/quickstep/res/values-pl/strings.xml b/quickstep/res/values-pl/strings.xml index 210edcf011..d83160dffb 100644 --- a/quickstep/res/values-pl/strings.xml +++ b/quickstep/res/values-pl/strings.xml @@ -31,4 +31,7 @@ "%1$s, %2$s" "> 1 min" "Na dziś zostało %1$s" + "Sugerowane aplikacje" + "Wszystkie aplikacje" + "Przewidywane aplikacje" diff --git a/quickstep/res/values-pt-rPT/strings.xml b/quickstep/res/values-pt-rPT/strings.xml index 8a129d5ea5..2fd34d636c 100644 --- a/quickstep/res/values-pt-rPT/strings.xml +++ b/quickstep/res/values-pt-rPT/strings.xml @@ -31,4 +31,7 @@ "%1$s, %2$s" "< 1 minuto" "Resta(m) %1$s hoje." + "Sugestões de aplicações" + "Todas as aplicações" + "As suas aplicações previstas" diff --git a/quickstep/res/values-pt/strings.xml b/quickstep/res/values-pt/strings.xml index e5380d560c..673dfe2e73 100644 --- a/quickstep/res/values-pt/strings.xml +++ b/quickstep/res/values-pt/strings.xml @@ -31,4 +31,7 @@ "%1$s, %2$s" "< 1 min" "%1$s restante(s) hoje" + "Sugestões de apps" + "Todos os apps" + "Suas predições de apps" diff --git a/quickstep/res/values-ro/strings.xml b/quickstep/res/values-ro/strings.xml index 54452a0584..2ac783eb0e 100644 --- a/quickstep/res/values-ro/strings.xml +++ b/quickstep/res/values-ro/strings.xml @@ -31,4 +31,7 @@ "%1$s, %2$s" "< 1 minut" "Au mai rămas %1$s astăzi" + "Sugestii de aplicații" + "Toate aplicațiile" + "Aplicațiile estimate" diff --git a/quickstep/res/values-ru/strings.xml b/quickstep/res/values-ru/strings.xml index 8b2016ab2f..5dd89a644b 100644 --- a/quickstep/res/values-ru/strings.xml +++ b/quickstep/res/values-ru/strings.xml @@ -31,4 +31,7 @@ "%1$s: %2$s" "< 1 мин." "Осталось сегодня: %1$s" + "Рекомендуемые приложения" + "Все приложения" + "Ваши рекомендуемые приложения" diff --git a/quickstep/res/values-si/strings.xml b/quickstep/res/values-si/strings.xml index 2163390679..f6584c4dca 100644 --- a/quickstep/res/values-si/strings.xml +++ b/quickstep/res/values-si/strings.xml @@ -31,4 +31,7 @@ "%1$s, %2$s" "< 1 විනාඩියක්" "අද %1$sක් ඉතුරුයි" + "යෙදුම් යෝජනා" + "සියලු යෙදුම්" + "ඔබේ පුරෝකථන කළ යෙදුම්" diff --git a/quickstep/res/values-sk/strings.xml b/quickstep/res/values-sk/strings.xml index 12983db6d6..8a9c7365f2 100644 --- a/quickstep/res/values-sk/strings.xml +++ b/quickstep/res/values-sk/strings.xml @@ -31,4 +31,7 @@ "%1$s, %2$s" "Menej ako 1 minúta" "Dnes ešte zostáva: %1$s" + "Návrhy aplikácií" + "Všetky aplikácie" + "Vaše predpovedané aplikácie" diff --git a/quickstep/res/values-sl/strings.xml b/quickstep/res/values-sl/strings.xml index a940f2bfc8..15f8f89cc7 100644 --- a/quickstep/res/values-sl/strings.xml +++ b/quickstep/res/values-sl/strings.xml @@ -31,4 +31,7 @@ "%1$s, %2$s" "< 1 min" "Danes je ostalo še %1$s" + "Predlogi za aplikacije" + "Vse aplikacije" + "Predvidene aplikacije" diff --git a/quickstep/res/values-sq/strings.xml b/quickstep/res/values-sq/strings.xml index e41bcb59aa..d8f5f28b42 100644 --- a/quickstep/res/values-sq/strings.xml +++ b/quickstep/res/values-sq/strings.xml @@ -31,4 +31,7 @@ "%1$s, %2$s" "< 1 minutë" "%1$s të mbetura sot" + "Sugjerimet e aplikacioneve" + "Të gjitha aplikacionet" + "Aplikacionet e tua të parashikuara" diff --git a/quickstep/res/values-sr/strings.xml b/quickstep/res/values-sr/strings.xml index 8f26c66501..b721641446 100644 --- a/quickstep/res/values-sr/strings.xml +++ b/quickstep/res/values-sr/strings.xml @@ -31,4 +31,7 @@ "%1$s, %2$s" "< 1 мин" "Још %1$s данас" + "Предлози апликација" + "Све апликације" + "Предвиђене апликације" diff --git a/quickstep/res/values-sv/strings.xml b/quickstep/res/values-sv/strings.xml index 70740e502c..ba7ebcdec7 100644 --- a/quickstep/res/values-sv/strings.xml +++ b/quickstep/res/values-sv/strings.xml @@ -31,4 +31,7 @@ "%1$s, %2$s" "< 1 min" "%1$s kvar i dag" + "Appförslag" + "Alla appar" + "Föreslagna appar" diff --git a/quickstep/res/values-sw/strings.xml b/quickstep/res/values-sw/strings.xml index c646b6a5fc..24db429093 100644 --- a/quickstep/res/values-sw/strings.xml +++ b/quickstep/res/values-sw/strings.xml @@ -31,4 +31,7 @@ "%1$s, %2$s" "< dak 1" "Umebakisha %1$s leo" + "Mapendekezo ya programu" + "Programu zote" + "Programu zako zinazopendekezwa" diff --git a/quickstep/res/values-ta/strings.xml b/quickstep/res/values-ta/strings.xml index 19bfaa92f7..97d51cd35f 100644 --- a/quickstep/res/values-ta/strings.xml +++ b/quickstep/res/values-ta/strings.xml @@ -31,4 +31,7 @@ "%1$s, %2$s" "< 1 நி" "இன்று %1$s மீதமுள்ளது" + "ஆப்ஸ் பரிந்துரைகள்" + "அனைத்து ஆப்ஸும்" + "நீங்கள் கணித்த ஆப்ஸ்" diff --git a/quickstep/res/values-te/strings.xml b/quickstep/res/values-te/strings.xml index 071755a95c..24b37f75d4 100644 --- a/quickstep/res/values-te/strings.xml +++ b/quickstep/res/values-te/strings.xml @@ -31,4 +31,7 @@ "%1$s, %2$s" "< 1 నిమిషం" "నేటికి %1$s మిగిలి ఉంది" + "యాప్ సూచనలు" + "అన్ని యాప్‌లు" + "మీ సూచించబడిన యాప్‌లు" diff --git a/quickstep/res/values-th/strings.xml b/quickstep/res/values-th/strings.xml index c0e78cec1f..0f6821bf19 100644 --- a/quickstep/res/values-th/strings.xml +++ b/quickstep/res/values-th/strings.xml @@ -31,4 +31,7 @@ "%1$s %2$s" "<1 นาที" "วันนี้เหลืออีก %1$s" + "คำแนะนำเกี่ยวกับแอป" + "แอปทั้งหมด" + "แอปที่คาดการณ์ไว้" diff --git a/quickstep/res/values-tl/strings.xml b/quickstep/res/values-tl/strings.xml index 76a0b250ac..491bac5bd9 100644 --- a/quickstep/res/values-tl/strings.xml +++ b/quickstep/res/values-tl/strings.xml @@ -31,4 +31,7 @@ "%1$s, %2$s" "< 1 min" "%1$s na lang ngayon" + "Mga iminumungkahing app" + "Lahat ng app" + "Iyong mga nahulaang app" diff --git a/quickstep/res/values-tr/strings.xml b/quickstep/res/values-tr/strings.xml index 8b59c7bd09..ec6d88405c 100644 --- a/quickstep/res/values-tr/strings.xml +++ b/quickstep/res/values-tr/strings.xml @@ -31,4 +31,7 @@ "%1$s, %2$s" "< 1 dk." "Bugün %1$s kaldı" + "Uygulama önerileri" + "Tüm uygulamalar" + "Tahmin edilen uygulamalarınız" diff --git a/quickstep/res/values-uk/strings.xml b/quickstep/res/values-uk/strings.xml index 39c3848bd7..77360625db 100644 --- a/quickstep/res/values-uk/strings.xml +++ b/quickstep/res/values-uk/strings.xml @@ -31,4 +31,7 @@ "%1$s, %2$s" "< 1 хв" "Сьогодні залишилося %1$s" + "Пропозиції додатків" + "Усі додатки" + "Передбачені додатки" diff --git a/quickstep/res/values-ur/strings.xml b/quickstep/res/values-ur/strings.xml index 4fd9e69bf1..87b303f2af 100644 --- a/quickstep/res/values-ur/strings.xml +++ b/quickstep/res/values-ur/strings.xml @@ -31,4 +31,7 @@ "%1$s،%2$s" "‏< 1 منٹ" "آج %1$s بچا ہے" + "ایپ کی تجاویز" + "تمام ایپس" + "آپ کی پیشن گوئی کردہ ایپس" diff --git a/quickstep/res/values-uz/strings.xml b/quickstep/res/values-uz/strings.xml index 466d79ea58..67c8e91c00 100644 --- a/quickstep/res/values-uz/strings.xml +++ b/quickstep/res/values-uz/strings.xml @@ -31,4 +31,7 @@ "%1$s, %2$s" "< 1 daqiqa" "Bugun %1$s qoldi" + "Tavsiya etiladigan ilovalar" + "Barcha ilovalar" + "Taklif qilingan ilovalaringiz" diff --git a/quickstep/res/values-vi/strings.xml b/quickstep/res/values-vi/strings.xml index 842b22bfe1..34c89efcf1 100644 --- a/quickstep/res/values-vi/strings.xml +++ b/quickstep/res/values-vi/strings.xml @@ -31,4 +31,7 @@ "%1$s, %2$s" "< 1 phút" "Hôm nay còn %1$s" + "Các ứng dụng đề xuất" + "Tất cả ứng dụng" + "Các ứng dụng gợi ý của bạn" diff --git a/quickstep/res/values-zh-rCN/strings.xml b/quickstep/res/values-zh-rCN/strings.xml index 951489fff7..0e83977fa3 100644 --- a/quickstep/res/values-zh-rCN/strings.xml +++ b/quickstep/res/values-zh-rCN/strings.xml @@ -31,4 +31,7 @@ "%1$s%2$s)" "不到 1 分钟" "今天还可使用 %1$s" + "应用推荐" + "所有应用" + "您的预测应用" diff --git a/quickstep/res/values-zh-rHK/strings.xml b/quickstep/res/values-zh-rHK/strings.xml index 361623dfad..ac7e8e9002 100644 --- a/quickstep/res/values-zh-rHK/strings.xml +++ b/quickstep/res/values-zh-rHK/strings.xml @@ -31,4 +31,7 @@ "%1$s%2$s" "少於 1 分鐘" "今天剩餘時間:%1$s" + "應用程式建議" + "所有應用程式" + "您的預測應用程式" diff --git a/quickstep/res/values-zh-rTW/strings.xml b/quickstep/res/values-zh-rTW/strings.xml index 6938d3e816..3323bfd57d 100644 --- a/quickstep/res/values-zh-rTW/strings.xml +++ b/quickstep/res/values-zh-rTW/strings.xml @@ -31,4 +31,7 @@ "%1$s (%2$s)" "< 1 分鐘" "今天還能使用 %1$s" + "應用程式建議" + "所有應用程式" + "系統預測你會使用的應用程式" diff --git a/quickstep/res/values-zu/strings.xml b/quickstep/res/values-zu/strings.xml index 98f7b02d9a..0f1d99d7b6 100644 --- a/quickstep/res/values-zu/strings.xml +++ b/quickstep/res/values-zu/strings.xml @@ -31,4 +31,7 @@ "%1$s, %2$s" "< 1 iminithi" "%1$s esele namhlanje" + "Iziphakamiso zohlelo lokusebenza" + "Zonke izinhlelo zokusebenza" + "Izinhlelo zakho zokusebenza eziqagelwe" diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml index 32f312fdbe..82d1aa6728 100644 --- a/quickstep/res/values/dimens.xml +++ b/quickstep/res/values/dimens.xml @@ -36,6 +36,7 @@ 0.0285dp + 0.15dp 0.285dp 0.5dp 36dp diff --git a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java index e1a115ad3d..7dd4df7d69 100644 --- a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java +++ b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java @@ -104,16 +104,18 @@ public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTrans private static final String CONTROL_REMOTE_APP_TRANSITION_PERMISSION = "android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS"; - private static final long APP_LAUNCH_DURATION = 500; + private static final long APP_LAUNCH_DURATION = 450; // Use a shorter duration for x or y translation to create a curve effect - private static final long APP_LAUNCH_CURVED_DURATION = APP_LAUNCH_DURATION / 2; + private static final long APP_LAUNCH_CURVED_DURATION = 250; private static final long APP_LAUNCH_ALPHA_DURATION = 50; + private static final long APP_LAUNCH_ALPHA_START_DELAY = 50; // We scale the durations for the downward app launch animations (minus the scale animation). private static final float APP_LAUNCH_DOWN_DUR_SCALE_FACTOR = 0.8f; private static final long APP_LAUNCH_DOWN_DURATION = (long) (APP_LAUNCH_DURATION * APP_LAUNCH_DOWN_DUR_SCALE_FACTOR); - private static final long APP_LAUNCH_DOWN_CURVED_DURATION = APP_LAUNCH_DOWN_DURATION / 2; + private static final long APP_LAUNCH_DOWN_CURVED_DURATION = + (long) (APP_LAUNCH_CURVED_DURATION * APP_LAUNCH_DOWN_DUR_SCALE_FACTOR); private static final long APP_LAUNCH_ALPHA_DOWN_DURATION = (long) (APP_LAUNCH_ALPHA_DURATION * APP_LAUNCH_DOWN_DUR_SCALE_FACTOR); @@ -132,7 +134,7 @@ public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTrans private final DragLayer mDragLayer; private final AlphaProperty mDragLayerAlpha; - private final Handler mHandler; + final Handler mHandler; private final boolean mIsRtl; private final float mContentTransY; @@ -475,17 +477,18 @@ public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTrans float shapeRevealDuration = APP_LAUNCH_DURATION * SHAPE_PROGRESS_DURATION; final float windowRadius = mDeviceProfile.isMultiWindowMode - ? 0 : getWindowCornerRadius(mLauncher.getResources()); - + ? 0 : getWindowCornerRadius(mLauncher.getResources()); appAnimator.addUpdateListener(new MultiValueUpdateListener() { FloatProp mDx = new FloatProp(0, dX, 0, xDuration, AGGRESSIVE_EASE); FloatProp mDy = new FloatProp(0, dY, 0, yDuration, AGGRESSIVE_EASE); FloatProp mIconScale = new FloatProp(initialStartScale, scale, 0, APP_LAUNCH_DURATION, EXAGGERATED_EASE); - FloatProp mIconAlpha = new FloatProp(1f, 0f, shapeRevealDuration, alphaDuration, - LINEAR); + FloatProp mIconAlpha = new FloatProp(1f, 0f, APP_LAUNCH_ALPHA_START_DELAY, + alphaDuration, LINEAR); FloatProp mCropHeight = new FloatProp(windowTargetBounds.width(), - windowTargetBounds.height(), 0, shapeRevealDuration, AGGRESSIVE_EASE); + windowTargetBounds.height(), 0, APP_LAUNCH_DURATION, EXAGGERATED_EASE); + FloatProp mWindowRadius = new FloatProp(windowTargetBounds.width() / 2f, + windowRadius, 0, APP_LAUNCH_DURATION, EXAGGERATED_EASE); @Override public void onUpdate(float percent) { @@ -518,6 +521,7 @@ public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTrans float transX0 = temp.left - offsetX; float transY0 = temp.top - offsetY; + float croppedHeight = (windowTargetBounds.height() - crop.height()) * scale; SurfaceParams[] params = new SurfaceParams[targets.length]; for (int i = targets.length - 1; i >= 0; i--) { RemoteAnimationTargetCompat target = targets[i]; @@ -529,8 +533,9 @@ public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTrans matrix.postTranslate(transX0, transY0); targetCrop = crop; alpha = 1f - mIconAlpha.value; - cornerRadius = windowRadius; + cornerRadius = mWindowRadius.value; matrix.mapRect(currentBounds, targetBounds); + currentBounds.bottom -= croppedHeight; mFloatingView.update(currentBounds, mIconAlpha.value, percent, 0f, cornerRadius * scale, true /* isOpening */); } else { @@ -573,70 +578,9 @@ public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTrans * @return Runner that plays when user goes to Launcher * ie. pressing home, swiping up from nav bar. */ - private RemoteAnimationRunnerCompat getWallpaperOpenRunner(boolean fromUnlock) { - return new LauncherAnimationRunner(mHandler, false /* startAtFrontOfQueue */) { - @Override - public void onCreateAnimation(RemoteAnimationTargetCompat[] targetCompats, - AnimationResult result) { - if (!mLauncher.hasBeenResumed()) { - // If launcher is not resumed, wait until new async-frame after resume - mLauncher.setOnResumeCallback(() -> - postAsyncCallback(mHandler, () -> - onCreateAnimation(targetCompats, result))); - return; - } - - if (mLauncher.hasSomeInvisibleFlag(PENDING_INVISIBLE_BY_WALLPAPER_ANIMATION)) { - mLauncher.addForceInvisibleFlag(INVISIBLE_BY_PENDING_FLAGS); - mLauncher.getStateManager().moveToRestState(); - } - - AnimatorSet anim = null; - RemoteAnimationProvider provider = mRemoteAnimationProvider; - if (provider != null) { - anim = provider.createWindowAnimation(targetCompats); - } - - if (anim == null) { - anim = new AnimatorSet(); - anim.play(fromUnlock - ? getUnlockWindowAnimator(targetCompats) - : getClosingWindowAnimators(targetCompats)); - - // Normally, we run the launcher content animation when we are transitioning - // home, but if home is already visible, then we don't want to animate the - // contents of launcher unless we know that we are animating home as a result - // of the home button press with quickstep, which will result in launcher being - // started on touch down, prior to the animation home (and won't be in the - // targets list because it is already visible). In that case, we force - // invisibility on touch down, and only reset it after the animation to home - // is initialized. - if (launcherIsATargetWithMode(targetCompats, MODE_OPENING) - || mLauncher.isForceInvisible()) { - // Only register the content animation for cancellation when state changes - mLauncher.getStateManager().setCurrentAnimation(anim); - if (fromUnlock) { - Pair contentAnimator = - getLauncherContentAnimator(false /* isAppOpening */, - new float[] {mContentTransY, 0}); - contentAnimator.first.setStartDelay(0); - anim.play(contentAnimator.first); - anim.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - contentAnimator.second.run(); - } - }); - } else { - createLauncherResumeAnimation(anim); - } - } - } - - mLauncher.clearForceInvisibleFlag(INVISIBLE_ALL); - result.setAnimation(anim); - } - }; + RemoteAnimationRunnerCompat getWallpaperOpenRunner(boolean fromUnlock) { + return new WallpaperOpenLauncherAnimationRunner(mHandler, false /* startAtFrontOfQueue */, + fromUnlock); } /** @@ -773,4 +717,79 @@ public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTrans return mLauncher.checkSelfPermission(CONTROL_REMOTE_APP_TRANSITION_PERMISSION) == PackageManager.PERMISSION_GRANTED; } + + /** + * Remote animation runner for animation from the app to Launcher, including recents. + */ + class WallpaperOpenLauncherAnimationRunner extends LauncherAnimationRunner { + private final boolean mFromUnlock; + + public WallpaperOpenLauncherAnimationRunner(Handler handler, boolean startAtFrontOfQueue, + boolean fromUnlock) { + super(handler, startAtFrontOfQueue); + mFromUnlock = fromUnlock; + } + + @Override + public void onCreateAnimation(RemoteAnimationTargetCompat[] targetCompats, + LauncherAnimationRunner.AnimationResult result) { + if (!mLauncher.hasBeenResumed()) { + // If launcher is not resumed, wait until new async-frame after resume + mLauncher.setOnResumeCallback(() -> + postAsyncCallback(mHandler, () -> + onCreateAnimation(targetCompats, result))); + return; + } + + if (mLauncher.hasSomeInvisibleFlag(PENDING_INVISIBLE_BY_WALLPAPER_ANIMATION)) { + mLauncher.addForceInvisibleFlag(INVISIBLE_BY_PENDING_FLAGS); + mLauncher.getStateManager().moveToRestState(); + } + + AnimatorSet anim = null; + RemoteAnimationProvider provider = mRemoteAnimationProvider; + if (provider != null) { + anim = provider.createWindowAnimation(targetCompats); + } + + if (anim == null) { + anim = new AnimatorSet(); + anim.play(mFromUnlock + ? getUnlockWindowAnimator(targetCompats) + : getClosingWindowAnimators(targetCompats)); + + // Normally, we run the launcher content animation when we are transitioning + // home, but if home is already visible, then we don't want to animate the + // contents of launcher unless we know that we are animating home as a result + // of the home button press with quickstep, which will result in launcher being + // started on touch down, prior to the animation home (and won't be in the + // targets list because it is already visible). In that case, we force + // invisibility on touch down, and only reset it after the animation to home + // is initialized. + if (launcherIsATargetWithMode(targetCompats, MODE_OPENING) + || mLauncher.isForceInvisible()) { + // Only register the content animation for cancellation when state changes + mLauncher.getStateManager().setCurrentAnimation(anim); + if (mFromUnlock) { + Pair contentAnimator = + getLauncherContentAnimator(false /* isAppOpening */, + new float[] {mContentTransY, 0}); + contentAnimator.first.setStartDelay(0); + anim.play(contentAnimator.first); + anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + contentAnimator.second.run(); + } + }); + } else { + createLauncherResumeAnimation(anim); + } + } + } + + mLauncher.clearForceInvisibleFlag(INVISIBLE_ALL); + result.setAnimation(anim); + } + } } diff --git a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java index 4f50cdb614..77ac35c50a 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java +++ b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java @@ -194,7 +194,7 @@ public class UiFactory extends RecentsUiFactory { public static ScaleAndTranslation getOverviewScaleAndTranslationForNormalState(Launcher l) { if (SysUINavigationMode.getMode(l) == Mode.NO_BUTTON) { - float offscreenTranslationX = l.getDragLayer().getWidth() + float offscreenTranslationX = l.getDeviceProfile().widthPx - l.getOverviewPanel().getPaddingStart(); return new ScaleAndTranslation(1f, offscreenTranslationX, 0f); } diff --git a/quickstep/src/com/android/launcher3/uioverrides/WallpaperColorInfo.java b/quickstep/src/com/android/launcher3/uioverrides/WallpaperColorInfo.java index 8218517dcc..711e59a33e 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/WallpaperColorInfo.java +++ b/quickstep/src/com/android/launcher3/uioverrides/WallpaperColorInfo.java @@ -35,6 +35,10 @@ import java.util.ArrayList; @TargetApi(Build.VERSION_CODES.P) public class WallpaperColorInfo implements OnColorsChangedListener { + private static final int MAIN_COLOR_LIGHT = 0xffdadce0; + private static final int MAIN_COLOR_DARK = 0xff202124; + private static final int MAIN_COLOR_REGULAR = 0xff000000; + private static final Object sInstanceLock = new Object(); private static WallpaperColorInfo sInstance; @@ -79,6 +83,10 @@ public class WallpaperColorInfo implements OnColorsChangedListener { return mExtractionInfo.supportsDarkText; } + public boolean isMainColorDark() { + return mExtractionInfo.mainColor == MAIN_COLOR_DARK; + } + @Override public void onColorsChanged(WallpaperColors colors, int which) { if ((which & FLAG_SYSTEM) != 0) { diff --git a/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java index f58f0d4857..893c053565 100644 --- a/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java +++ b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java @@ -35,10 +35,18 @@ public class MotionPauseDetector { /** If no motion is added for this amount of time, assume the motion has paused. */ private static final long FORCE_PAUSE_TIMEOUT = 300; + /** + * After {@link #makePauseHarderToTrigger()}, must + * move slowly for this long to trigger a pause. + */ + private static final long HARDER_TRIGGER_TIMEOUT = 400; + private final float mSpeedVerySlow; + private final float mSpeedSlow; private final float mSpeedSomewhatFast; private final float mSpeedFast; private final Alarm mForcePauseTimeout; + private final boolean mMakePauseHarderToTrigger; private Long mPreviousTime = null; private Float mPreviousPosition = null; @@ -52,19 +60,29 @@ public class MotionPauseDetector { private boolean mHasEverBeenPaused; /** @see #setDisallowPause(boolean) */ private boolean mDisallowPause; + // Time at which speed became < mSpeedSlow (only used if mMakePauseHarderToTrigger == true). + private long mSlowStartTime; public MotionPauseDetector(Context context) { + this(context, false); + } + + /** + * @param makePauseHarderToTrigger Used for gestures that require a more explicit pause. + */ + public MotionPauseDetector(Context context, boolean makePauseHarderToTrigger) { Resources res = context.getResources(); mSpeedVerySlow = res.getDimension(R.dimen.motion_pause_detector_speed_very_slow); + mSpeedSlow = res.getDimension(R.dimen.motion_pause_detector_speed_slow); mSpeedSomewhatFast = res.getDimension(R.dimen.motion_pause_detector_speed_somewhat_fast); mSpeedFast = res.getDimension(R.dimen.motion_pause_detector_speed_fast); mForcePauseTimeout = new Alarm(); mForcePauseTimeout.setOnAlarmListener(alarm -> updatePaused(true /* isPaused */)); + mMakePauseHarderToTrigger = makePauseHarderToTrigger; } /** - * Get callbacks for when motion pauses and resumes, including an - * immediate callback with the current pause state. + * Get callbacks for when motion pauses and resumes. */ public void setOnMotionPauseListener(OnMotionPauseListener listener) { mOnMotionPauseListener = listener; @@ -88,13 +106,15 @@ public class MotionPauseDetector { if (mFirstPosition == null) { mFirstPosition = position; } - mForcePauseTimeout.setAlarm(FORCE_PAUSE_TIMEOUT); + mForcePauseTimeout.setAlarm(mMakePauseHarderToTrigger + ? HARDER_TRIGGER_TIMEOUT + : FORCE_PAUSE_TIMEOUT); if (mPreviousTime != null && mPreviousPosition != null) { long changeInTime = Math.max(1, time - mPreviousTime); float changeInPosition = position - mPreviousPosition; float velocity = changeInPosition / changeInTime; if (mPreviousVelocity != null) { - checkMotionPaused(velocity, mPreviousVelocity); + checkMotionPaused(velocity, mPreviousVelocity, time); } mPreviousVelocity = velocity; } @@ -102,7 +122,7 @@ public class MotionPauseDetector { mPreviousPosition = position; } - private void checkMotionPaused(float velocity, float prevVelocity) { + private void checkMotionPaused(float velocity, float prevVelocity, long time) { float speed = Math.abs(velocity); float previousSpeed = Math.abs(prevVelocity); boolean isPaused; @@ -122,6 +142,17 @@ public class MotionPauseDetector { boolean isRapidDeceleration = speed < previousSpeed * RAPID_DECELERATION_FACTOR; isPaused = isRapidDeceleration && speed < mSpeedSomewhatFast; } + if (mMakePauseHarderToTrigger) { + if (speed < mSpeedSlow) { + if (mSlowStartTime == 0) { + mSlowStartTime = time; + } + isPaused = time - mSlowStartTime >= HARDER_TRIGGER_TIMEOUT; + } else { + mSlowStartTime = 0; + isPaused = false; + } + } } } updatePaused(isPaused); @@ -149,6 +180,7 @@ public class MotionPauseDetector { mFirstPosition = null; setOnMotionPauseListener(null); mIsPaused = mHasEverBeenPaused = false; + mSlowStartTime = 0; mForcePauseTimeout.cancelAlarm(); } diff --git a/quickstep/tests/src/com/android/quickstep/AppPredictionsUITests.java b/quickstep/tests/src/com/android/quickstep/AppPredictionsUITests.java index 1817135504..47ce44c676 100644 --- a/quickstep/tests/src/com/android/quickstep/AppPredictionsUITests.java +++ b/quickstep/tests/src/com/android/quickstep/AppPredictionsUITests.java @@ -40,7 +40,6 @@ import com.android.launcher3.model.AppLaunchTracker; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; @@ -70,6 +69,8 @@ public class AppPredictionsUITests extends AbstractQuickStepTest { // Disable app tracker AppLaunchTracker.INSTANCE.initializeForTesting(new AppLaunchTracker()); + PredictionUiStateManager.INSTANCE.initializeForTesting(null); + mCallback = PredictionUiStateManager.INSTANCE.get(mTargetContext).appPredictorCallback( Client.HOME); @@ -85,7 +86,6 @@ public class AppPredictionsUITests extends AbstractQuickStepTest { * Test that prediction UI is updated as soon as we get predictions from the system */ @Test - @Ignore // b/131772711: this really fails (when being run as a part of the whole test suite)! public void testPredictionExistsInAllApps() { mActivityMonitor.startLauncher(); mLauncher.pressHome().switchToAllApps(); @@ -118,7 +118,6 @@ public class AppPredictionsUITests extends AbstractQuickStepTest { } @Test - @Ignore // b/131772711 - this was failing in the lab public void testPredictionsDisabled() { mActivityMonitor.startLauncher(); sendPredictionUpdate(); @@ -150,10 +149,10 @@ public class AppPredictionsUITests extends AbstractQuickStepTest { List targets = new ArrayList<>(activities.length); for (LauncherActivityInfo info : activities) { ComponentName cn = info.getComponentName(); - AppTarget target = new AppTarget.Builder(new AppTargetId("app:" + cn)) - .setTarget(cn.getPackageName(), info.getUser()) - .setClassName(cn.getClassName()) - .build(); + AppTarget target = + new AppTarget.Builder(new AppTargetId("app:" + cn), cn.getPackageName(), info.getUser()) + .setClassName(cn.getClassName()) + .build(); targets.add(target); } mCallback.onTargetsAvailable(targets); diff --git a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java index 960d907dca..20fdff2c55 100644 --- a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java +++ b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java @@ -44,7 +44,6 @@ import com.android.launcher3.tapl.LauncherInstrumentation; import com.android.launcher3.testcomponent.TestCommandReceiver; import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TestRule; @@ -98,7 +97,6 @@ public class FallbackRecentsTest { @NavigationModeSwitch(mode = THREE_BUTTON) @Test - @Ignore // b/131630813 public void goToOverviewFromHome() { mDevice.pressHome(); assertTrue("Fallback Launcher not visible", mDevice.wait(Until.hasObject(By.pkg( @@ -109,7 +107,6 @@ public class FallbackRecentsTest { @NavigationModeSwitch(mode = THREE_BUTTON) @Test - @Ignore // b/131630813 public void goToOverviewFromApp() { startAppFast("com.android.settings"); diff --git a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java index 93e403cc84..e552f56ba6 100644 --- a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java +++ b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java @@ -71,8 +71,7 @@ public class NavigationModeSwitchRule implements TestRule { @Override public Statement apply(Statement base, Description description) { - // b/130558787; b/131419978 - if (false && TestHelpers.isInLauncherProcess() && + if (TestHelpers.isInLauncherProcess() && description.getAnnotation(NavigationModeSwitch.class) != null) { Mode mode = description.getAnnotation(NavigationModeSwitch.class).mode(); return new Statement() { diff --git a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java index 8bdb90db79..2111e2ca27 100644 --- a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java +++ b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java @@ -28,6 +28,7 @@ import com.android.launcher3.Launcher; import com.android.launcher3.util.RaceConditionReproducer; import com.android.quickstep.NavigationModeSwitchRule.Mode; import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch; +import com.android.quickstep.inputconsumers.OtherActivityInputConsumer; import org.junit.Before; import org.junit.Ignore; diff --git a/res/drawable-v28/round_rect_folder.xml b/res/drawable-v28/round_rect_folder.xml new file mode 100644 index 0000000000..0403be09b0 --- /dev/null +++ b/res/drawable-v28/round_rect_folder.xml @@ -0,0 +1,21 @@ + + + + + + diff --git a/res/drawable/round_rect_folder.xml b/res/drawable/round_rect_folder.xml new file mode 100644 index 0000000000..8b3d06ca9b --- /dev/null +++ b/res/drawable/round_rect_folder.xml @@ -0,0 +1,21 @@ + + + + + + diff --git a/res/layout/folder_application.xml b/res/layout/folder_application.xml index de861a0ad4..c156e113fb 100644 --- a/res/layout/folder_application.xml +++ b/res/layout/folder_application.xml @@ -18,5 +18,6 @@ xmlns:android="http://schemas.android.com/apk/res/android" xmlns:launcher="http://schemas.android.com/apk/res-auto" style="@style/BaseIcon" + android:textColor="?attr/folderTextColor" android:includeFontPadding="false" launcher:iconDisplay="folder" /> diff --git a/res/layout/hotseat.xml b/res/layout/hotseat.xml new file mode 100644 index 0000000000..82b0b8d74e --- /dev/null +++ b/res/layout/hotseat.xml @@ -0,0 +1,24 @@ + + + \ No newline at end of file diff --git a/res/layout/launcher.xml b/res/layout/launcher.xml index 6ecc1f55d2..9cab9c2a5e 100644 --- a/res/layout/launcher.xml +++ b/res/layout/launcher.xml @@ -39,19 +39,20 @@ launcher:pageIndicator="@+id/page_indicator" /> - + layout="@layout/hotseat" /> + + + + \ No newline at end of file diff --git a/res/layout/user_folder_icon_normalized.xml b/res/layout/user_folder_icon_normalized.xml index 2e6ce946f8..835fee2d3d 100644 --- a/res/layout/user_folder_icon_normalized.xml +++ b/res/layout/user_folder_icon_normalized.xml @@ -18,7 +18,7 @@ xmlns:launcher="http://schemas.android.com/apk/res-auto" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:background="@drawable/round_rect_primary" + android:background="@drawable/round_rect_folder" android:elevation="5dp" android:orientation="vertical" > diff --git a/res/values-v28/styles.xml b/res/values-v28/styles.xml new file mode 100644 index 0000000000..7df9ce541f --- /dev/null +++ b/res/values-v28/styles.xml @@ -0,0 +1,23 @@ + + + + + diff --git a/res/values/attrs.xml b/res/values/attrs.xml index 43194d5cb6..69b8c8a228 100644 --- a/res/values/attrs.xml +++ b/res/values/attrs.xml @@ -36,8 +36,10 @@ + + @@ -55,7 +57,7 @@ - + diff --git a/res/values/dimens.xml b/res/values/dimens.xml index 469b176fb4..0da56dafb9 100644 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -235,6 +235,7 @@ 26dp 194dp + 15dp 8dp diff --git a/res/values/styles.xml b/res/values/styles.xml index 7932c6d608..8116e30ef1 100644 --- a/res/values/styles.xml +++ b/res/values/styles.xml @@ -26,6 +26,7 @@ @android:color/transparent true true + ?attr/workspaceTextColor + + + + - +