1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.compose 18 19 import android.view.View 20 import androidx.lifecycle.Lifecycle 21 import androidx.lifecycle.findViewTreeLifecycleOwner 22 import androidx.lifecycle.setViewTreeLifecycleOwner 23 import androidx.savedstate.SavedStateRegistryController 24 import androidx.savedstate.SavedStateRegistryOwner 25 import com.android.compose.animation.ViewTreeSavedStateRegistryOwner 26 import com.android.systemui.lifecycle.ViewLifecycleOwner 27 28 /** 29 * An initializer to use Compose outside of an Activity, e.g. inside a window added directly using 30 * [android.view.WindowManager.addView] (like the shade or status bar) or inside a dialog. 31 * 32 * Example: 33 * ``` 34 * windowManager.addView(MyWindowRootView(context), /* layoutParams */) 35 * 36 * class MyWindowRootView(context: Context) : FrameLayout(context) { 37 * override fun onAttachedToWindow() { 38 * super.onAttachedToWindow() 39 * ComposeInitializer.onAttachedToWindow(this) 40 * } 41 * 42 * override fun onDetachedFromWindow() { 43 * super.onDetachedFromWindow() 44 * ComposeInitializer.onDetachedFromWindow(this) 45 * } 46 * } 47 * ``` 48 */ 49 object ComposeInitializer { 50 /** Function to be called on your window root view's [View.onAttachedToWindow] function. */ onAttachedToWindownull51 fun onAttachedToWindow(root: View) { 52 if (root.findViewTreeLifecycleOwner() != null) { 53 error("root $root already has a LifecycleOwner") 54 } 55 56 val parent = root.parent 57 if (parent is View && parent.id != android.R.id.content) { 58 error( 59 "ComposeInitializer.onAttachedToWindow(View) must be called on the content child." + 60 "Outside of activities and dialogs, this is usually the top-most View of a " + 61 "window." 62 ) 63 } 64 65 // The lifecycle owner, which is STARTED when [root] is visible and RESUMED when [root] is 66 // both visible and focused. 67 val lifecycleOwner = ViewLifecycleOwner(root) 68 69 // We create a trivial implementation of [SavedStateRegistryOwner] that does not do any save 70 // or restore because SystemUI process is always running and top-level windows using this 71 // initializer are created once, when the process is started. 72 val savedStateRegistryOwner = 73 object : SavedStateRegistryOwner { 74 private val savedStateRegistryController = 75 SavedStateRegistryController.create(this).apply { performRestore(null) } 76 77 override val savedStateRegistry = savedStateRegistryController.savedStateRegistry 78 79 override val lifecycle: Lifecycle 80 get() = lifecycleOwner.lifecycle 81 } 82 83 // We must call [ViewLifecycleOwner.onCreate] after creating the [SavedStateRegistryOwner] 84 // because `onCreate` might move the lifecycle state to STARTED which will make 85 // [SavedStateRegistryController.performRestore] throw. 86 lifecycleOwner.onCreate() 87 88 // Set the owners on the root. They will be reused by any ComposeView inside the root 89 // hierarchy. 90 root.setViewTreeLifecycleOwner(lifecycleOwner) 91 ViewTreeSavedStateRegistryOwner.set(root, savedStateRegistryOwner) 92 } 93 94 /** Function to be called on your window root view's [View.onDetachedFromWindow] function. */ onDetachedFromWindownull95 fun onDetachedFromWindow(root: View) { 96 (root.findViewTreeLifecycleOwner() as ViewLifecycleOwner).onDestroy() 97 root.setViewTreeLifecycleOwner(null) 98 ViewTreeSavedStateRegistryOwner.set(root, null) 99 } 100 } 101