From 3b9d80d2d8f61920f66ab4fef0bcae496bdcab66 Mon Sep 17 00:00:00 2001 From: Schneider Victor-tulias Date: Fri, 10 Nov 2023 11:52:25 -0500 Subject: [PATCH] Clean up NPEs in AbsSwipeUpHandler AbsSwipeUphandler has many potential and common NPEs. Added more null checks to AbsSwipeUpHandler Flag: N/A Fixes: 295905702 Fixes: 309535060 Test: StartLauncherViaGestureTests, quick switched and launched app from recents Change-Id: I11f62eac423ae3c5792ce97ca49963f1e005b289 --- .../android/quickstep/AbsSwipeUpHandler.java | 195 ++++++++++-------- .../android/launcher3/dragndrop/DragView.java | 3 +- 2 files changed, 110 insertions(+), 88 deletions(-) diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java index b06a97842c..87c774f3cb 100644 --- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java +++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java @@ -20,6 +20,7 @@ import static android.view.Surface.ROTATION_0; import static android.view.Surface.ROTATION_270; import static android.view.Surface.ROTATION_90; import static android.widget.Toast.LENGTH_SHORT; + import static com.android.app.animation.Interpolators.ACCELERATE_DECELERATE; import static com.android.app.animation.Interpolators.DECELERATE; import static com.android.app.animation.Interpolators.OVERSHOOT_1_2; @@ -80,6 +81,7 @@ import android.os.SystemClock; import android.util.Log; import android.view.MotionEvent; import android.view.RemoteAnimationTarget; +import android.view.SurfaceControl; import android.view.View; import android.view.View.OnApplyWindowInsetsListener; import android.view.ViewGroup; @@ -178,7 +180,7 @@ public abstract class AbsSwipeUpHandler, protected @Nullable RecentsAnimationController mRecentsAnimationController; protected @Nullable RecentsAnimationController mDeferredCleanupRecentsAnimationController; protected RecentsAnimationTargets mRecentsAnimationTargets; - protected T mActivity; + protected @Nullable T mActivity; protected @Nullable Q mRecentsView; protected Runnable mGestureEndCallback; protected MultiStateCallback mStateCallback; @@ -548,7 +550,7 @@ public abstract class AbsSwipeUpHandler, private void onLauncherStart() { final T activity = mActivityInterface.getCreatedActivity(); - if (mActivity != activity) { + if (activity == null || mActivity != activity) { return; } if (mStateCallback.hasStates(STATE_HANDLER_INVALIDATED)) { @@ -921,6 +923,7 @@ public abstract class AbsSwipeUpHandler, // needs to be canceled mRecentsAnimationController.setWillFinishToHome(swipeUpThresholdPassed); + if (mActivity == null) return; if (swipeUpThresholdPassed) { mActivity.getSystemUiController().updateUiState(UI_STATE_FULLSCREEN_TASK, 0); } else { @@ -1496,7 +1499,9 @@ public abstract class AbsSwipeUpHandler, if (mGestureState.getEndTarget().isLauncher) { // This is also called when the launcher is resumed, in order to clear the pending // widgets that have yet to be configured. - DragView.removeAllViews(mActivity); + if (mActivity != null) { + DragView.removeAllViews(mActivity); + } TaskStackChangeListeners.getInstance().registerTaskStackListener( mActivityRestartListener); @@ -1859,11 +1864,9 @@ public abstract class AbsSwipeUpHandler, } public void onConsumerAboutToBeSwitched() { - if (mActivity != null) { - // In the off chance that the gesture ends before Launcher is started, we should clear - // the callback here so that it doesn't update with the wrong state - resetLauncherListeners(); - } + // In the off chance that the gesture ends before Launcher is started, we should clear + // the callback here so that it doesn't update with the wrong state + resetLauncherListeners(); if (mGestureState.isRecentsAnimationRunning() && mGestureState.getEndTarget() != null && !mGestureState.getEndTarget().isLauncher) { // Continued quick switch. @@ -1998,11 +2001,12 @@ public abstract class AbsSwipeUpHandler, * continued quick switch gesture, which cancels the previous handler but doesn't invalidate it. */ private void resetLauncherListeners() { - mActivity.removeEventCallback(EVENT_STARTED, mLauncherOnStartCallback); - mActivity.removeEventCallback(EVENT_DESTROYED, mLauncherOnDestroyCallback); - - mActivity.getRootView().setOnApplyWindowInsetsListener(null); + if (mActivity != null) { + mActivity.removeEventCallback(EVENT_STARTED, mLauncherOnStartCallback); + mActivity.removeEventCallback(EVENT_DESTROYED, mLauncherOnDestroyCallback); + mActivity.getRootView().setOnApplyWindowInsetsListener(null); + } if (mRecentsView != null) { mRecentsView.removeOnScrollChangedListener(mOnRecentsScrollListener); } @@ -2038,10 +2042,12 @@ public abstract class AbsSwipeUpHandler, // Update the screenshot of the task if (shouldUpdate) { UI_HELPER_EXECUTOR.execute(() -> { - if (mRecentsAnimationController == null) return; + RecentsAnimationController recentsAnimationController = + mRecentsAnimationController; + if (recentsAnimationController == null) return; for (int id : runningTaskIds) { mTaskSnapshotCache.put( - id, mRecentsAnimationController.screenshotTask(id)); + id, recentsAnimationController.screenshotTask(id)); } MAIN_EXECUTOR.execute(() -> { @@ -2311,7 +2317,7 @@ public abstract class AbsSwipeUpHandler, } @Override - public void onRecentsAnimationFinished(RecentsAnimationController controller) { + public void onRecentsAnimationFinished(@NonNull RecentsAnimationController controller) { mRecentsAnimationController = null; mRecentsAnimationTargets = null; if (mRecentsView != null) { @@ -2321,79 +2327,94 @@ public abstract class AbsSwipeUpHandler, @Override public void onTasksAppeared(@NonNull RemoteAnimationTarget[] appearedTaskTargets) { - if (mRecentsAnimationController != null) { - boolean hasStartedTaskBefore = Arrays.stream(appearedTaskTargets).anyMatch( - mGestureState.mLastStartedTaskIdPredicate); - if (!mStateCallback.hasStates(STATE_GESTURE_COMPLETED) && !hasStartedTaskBefore) { - // This is a special case, if a task is started mid-gesture that wasn't a part of a - // previous quickswitch task launch, then cancel the animation back to the app - RemoteAnimationTarget appearedTaskTarget = appearedTaskTargets[0]; - TaskInfo taskInfo = appearedTaskTarget.taskInfo; - ActiveGestureLog.INSTANCE.addLog( - new ActiveGestureLog.CompoundString("Unexpected task appeared") - .append(" id=") - .append(taskInfo.taskId) - .append(" pkg=") - .append(taskInfo.baseIntent.getComponent().getPackageName())); - finishRecentsAnimationOnTasksAppeared(null /* onFinishComplete */); - } else if (handleTaskAppeared(appearedTaskTargets)) { - Optional taskTargetOptional = - Arrays.stream(appearedTaskTargets) - .filter(mGestureState.mLastStartedTaskIdPredicate) - .findFirst(); - if (!taskTargetOptional.isPresent()) { - ActiveGestureLog.INSTANCE.addLog("No appeared task matching started task id"); - finishRecentsAnimationOnTasksAppeared(null /* onFinishComplete */); - return; - } - RemoteAnimationTarget taskTarget = taskTargetOptional.get(); - TaskView taskView = mRecentsView == null - ? null : mRecentsView.getTaskViewByTaskId(taskTarget.taskId); - if (taskView == null || !taskView.getThumbnail().shouldShowSplashView()) { - ActiveGestureLog.INSTANCE.addLog("Invalid task view splash state"); - finishRecentsAnimationOnTasksAppeared(null /* onFinishComplete */); - return; - } - - ViewGroup splashView = mActivity.getDragLayer(); - final QuickstepLauncher quickstepLauncher = mActivity instanceof QuickstepLauncher - ? (QuickstepLauncher) mActivity : null; - if (quickstepLauncher != null) { - quickstepLauncher.getDepthController().pauseBlursOnWindows(true); - } - - // When revealing the app with launcher splash screen, make the app visible - // and behind the splash view before the splash is animated away. - SurfaceTransactionApplier surfaceApplier = - new SurfaceTransactionApplier(splashView); - SurfaceTransaction transaction = new SurfaceTransaction(); - for (RemoteAnimationTarget target : appearedTaskTargets) { - transaction.forSurface(target.leash).setAlpha(1).setLayer(-1).setShow(); - } - surfaceApplier.scheduleApply(transaction); - - SplashScreenExitAnimationUtils.startAnimations(splashView, taskTarget.leash, - mSplashMainWindowShiftLength, new TransactionPool(), new Rect(), - SPLASH_ANIMATION_DURATION, SPLASH_FADE_OUT_DURATION, - /* iconStartAlpha= */ 0, /* brandingStartAlpha= */ 0, - SPLASH_APP_REVEAL_DELAY, SPLASH_APP_REVEAL_DURATION, - new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - // Hiding launcher which shows the app surface behind, then - // finishing recents to the app. After transition finish, showing - // the views on launcher again, so it can be visible when next - // animation starts. - splashView.setAlpha(0); - if (quickstepLauncher != null) { - quickstepLauncher.getDepthController() - .pauseBlursOnWindows(false); - } - finishRecentsAnimationOnTasksAppeared(() -> splashView.setAlpha(1)); - } - }); - } + if (mRecentsAnimationController == null) { + return; } + boolean hasStartedTaskBefore = Arrays.stream(appearedTaskTargets).anyMatch( + mGestureState.mLastStartedTaskIdPredicate); + if (!mStateCallback.hasStates(STATE_GESTURE_COMPLETED) && !hasStartedTaskBefore) { + // This is a special case, if a task is started mid-gesture that wasn't a part of a + // previous quickswitch task launch, then cancel the animation back to the app + RemoteAnimationTarget appearedTaskTarget = appearedTaskTargets[0]; + TaskInfo taskInfo = appearedTaskTarget.taskInfo; + ActiveGestureLog.INSTANCE.addLog( + new ActiveGestureLog.CompoundString("Unexpected task appeared") + .append(" id=") + .append(taskInfo.taskId) + .append(" pkg=") + .append(taskInfo.baseIntent.getComponent().getPackageName())); + finishRecentsAnimationOnTasksAppeared(null /* onFinishComplete */); + return; + } + if (!handleTaskAppeared(appearedTaskTargets)) { + return; + } + Optional taskTargetOptional = + Arrays.stream(appearedTaskTargets) + .filter(mGestureState.mLastStartedTaskIdPredicate) + .findFirst(); + if (!taskTargetOptional.isPresent()) { + ActiveGestureLog.INSTANCE.addLog("No appeared task matching started task id"); + finishRecentsAnimationOnTasksAppeared(null /* onFinishComplete */); + return; + } + RemoteAnimationTarget taskTarget = taskTargetOptional.get(); + TaskView taskView = mRecentsView == null + ? null : mRecentsView.getTaskViewByTaskId(taskTarget.taskId); + if (taskView == null || !taskView.getThumbnail().shouldShowSplashView()) { + ActiveGestureLog.INSTANCE.addLog("Invalid task view splash state"); + finishRecentsAnimationOnTasksAppeared(null /* onFinishComplete */); + return; + } + if (mActivity == null) { + ActiveGestureLog.INSTANCE.addLog("Activity destroyed"); + finishRecentsAnimationOnTasksAppeared(null /* onFinishComplete */); + return; + } + animateSplashScreenExit(mActivity, appearedTaskTargets, taskTarget.leash); + } + + private void animateSplashScreenExit( + @NonNull T activity, + @NonNull RemoteAnimationTarget[] appearedTaskTargets, + @NonNull SurfaceControl leash) { + ViewGroup splashView = activity.getDragLayer(); + final QuickstepLauncher quickstepLauncher = activity instanceof QuickstepLauncher + ? (QuickstepLauncher) activity : null; + if (quickstepLauncher != null) { + quickstepLauncher.getDepthController().pauseBlursOnWindows(true); + } + + // When revealing the app with launcher splash screen, make the app visible + // and behind the splash view before the splash is animated away. + SurfaceTransactionApplier surfaceApplier = + new SurfaceTransactionApplier(splashView); + SurfaceTransaction transaction = new SurfaceTransaction(); + for (RemoteAnimationTarget target : appearedTaskTargets) { + transaction.forSurface(target.leash).setAlpha(1).setLayer(-1).setShow(); + } + surfaceApplier.scheduleApply(transaction); + + SplashScreenExitAnimationUtils.startAnimations(splashView, leash, + mSplashMainWindowShiftLength, new TransactionPool(), new Rect(), + SPLASH_ANIMATION_DURATION, SPLASH_FADE_OUT_DURATION, + /* iconStartAlpha= */ 0, /* brandingStartAlpha= */ 0, + SPLASH_APP_REVEAL_DELAY, SPLASH_APP_REVEAL_DURATION, + new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + // Hiding launcher which shows the app surface behind, then + // finishing recents to the app. After transition finish, showing + // the views on launcher again, so it can be visible when next + // animation starts. + splashView.setAlpha(0); + if (quickstepLauncher != null) { + quickstepLauncher.getDepthController() + .pauseBlursOnWindows(false); + } + finishRecentsAnimationOnTasksAppeared(() -> splashView.setAlpha(1)); + } + }); } private void finishRecentsAnimationOnTasksAppeared(Runnable onFinishComplete) { diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java index c2d9e025c1..1f0735243d 100644 --- a/src/com/android/launcher3/dragndrop/DragView.java +++ b/src/com/android/launcher3/dragndrop/DragView.java @@ -50,6 +50,7 @@ import android.view.ViewGroup; import android.widget.FrameLayout; import android.widget.ImageView; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.dynamicanimation.animation.FloatPropertyCompat; import androidx.dynamicanimation.animation.SpringAnimation; @@ -614,7 +615,7 @@ public abstract class DragView extends Fram /** * Removes any stray DragView from the DragLayer. */ - public static void removeAllViews(ActivityContext activity) { + public static void removeAllViews(@NonNull ActivityContext activity) { BaseDragLayer dragLayer = activity.getDragLayer(); // Iterate in reverse order. DragView is added later to the dragLayer, // and will be one of the last views.