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

Commit b708b398 authored by Tianfan Zhang's avatar Tianfan Zhang
Browse files

Create UnderlayRepository and UnderlayInteractor to trigger UnderlayView creation/destruction

logic. The broadcast should be a temporary solution and may be
migrated to Binder very soon.

Bug: 403422950
Flag: com.android.systemui.enable_underlay
Test: local build and unit test
Change-Id: Ia349994d82e2f0f82f584dfd8c97df7c7002582e
parent 941e4343
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.systemui.underlay

import com.android.systemui.CoreStartable
import com.android.systemui.underlay.ui.startable.UnderlayCoreStartable
import dagger.Binds
import dagger.Module
import dagger.multibindings.ClassKey
+48 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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.underlay

import android.graphics.PixelFormat
import android.view.Gravity
import android.view.WindowManager.LayoutParams

object UnderlayUtils {
    fun getUnderlayLayoutParams(
        width: Int,
        height: Int,
        readyToShowUnderlay: Boolean,
    ): LayoutParams {
        val touchFlag =
            if (readyToShowUnderlay) {
                LayoutParams.FLAG_NOT_TOUCH_MODAL
            } else {
                LayoutParams.FLAG_NOT_TOUCHABLE
            }
        return LayoutParams(
                width,
                height,
                LayoutParams.TYPE_STATUS_BAR_ADDITIONAL,
                LayoutParams.FLAG_NOT_FOCUSABLE or touchFlag,
                PixelFormat.TRANSLUCENT,
            )
            .apply {
                alpha = if (readyToShowUnderlay) 1f else 0f
                gravity = Gravity.BOTTOM or Gravity.START
                fitInsetsTypes = 0
                isFitInsetsIgnoringVisibility = false
            }
    }
}
+57 −0
Original line number Diff line number Diff line
/*
 * Copyright 2025 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.underlay.data.repository

import android.content.IntentFilter
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.stateIn

@SysUISingleton
class UnderlayRepository
@Inject
constructor(
    @Background private val backgroundScope: CoroutineScope,
    broadcastDispatcher: BroadcastDispatcher,
) {
    val isUnderlayAttached: StateFlow<Boolean> =
        broadcastDispatcher
            .broadcastFlow(
                filter =
                    IntentFilter().apply {
                        addAction(ACTION_CREATE_UNDERLAY)
                        addAction(ACTION_DESTROY_UNDERLAY)
                    }
            ) { intent, _ ->
                intent.action == ACTION_CREATE_UNDERLAY
            }
            .stateIn(
                scope = backgroundScope,
                started = SharingStarted.WhileSubscribed(),
                initialValue = false,
            )

    private companion object {
        const val ACTION_CREATE_UNDERLAY = "com.systemui.underlay.action.CREATE_UNDERLAY"
        const val ACTION_DESTROY_UNDERLAY = "com.systemui.underlay.action.DESTROY_UNDERLAY"
    }
}
+25 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 The Android Open Source Project
 * Copyright 2025 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.
@@ -14,32 +14,12 @@
 * limitations under the License.
 */

package com.android.systemui.underlay
package com.android.systemui.underlay.domain.interactor

import android.util.Log
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.underlay.shared.flag.UnderlayFlag
import com.android.systemui.underlay.data.repository.UnderlayRepository
import javax.inject.Inject
import kotlinx.coroutines.flow.StateFlow

/**
 * Core startable for the underlay.
 *
 * This is responsible for starting the underlay and its dependencies.
 */
@SysUISingleton
class UnderlayCoreStartable @Inject constructor() : CoreStartable {

    override fun start() {
        if (!UnderlayFlag.isEnabled) {
            Log.d(TAG, "Underlay flag is disabled, not starting.")
            return
        }

        Log.d(TAG, "start!")
    }

    private companion object {
        const val TAG = "UnderlayCoreStartable"
    }
class UnderlayInteractor @Inject constructor(repository: UnderlayRepository) {
    val isUnderlayAttached: StateFlow<Boolean> = repository.isUnderlayAttached
}
+87 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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.underlay.ui.startable

import android.util.Log
import android.view.WindowManager
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.underlay.UnderlayUtils
import com.android.systemui.underlay.domain.interactor.UnderlayInteractor
import com.android.systemui.underlay.shared.flag.UnderlayFlag
import com.android.systemui.underlay.ui.view.UnderlayWindowRootView
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch

/**
 * Core startable for the underlay.
 *
 * This is responsible for starting the underlay and its dependencies.
 */
@SysUISingleton
class UnderlayCoreStartable
@Inject
constructor(
    private val windowManager: WindowManager,
    private val underlayInteractor: UnderlayInteractor,
    private val underlayWindowRootView: UnderlayWindowRootView,
    @Application private val mainScope: CoroutineScope,
) : CoreStartable {

    override fun start() {
        if (!UnderlayFlag.isEnabled) {
            Log.d(TAG, "Underlay flag is disabled, not starting.")
            return
        }

        Log.d(TAG, "start!")
        mainScope.launch {
            underlayInteractor.isUnderlayAttached.collect { isUnderlayAttached ->
                if (isUnderlayAttached) {
                    createUnderlayView()
                } else {
                    destroyUnderlayView()
                }
            }
        }
    }

    private fun createUnderlayView() {
        if (!underlayWindowRootView.isAttachedToWindow) {
            windowManager.addView(
                underlayWindowRootView,
                UnderlayUtils.getUnderlayLayoutParams(
                    width = WindowManager.LayoutParams.MATCH_PARENT,
                    height = WindowManager.LayoutParams.WRAP_CONTENT,
                    readyToShowUnderlay = false,
                ),
            )
        }
    }

    private fun destroyUnderlayView() {
        if (underlayWindowRootView.isAttachedToWindow) {
            windowManager.removeView(underlayWindowRootView)
        }
    }

    private companion object {
        const val TAG = "UnderlayCoreStartable"
    }
}
Loading