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

Commit 8a83f07a authored by Lyn Han's avatar Lyn Han Committed by Android (Google) Code Review
Browse files

Merge "Add FSI repo" into tm-qpr-dev

parents 92a79033 3f254059
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -40,6 +40,7 @@ import com.android.systemui.reardisplay.RearDisplayDialogController
import com.android.systemui.recents.Recents
import com.android.systemui.settings.dagger.MultiUserUtilsModule
import com.android.systemui.shortcut.ShortcutKeyDispatcher
import com.android.systemui.statusbar.notification.fsi.FsiChromeRepo
import com.android.systemui.statusbar.notification.InstantAppNotifier
import com.android.systemui.statusbar.phone.KeyguardLiftController
import com.android.systemui.stylus.StylusUsiPowerStartable
@@ -79,6 +80,12 @@ abstract class SystemUICoreStartableModule {
    @ClassKey(ClipboardListener::class)
    abstract fun bindClipboardListener(sysui: ClipboardListener): CoreStartable

    /** Inject into FsiChromeRepo.  */
    @Binds
    @IntoMap
    @ClassKey(FsiChromeRepo::class)
    abstract fun bindFSIChromeRepo(sysui: FsiChromeRepo): CoreStartable

    /** Inject into GarbageMonitor.Service.  */
    @Binds
    @IntoMap
+102 −0
Original line number Diff line number Diff line
package com.android.systemui.statusbar.notification.fsi

import android.app.PendingIntent
import android.content.Context
import android.content.pm.PackageManager
import android.graphics.drawable.Drawable
import android.os.RemoteException
import android.service.dreams.IDreamManager
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.provider.LaunchFullScreenIntentProvider
import com.android.systemui.statusbar.notification.fsi.FsiDebug.Companion.log
import com.android.systemui.statusbar.phone.CentralSurfaces
import java.util.concurrent.Executor
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow

/**
 * Class that bridges the gap between clean app architecture and existing code. Provides new
 * implementation of StatusBarNotificationActivityStarter launchFullscreenIntent that pipes
 * one-directional data => FsiChromeViewModel => FsiChromeView.
 */
@SysUISingleton
class FsiChromeRepo
@Inject
constructor(
    private val context: Context,
    private val pm: PackageManager,
    private val keyguardRepo: KeyguardRepository,
    private val launchFullScreenIntentProvider: LaunchFullScreenIntentProvider,
    private val featureFlags: FeatureFlags,
    private val uiBgExecutor: Executor,
    private val dreamManager: IDreamManager,
    private val centralSurfaces: CentralSurfaces
) : CoreStartable {

    companion object {
        private const val classTag = "FsiChromeRepo"
    }

    data class FSIInfo(
        val appName: String,
        val appIcon: Drawable,
        val fullscreenIntent: PendingIntent
    )

    private val _infoFlow = MutableStateFlow<FSIInfo?>(null)
    val infoFlow: StateFlow<FSIInfo?> = _infoFlow

    override fun start() {
        log("$classTag start listening for FSI notifications")

        // Listen for FSI launch events for the lifetime of SystemUI.
        launchFullScreenIntentProvider.registerListener { entry -> launchFullscreenIntent(entry) }
    }

    fun dismiss() {
        _infoFlow.value = null
    }

    fun onFullscreen() {
        // TODO(b/243421660) implement transition from container to fullscreen
    }

    fun stopScreenSaver() {
        uiBgExecutor.execute {
            try {
                dreamManager.awaken()
            } catch (e: RemoteException) {
                e.printStackTrace()
            }
        }
    }

    fun launchFullscreenIntent(entry: NotificationEntry) {
        if (!featureFlags.isEnabled(Flags.FSI_CHROME)) {
            return
        }
        if (!keyguardRepo.isKeyguardShowing()) {
            return
        }
        stopScreenSaver()

        var appName = pm.getApplicationLabel(context.applicationInfo) as String
        val appIcon = pm.getApplicationIcon(context.packageName)
        val fullscreenIntent = entry.sbn.notification.fullScreenIntent

        log("FsiChromeRepo launchFullscreenIntent appName=$appName appIcon $appIcon")
        _infoFlow.value = FSIInfo(appName, appIcon, fullscreenIntent)

        // If screen is off or we're showing AOD, show lockscreen.
        centralSurfaces.wakeUpForFullScreenIntent()

        // Don't show HUN since we're already showing FSI.
        entry.notifyFullScreenIntentLaunched()
    }
}
+10 −2
Original line number Diff line number Diff line
@@ -50,6 +50,8 @@ import com.android.systemui.ActivityIntentHelper;
import com.android.systemui.EventLogTags;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.shade.ShadeController;
@@ -106,6 +108,7 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte
    private final LockPatternUtils mLockPatternUtils;
    private final StatusBarRemoteInputCallback mStatusBarRemoteInputCallback;
    private final ActivityIntentHelper mActivityIntentHelper;
    private final FeatureFlags mFeatureFlags;

    private final MetricsLogger mMetricsLogger;
    private final StatusBarNotificationActivityStarterLogger mLogger;
@@ -149,7 +152,8 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte
            NotificationPanelViewController panel,
            ActivityLaunchAnimator activityLaunchAnimator,
            NotificationLaunchAnimatorControllerProvider notificationAnimationProvider,
            LaunchFullScreenIntentProvider launchFullScreenIntentProvider) {
            LaunchFullScreenIntentProvider launchFullScreenIntentProvider,
            FeatureFlags featureFlags) {
        mContext = context;
        mMainThreadHandler = mainThreadHandler;
        mUiBgExecutor = uiBgExecutor;
@@ -170,6 +174,7 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte
        mLockPatternUtils = lockPatternUtils;
        mStatusBarRemoteInputCallback = remoteInputCallback;
        mActivityIntentHelper = activityIntentHelper;
        mFeatureFlags = featureFlags;
        mMetricsLogger = metricsLogger;
        mLogger = logger;
        mOnUserInteractionCallback = onUserInteractionCallback;
@@ -548,7 +553,10 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte
            mLogger.logFullScreenIntentSuppressedByVR(entry);
            return;
        }

        if (mFeatureFlags.isEnabled(Flags.FSI_CHROME)) {
            // FsiChromeRepo runs its own implementation of launchFullScreenIntent
            return;
        }
        // Stop screensaver if the notification has a fullscreen intent.
        // (like an incoming phone call)
        mUiBgExecutor.execute(() -> {
+210 −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.fsi

import android.R
import android.app.Notification
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.graphics.drawable.Drawable
import android.os.UserHandle
import android.service.dreams.IDreamManager
import android.service.notification.StatusBarNotification
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.provider.LaunchFullScreenIntentProvider
import com.android.systemui.statusbar.phone.CentralSurfaces
import java.util.concurrent.Executor
import junit.framework.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.anyString
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations

@SmallTest
@RunWith(AndroidTestingRunner::class)
@RunWithLooper(setAsMainLooper = true)
class FsiChromeRepoTest : SysuiTestCase() {

    @Mock lateinit var centralSurfaces: CentralSurfaces
    @Mock lateinit var fsiChromeRepo: FsiChromeRepo
    @Mock lateinit var packageManager: PackageManager

    var keyguardRepo = FakeKeyguardRepository()
    @Mock private lateinit var applicationInfo: ApplicationInfo

    @Mock lateinit var launchFullScreenIntentProvider: LaunchFullScreenIntentProvider
    var featureFlags = FakeFeatureFlags()
    @Mock lateinit var dreamManager: IDreamManager

    // Execute all foreground & background requests immediately
    private val uiBgExecutor = Executor { r -> r.run() }

    private val appName: String = "appName"
    private val appIcon: Drawable = context.getDrawable(com.android.systemui.R.drawable.ic_android)
    private val fsi: PendingIntent = Mockito.mock(PendingIntent::class.java)

    @Before
    fun setUp() {
        MockitoAnnotations.initMocks(this)

        // Set up package manager mocks
        whenever(packageManager.getApplicationIcon(anyString())).thenReturn(appIcon)
        whenever(packageManager.getApplicationIcon(any(ApplicationInfo::class.java)))
            .thenReturn(appIcon)
        whenever(packageManager.getApplicationLabel(any())).thenReturn(appName)
        mContext.setMockPackageManager(packageManager)

        fsiChromeRepo =
            FsiChromeRepo(
                mContext,
                packageManager,
                keyguardRepo,
                launchFullScreenIntentProvider,
                featureFlags,
                uiBgExecutor,
                dreamManager,
                centralSurfaces
            )
    }

    private fun createFsiEntry(fsi: PendingIntent): NotificationEntry {
        val nb =
            Notification.Builder(mContext, "a")
                .setContentTitle("foo")
                .setSmallIcon(R.drawable.sym_def_app_icon)
                .setFullScreenIntent(fsi, /* highPriority= */ true)

        val sbn =
            StatusBarNotification(
                "pkg",
                "opPkg",
                /* id= */ 0,
                "tag" + System.currentTimeMillis(),
                /* uid= */ 0,
                /* initialPid */ 0,
                nb.build(),
                UserHandle(0),
                /* overrideGroupKey= */ null,
                /* postTime= */ 0
            )

        val entry = Mockito.mock(NotificationEntry::class.java)
        whenever(entry.importance).thenReturn(NotificationManager.IMPORTANCE_HIGH)
        whenever(entry.sbn).thenReturn(sbn)
        return entry
    }

    @Test
    fun testLaunchFullscreenIntent_flagNotEnabled_noLaunch() {
        // Setup
        featureFlags.set(Flags.FSI_CHROME, false)

        // Test
        val entry = createFsiEntry(fsi)
        fsiChromeRepo.launchFullscreenIntent(entry)

        // Verify
        Mockito.verify(centralSurfaces, never()).wakeUpForFullScreenIntent()
    }

    @Test
    fun testLaunchFullscreenIntent_notOnKeyguard_noLaunch() {
        // Setup
        featureFlags.set(Flags.FSI_CHROME, true)
        keyguardRepo.setKeyguardShowing(false)

        // Test
        val entry = createFsiEntry(fsi)
        fsiChromeRepo.launchFullscreenIntent(entry)

        // Verify
        Mockito.verify(centralSurfaces, never()).wakeUpForFullScreenIntent()
    }

    @Test
    fun testLaunchFullscreenIntent_stopsScreensaver() {
        // Setup
        featureFlags.set(Flags.FSI_CHROME, true)
        keyguardRepo.setKeyguardShowing(true)

        // Test
        val entry = createFsiEntry(fsi)
        fsiChromeRepo.launchFullscreenIntent(entry)

        // Verify
        Mockito.verify(dreamManager, times(1)).awaken()
    }

    @Test
    fun testLaunchFullscreenIntent_updatesFsiInfoFlow() {
        // Setup
        featureFlags.set(Flags.FSI_CHROME, true)
        keyguardRepo.setKeyguardShowing(true)

        // Test
        val entry = createFsiEntry(fsi)
        fsiChromeRepo.launchFullscreenIntent(entry)

        // Verify
        val expectedFsiInfo = FsiChromeRepo.FSIInfo(appName, appIcon, fsi)
        assertEquals(expectedFsiInfo, fsiChromeRepo.infoFlow.value)
    }

    @Test
    fun testLaunchFullscreenIntent_notifyFsiLaunched() {
        // Setup
        featureFlags.set(Flags.FSI_CHROME, true)
        keyguardRepo.setKeyguardShowing(true)

        // Test
        val entry = createFsiEntry(fsi)
        fsiChromeRepo.launchFullscreenIntent(entry)

        // Verify
        Mockito.verify(entry, times(1)).notifyFullScreenIntentLaunched()
    }

    @Test
    fun testLaunchFullscreenIntent_wakesUpDevice() {
        // Setup
        featureFlags.set(Flags.FSI_CHROME, true)
        keyguardRepo.setKeyguardShowing(true)

        // Test
        val entry = createFsiEntry(fsi)
        fsiChromeRepo.launchFullscreenIntent(entry)

        // Verify
        Mockito.verify(centralSurfaces, times(1)).wakeUpForFullScreenIntent()
    }
}
+3 −1
Original line number Diff line number Diff line
@@ -55,6 +55,7 @@ import com.android.systemui.ActivityIntentHelper;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -220,7 +221,8 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase {
                        mock(NotificationPanelViewController.class),
                        mActivityLaunchAnimator,
                        notificationAnimationProvider,
                        mock(LaunchFullScreenIntentProvider.class)
                        mock(LaunchFullScreenIntentProvider.class),
                        mock(FeatureFlags.class)
                );

        // set up dismissKeyguardThenExecute to synchronously invoke the OnDismissAction arg