Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 21647dd9 authored by Steve Elliott's avatar Steve Elliott
Browse files

Extend lifetime of notifs during activity launch

Clicking on a notification can result in a chain of asynchronous
code, depending on the state of the shade and keyguard. If the
notification that was clicked is cancelled during this async flow, then
when it's finally complete, if the notification has FLAG_AUTO_CANCEL,
SystemUI will attempt to dismiss the notification, which is no longer
there according to the pipeline, due to the aforementioned cancellation.

This CL fixes the issue by introducing a new
ActivityLaunchAnimCoordinater that registers a lifetime extender to keep
notifications alive during their launch animation.

Fixes: 219939676

Test: atest SystemUITests
Test: 1. Install Notify.apk (go/notify-apk)
      2. In the Notify app: Check off Delayed 5000 and Auto Cancel
      3. Click "Add" and spam click "Update" (5-10 times should suffice)
      4. Expand notification shade
      5. Immediately tap the Notify notification when it gets posted
      Observe: no crashing

Change-Id: I819ad840845df1bfa3acd71187c6ba47de9a64b7
parent 3ba165ab
Loading
Loading
Loading
Loading
+95 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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.systemui.statusbar.notification.collection.coordinator

import com.android.systemui.statusbar.notification.collection.NotifPipeline
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender.OnEndLifetimeExtensionCallback
import com.android.systemui.statusbar.phone.NotifActivityLaunchEvents
import dagger.Binds
import dagger.Module
import javax.inject.Inject

/** Extends the lifetime of notifications while their activity launch animation is playing. */
interface ActivityLaunchAnimCoordinator : Coordinator

/** Provides an [ActivityLaunchAnimCoordinator] to [CoordinatorScope]. */
@Module(includes = [PrivateActivityStarterCoordinatorModule::class])
object ActivityLaunchAnimCoordinatorModule

@Module
private interface PrivateActivityStarterCoordinatorModule {
    @Binds
    fun bindCoordinator(impl: ActivityLaunchAnimCoordinatorImpl): ActivityLaunchAnimCoordinator
}

/**
 * Listens for [NotifActivityLaunchEvents], and then extends the lifetimes of any notifs while their
 * launch animation is playing.
 */
@CoordinatorScope
private class ActivityLaunchAnimCoordinatorImpl @Inject constructor(
    private val activityLaunchEvents: NotifActivityLaunchEvents
) : ActivityLaunchAnimCoordinator {
    // Tracks notification launches, and whether or not their lifetimes are extended.
    private val notifsLaunchingActivities = mutableMapOf<String, Boolean>()

    private var onEndLifetimeExtensionCallback: OnEndLifetimeExtensionCallback? = null

    override fun attach(pipeline: NotifPipeline) {
        activityLaunchEvents.registerListener(activityStartEventListener)
        pipeline.addNotificationLifetimeExtender(extender)
    }

    private val activityStartEventListener = object : NotifActivityLaunchEvents.Listener {
        override fun onStartLaunchNotifActivity(entry: NotificationEntry) {
            notifsLaunchingActivities[entry.key] = false
        }

        override fun onFinishLaunchNotifActivity(entry: NotificationEntry) {
            if (notifsLaunchingActivities.remove(entry.key) == true) {
                // If we were extending the lifetime of this notification, stop.
                onEndLifetimeExtensionCallback?.onEndLifetimeExtension(extender, entry)
            }
        }
    }

    private val extender = object : NotifLifetimeExtender {
        override fun getName(): String = "ActivityStarterCoordinator"

        override fun setCallback(callback: OnEndLifetimeExtensionCallback) {
            onEndLifetimeExtensionCallback = callback
        }

        override fun maybeExtendLifetime(entry: NotificationEntry, reason: Int): Boolean {
            if (entry.key in notifsLaunchingActivities) {
                // Track that we're now extending this notif
                notifsLaunchingActivities[entry.key] = true
                return true
            }
            return false
        }

        override fun cancelLifetimeExtension(entry: NotificationEntry) {
            if (entry.key in notifsLaunchingActivities) {
                notifsLaunchingActivities[entry.key] = false
            }
        }
    }
}
+4 −2
Original line number Diff line number Diff line
@@ -58,7 +58,8 @@ class NotifCoordinatorsImpl @Inject constructor(
    smartspaceDedupingCoordinator: SmartspaceDedupingCoordinator,
    viewConfigCoordinator: ViewConfigCoordinator,
    visualStabilityCoordinator: VisualStabilityCoordinator,
    sensitiveContentCoordinator: SensitiveContentCoordinator
    sensitiveContentCoordinator: SensitiveContentCoordinator,
    activityLaunchAnimCoordinator: ActivityLaunchAnimCoordinator
) : NotifCoordinators {

    private val mCoordinators: MutableList<Coordinator> = ArrayList()
@@ -97,6 +98,7 @@ class NotifCoordinatorsImpl @Inject constructor(
        mCoordinators.add(viewConfigCoordinator)
        mCoordinators.add(visualStabilityCoordinator)
        mCoordinators.add(sensitiveContentCoordinator)
        mCoordinators.add(activityLaunchAnimCoordinator)
        if (notifPipelineFlags.isSmartspaceDedupingEnabled()) {
            mCoordinators.add(smartspaceDedupingCoordinator)
        }
+3 −3
Original line number Diff line number Diff line
@@ -34,11 +34,11 @@ import dagger.Binds
import dagger.Module
import javax.inject.Inject

@Module(includes = [PrivateModule::class])
@Module(includes = [PrivateSensitiveContentCoordinatorModule::class])
interface SensitiveContentCoordinatorModule

@Module
private interface PrivateModule {
private interface PrivateSensitiveContentCoordinatorModule {
    @Binds
    fun bindCoordinator(impl: SensitiveContentCoordinatorImpl): SensitiveContentCoordinator
}
+6 −6
Original line number Diff line number Diff line
@@ -32,7 +32,7 @@ import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifStabilityManager;
import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
import com.android.systemui.statusbar.notification.collection.render.NotifPanelEventSource;
import com.android.systemui.statusbar.phone.NotifPanelEvents;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.util.concurrency.DelayableExecutor;

@@ -53,10 +53,10 @@ import javax.inject.Inject;
// TODO(b/204468557): Move to @CoordinatorScope
@SysUISingleton
public class VisualStabilityCoordinator implements Coordinator, Dumpable,
        NotifPanelEventSource.Callbacks {
        NotifPanelEvents.Listener {
    private final DelayableExecutor mDelayableExecutor;
    private final HeadsUpManager mHeadsUpManager;
    private final NotifPanelEventSource mNotifPanelEventSource;
    private final NotifPanelEvents mNotifPanelEvents;
    private final StatusBarStateController mStatusBarStateController;
    private final VisualStabilityProvider mVisualStabilityProvider;
    private final WakefulnessLifecycle mWakefulnessLifecycle;
@@ -87,7 +87,7 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable,
            DelayableExecutor delayableExecutor,
            DumpManager dumpManager,
            HeadsUpManager headsUpManager,
            NotifPanelEventSource notifPanelEventSource,
            NotifPanelEvents notifPanelEvents,
            StatusBarStateController statusBarStateController,
            VisualStabilityProvider visualStabilityProvider,
            WakefulnessLifecycle wakefulnessLifecycle) {
@@ -96,7 +96,7 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable,
        mWakefulnessLifecycle = wakefulnessLifecycle;
        mStatusBarStateController = statusBarStateController;
        mDelayableExecutor = delayableExecutor;
        mNotifPanelEventSource = notifPanelEventSource;
        mNotifPanelEvents = notifPanelEvents;

        dumpManager.registerDumpable(this);
    }
@@ -109,7 +109,7 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable,

        mStatusBarStateController.addCallback(mStatusBarStateControllerListener);
        mPulsing = mStatusBarStateController.isPulsing();
        mNotifPanelEventSource.registerCallbacks(this);
        mNotifPanelEvents.registerListener(this);

        pipeline.setVisualStabilityManager(mNotifStabilityManager);
    }
+6 −2
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.notification.collection.coordinator.dagger

import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.notification.collection.coordinator.ActivityLaunchAnimCoordinatorModule
import com.android.systemui.statusbar.notification.collection.coordinator.NotifCoordinators
import com.android.systemui.statusbar.notification.collection.coordinator.NotifCoordinatorsImpl
import com.android.systemui.statusbar.notification.collection.coordinator.SensitiveContentCoordinatorModule
@@ -47,7 +48,10 @@ interface CoordinatorsSubcomponent {
    }
}

@Module(includes = [SensitiveContentCoordinatorModule::class])
@Module(includes = [
    ActivityLaunchAnimCoordinatorModule::class,
    SensitiveContentCoordinatorModule::class,
])
private abstract class InternalCoordinatorsModule {
    @Binds
    @Internal
Loading