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

Commit ae0c57f4 authored by Jeff DeCew's avatar Jeff DeCew
Browse files

Pause ViewFlippers while lock screen is showing

Fixes: 309146176
Flag: ACONFIG com.android.systemui.notification_view_flipper_pausing DEVELOPMENT
Test: atest SystemUITests
Merged-In: If6a35e062246becdcdb1430ccce6bb79bbe96d02
Change-Id: If6a35e062246becdcdb1430ccce6bb79bbe96d02
parent 56fd37ce
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -24,6 +24,16 @@ flag {
   }
}

flag {
   name: "notification_view_flipper_pausing"
   namespace: "systemui"
   description: "Pause ViewFlippers inside Notification custom layouts when the shade is closed."
   bug: "309146176"
   metadata {
        purpose: PURPOSE_BUGFIX
   }
}

flag {
    name: "notification_async_group_header_inflation"
    namespace: "systemui"
+7 −1
Original line number Diff line number Diff line
@@ -19,7 +19,9 @@ package com.android.systemui.statusbar.notification.row
import android.widget.flags.Flags.notifLinearlayoutOptimized
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.statusbar.notification.shared.NotificationViewFlipperPausing
import javax.inject.Inject
import javax.inject.Provider

interface NotifRemoteViewsFactoryContainer {
    val factories: Set<NotifRemoteViewsFactory>
@@ -31,7 +33,8 @@ constructor(
    featureFlags: FeatureFlags,
    precomputedTextViewFactory: PrecomputedTextViewFactory,
    bigPictureLayoutInflaterFactory: BigPictureLayoutInflaterFactory,
    optimizedLinearLayoutFactory: NotificationOptimizedLinearLayoutFactory
    optimizedLinearLayoutFactory: NotificationOptimizedLinearLayoutFactory,
    notificationViewFlipperFactory: Provider<NotificationViewFlipperFactory>,
) : NotifRemoteViewsFactoryContainer {
    override val factories: Set<NotifRemoteViewsFactory> = buildSet {
        add(precomputedTextViewFactory)
@@ -41,5 +44,8 @@ constructor(
        if (notifLinearlayoutOptimized()) {
            add(optimizedLinearLayoutFactory)
        }
        if (NotificationViewFlipperPausing.isEnabled) {
            add(notificationViewFlipperFactory.get())
        }
    }
}
+59 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.row

import android.content.Context
import android.util.AttributeSet
import android.view.View
import android.widget.ViewFlipper
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag
import com.android.systemui.statusbar.notification.row.ui.viewbinder.NotificationViewFlipperBinder
import com.android.systemui.statusbar.notification.row.ui.viewmodel.NotificationViewFlipperViewModel
import com.android.systemui.statusbar.notification.shared.NotificationViewFlipperPausing
import javax.inject.Inject

/**
 * A factory which owns the construction of any ViewFlipper inside of Notifications, and binds it
 * with a view model. This ensures that ViewFlippers are paused when the keyguard is showing.
 */
class NotificationViewFlipperFactory
@Inject
constructor(
    private val viewModel: NotificationViewFlipperViewModel,
) : NotifRemoteViewsFactory {
    init {
        /* check if */ NotificationViewFlipperPausing.isUnexpectedlyInLegacyMode()
    }

    override fun instantiate(
        row: ExpandableNotificationRow,
        @InflationFlag layoutType: Int,
        parent: View?,
        name: String,
        context: Context,
        attrs: AttributeSet
    ): View? {
        return when (name) {
            ViewFlipper::class.java.name,
            ViewFlipper::class.java.simpleName ->
                ViewFlipper(context, attrs).also { viewFlipper ->
                    NotificationViewFlipperBinder.bindWhileAttached(viewFlipper, viewModel)
                }
            else -> null
        }
    }
}
+54 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.row.ui.viewbinder

import android.widget.ViewFlipper
import androidx.lifecycle.lifecycleScope
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.statusbar.notification.row.ui.viewmodel.NotificationViewFlipperViewModel
import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch

/** Binds a [NotificationViewFlipper] to its [view model][NotificationViewFlipperViewModel]. */
object NotificationViewFlipperBinder {
    fun bindWhileAttached(
        viewFlipper: ViewFlipper,
        viewModel: NotificationViewFlipperViewModel,
    ): DisposableHandle {
        if (viewFlipper.isAutoStart) {
            // If the ViewFlipper is not set to AutoStart, the pause binding is meaningless
            return DisposableHandle {}
        }
        return viewFlipper.repeatWhenAttached {
            lifecycleScope.launch { bind(viewFlipper, viewModel) }
        }
    }

    suspend fun bind(
        viewFlipper: ViewFlipper,
        viewModel: NotificationViewFlipperViewModel,
    ) = coroutineScope { launch { viewModel.isPaused.collect { viewFlipper.setPaused(it) } } }

    private fun ViewFlipper.setPaused(paused: Boolean) {
        if (paused) {
            stopFlipping()
        } else if (isAutoStart) {
            startFlipping()
        }
    }
}
+39 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.row.ui.viewmodel

import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dump.DumpManager
import com.android.systemui.statusbar.notification.shared.NotificationViewFlipperPausing
import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackInteractor
import com.android.systemui.util.kotlin.FlowDumperImpl
import javax.inject.Inject

/** A model which represents whether ViewFlippers inside notifications should be paused. */
@SysUISingleton
class NotificationViewFlipperViewModel
@Inject
constructor(
    dumpManager: DumpManager,
    stackInteractor: NotificationStackInteractor,
) : FlowDumperImpl(dumpManager) {
    init {
        /* check if */ NotificationViewFlipperPausing.isUnexpectedlyInLegacyMode()
    }

    val isPaused = stackInteractor.isShowingOnLockscreen.dumpWhileCollecting("isPaused")
}
Loading