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

Commit be08e7da authored by Yalan Yiue's avatar Yalan Yiue Committed by Android (Google) Code Review
Browse files

Merge "Show notification to launch tutorial when scheduler is ready" into main

parents 5a3dacc1 df8f00c3
Loading
Loading
Loading
Loading
+16 −0
Original line number Diff line number Diff line
@@ -3699,6 +3699,22 @@
          -->
    <string name="shortcut_helper_key_combinations_or_separator">or</string>

    <!-- Keyboard touchpad tutorial scheduler-->
    <!-- Notification title for launching keyboard tutorial [CHAR_LIMIT=100] -->
    <string name="launch_keyboard_tutorial_notification_title">Navigate using your keyboard</string>
    <!-- Notification text for launching keyboard tutorial [CHAR_LIMIT=100] -->
    <string name="launch_keyboard_tutorial_notification_content">Learn keyboards shortcuts</string>

    <!-- Notification title for launching touchpad tutorial [CHAR_LIMIT=100] -->
    <string name="launch_touchpad_tutorial_notification_title">Navigate using your touchpad</string>
    <!-- Notification text for launching keyboard tutorial [CHAR_LIMIT=100] -->
    <string name="launch_touchpad_tutorial_notification_content">Learn touchpad gestures</string>

    <!-- Notification title for launching keyboard tutorial [CHAR_LIMIT=100] -->
    <string name="launch_keyboard_touchpad_tutorial_notification_title">Navigate using your keyboard and touchpad</string>
    <!-- Notification text for launching keyboard tutorial [CHAR_LIMIT=100] -->
    <string name="launch_keyboard_touchpad_tutorial_notification_content">Learn touchpad gestures, keyboards shortcuts, and more</string>

    <!-- TOUCHPAD TUTORIAL-->
    <!-- Label for button opening tutorial for back gesture on touchpad [CHAR LIMIT=NONE] -->
    <string name="touchpad_tutorial_back_gesture_button">Back gesture</string>
+4 −4
Original line number Diff line number Diff line
@@ -18,20 +18,20 @@ package com.android.systemui.inputdevice.tutorial

import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.inputdevice.tutorial.domain.interactor.TutorialSchedulerInteractor
import com.android.systemui.inputdevice.tutorial.ui.TutorialNotificationCoordinator
import com.android.systemui.shared.Flags.newTouchpadGesturesTutorial
import dagger.Lazy
import javax.inject.Inject

/** A [CoreStartable] to launch a scheduler for keyboard and touchpad education */
/** A [CoreStartable] to launch a scheduler for keyboard and touchpad tutorial notification */
@SysUISingleton
class KeyboardTouchpadTutorialCoreStartable
@Inject
constructor(private val tutorialSchedulerInteractor: Lazy<TutorialSchedulerInteractor>) :
constructor(private val tutorialNotificationCoordinator: Lazy<TutorialNotificationCoordinator>) :
    CoreStartable {
    override fun start() {
        if (newTouchpadGesturesTutorial()) {
            tutorialSchedulerInteractor.get().start()
            tutorialNotificationCoordinator.get().start()
        }
    }
}
+17 −26
Original line number Diff line number Diff line
@@ -17,9 +17,7 @@
package com.android.systemui.inputdevice.tutorial.domain.interactor

import android.os.SystemProperties
import android.util.Log
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.inputdevice.tutorial.data.repository.DeviceType
import com.android.systemui.inputdevice.tutorial.data.repository.DeviceType.KEYBOARD
import com.android.systemui.inputdevice.tutorial.data.repository.DeviceType.TOUCHPAD
@@ -31,23 +29,22 @@ import java.time.Instant
import javax.inject.Inject
import kotlin.time.Duration.Companion.hours
import kotlin.time.toKotlinDuration
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.launch

/**
 * When the first time a keyboard or touchpad is connected, wait for [LAUNCH_DELAY], then launch the
 * tutorial as soon as there's a connected device
 * When the first time a keyboard or touchpad is connected, wait for [LAUNCH_DELAY], and as soon as
 * there's a connected device, show a notification to launch the tutorial.
 */
@SysUISingleton
class TutorialSchedulerInteractor
@Inject
constructor(
    @Background private val backgroundScope: CoroutineScope,
    keyboardRepository: KeyboardRepository,
    touchpadRepository: TouchpadRepository,
    private val repo: TutorialSchedulerRepository
@@ -58,17 +55,6 @@ constructor(
            TOUCHPAD to touchpadRepository.isAnyTouchpadConnected
        )

    fun start() {
        backgroundScope.launch {
            // Merging two flows to ensure that launch tutorial is launched consecutively in order
            // to avoid race condition
            merge(touchpadScheduleFlow, keyboardScheduleFlow).collect {
                val tutorialType = resolveTutorialType(it)
                launchTutorial(tutorialType)
            }
        }
    }

    private val touchpadScheduleFlow = flow {
        if (!repo.isLaunched(TOUCHPAD)) {
            schedule(TOUCHPAD)
@@ -95,13 +81,18 @@ constructor(
    private suspend fun waitForDeviceConnection(deviceType: DeviceType) =
        isAnyDeviceConnected[deviceType]!!.filter { it }.first()

    private suspend fun launchTutorial(tutorialType: TutorialType) {
    // Merging two flows ensures that tutorial is launched consecutively to avoid race condition
    val tutorials: Flow<TutorialType> =
        merge(touchpadScheduleFlow, keyboardScheduleFlow).map {
            val tutorialType = resolveTutorialType(it)

            // TODO: notifying time is not oobe launching time - move these updates into oobe
            if (tutorialType == TutorialType.KEYBOARD || tutorialType == TutorialType.BOTH)
                repo.updateLaunchTime(KEYBOARD, Instant.now())
            if (tutorialType == TutorialType.TOUCHPAD || tutorialType == TutorialType.BOTH)
                repo.updateLaunchTime(TOUCHPAD, Instant.now())
        // TODO: launch tutorial
        Log.d(TAG, "Launch tutorial for $tutorialType")

            tutorialType
        }

    private suspend fun resolveTutorialType(deviceType: DeviceType): TutorialType {
+146 −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.inputdevice.tutorial.ui

import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.core.app.NotificationCompat
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.inputdevice.tutorial.domain.interactor.TutorialSchedulerInteractor
import com.android.systemui.inputdevice.tutorial.domain.interactor.TutorialSchedulerInteractor.Companion.TAG
import com.android.systemui.inputdevice.tutorial.domain.interactor.TutorialSchedulerInteractor.TutorialType
import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity
import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_TYPE_BOTH
import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_TYPE_KEY
import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_TYPE_KEYBOARD
import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_TYPE_TOUCHPAD
import com.android.systemui.res.R
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch

/** When the scheduler is due, show a notification to launch tutorial */
@SysUISingleton
class TutorialNotificationCoordinator
@Inject
constructor(
    @Background private val backgroundScope: CoroutineScope,
    @Application private val context: Context,
    private val tutorialSchedulerInteractor: TutorialSchedulerInteractor,
    private val notificationManager: NotificationManager
) {
    fun start() {
        backgroundScope.launch {
            tutorialSchedulerInteractor.tutorials.collect { showNotification(it) }
        }
    }

    // By sharing the same tag and id, we update the content of existing notification instead of
    // creating multiple notifications
    private fun showNotification(tutorialType: TutorialType) {
        if (tutorialType == TutorialType.NONE) return

        if (notificationManager.getNotificationChannel(CHANNEL_ID) == null)
            createNotificationChannel()

        // Replace "System UI" app name with "Android System"
        val extras = Bundle()
        extras.putString(
            Notification.EXTRA_SUBSTITUTE_APP_NAME,
            context.getString(com.android.internal.R.string.android_system_label)
        )

        val info = getNotificationInfo(tutorialType)!!
        val notification =
            NotificationCompat.Builder(context, CHANNEL_ID)
                .setSmallIcon(R.drawable.ic_settings)
                .setContentTitle(info.title)
                .setContentText(info.text)
                .setContentIntent(createPendingIntent(info.type))
                .setPriority(NotificationCompat.PRIORITY_DEFAULT)
                .setAutoCancel(true)
                .addExtras(extras)
                .build()

        notificationManager.notify(TAG, NOTIFICATION_ID, notification)
    }

    private fun createNotificationChannel() {
        val channel =
            NotificationChannel(
                CHANNEL_ID,
                context.getString(com.android.internal.R.string.android_system_label),
                NotificationManager.IMPORTANCE_DEFAULT
            )
        notificationManager.createNotificationChannel(channel)
    }

    private fun createPendingIntent(tutorialType: String): PendingIntent {
        val intent =
            Intent(context, KeyboardTouchpadTutorialActivity::class.java).apply {
                putExtra(INTENT_TUTORIAL_TYPE_KEY, tutorialType)
                flags = Intent.FLAG_ACTIVITY_NEW_TASK
            }
        return PendingIntent.getActivity(
            context,
            /* requestCode= */ 0,
            intent,
            PendingIntent.FLAG_IMMUTABLE
        )
    }

    private data class NotificationInfo(val title: String, val text: String, val type: String)

    private fun getNotificationInfo(tutorialType: TutorialType): NotificationInfo? =
        when (tutorialType) {
            TutorialType.KEYBOARD ->
                NotificationInfo(
                    context.getString(R.string.launch_keyboard_tutorial_notification_title),
                    context.getString(R.string.launch_keyboard_tutorial_notification_content),
                    INTENT_TUTORIAL_TYPE_KEYBOARD
                )
            TutorialType.TOUCHPAD ->
                NotificationInfo(
                    context.getString(R.string.launch_touchpad_tutorial_notification_title),
                    context.getString(R.string.launch_touchpad_tutorial_notification_content),
                    INTENT_TUTORIAL_TYPE_TOUCHPAD
                )
            TutorialType.BOTH ->
                NotificationInfo(
                    context.getString(
                        R.string.launch_keyboard_touchpad_tutorial_notification_title
                    ),
                    context.getString(
                        R.string.launch_keyboard_touchpad_tutorial_notification_content
                    ),
                    INTENT_TUTORIAL_TYPE_BOTH
                )
            TutorialType.NONE -> null
        }

    companion object {
        private const val CHANNEL_ID = "TutorialSchedulerNotificationChannel"
        private const val NOTIFICATION_ID = 5566
    }
}
+1 −0
Original line number Diff line number Diff line
@@ -54,6 +54,7 @@ constructor(
        const val INTENT_TUTORIAL_TYPE_KEY = "tutorial_type"
        const val INTENT_TUTORIAL_TYPE_TOUCHPAD = "touchpad"
        const val INTENT_TUTORIAL_TYPE_KEYBOARD = "keyboard"
        const val INTENT_TUTORIAL_TYPE_BOTH = "both"
    }

    private val vm by
Loading