Merge changes from topics "people-space-benchmark", "people-space-jank-benchmark", "sysui-compose-initializer" into tm-qpr-dev am: 220397f120
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/20803134 Change-Id: Ib9291cf05d7342f62b3e90de295a51022fc51cb5 Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
committed by
Automerger Merge Worker
commit
48710b2c15
@@ -271,6 +271,7 @@ android_library {
|
|||||||
"LowLightDreamLib",
|
"LowLightDreamLib",
|
||||||
"motion_tool_lib",
|
"motion_tool_lib",
|
||||||
"androidx.core_core-animation-testing-nodeps",
|
"androidx.core_core-animation-testing-nodeps",
|
||||||
|
"androidx.compose.ui_ui",
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,13 +17,21 @@
|
|||||||
|
|
||||||
package com.android.systemui.compose
|
package com.android.systemui.compose
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.view.View
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.lifecycle.LifecycleOwner
|
||||||
import com.android.systemui.people.ui.viewmodel.PeopleViewModel
|
import com.android.systemui.people.ui.viewmodel.PeopleViewModel
|
||||||
|
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
|
||||||
|
|
||||||
/** The Compose facade, when Compose is *not* available. */
|
/** The Compose facade, when Compose is *not* available. */
|
||||||
object ComposeFacade : BaseComposeFacade {
|
object ComposeFacade : BaseComposeFacade {
|
||||||
override fun isComposeAvailable(): Boolean = false
|
override fun isComposeAvailable(): Boolean = false
|
||||||
|
|
||||||
|
override fun composeInitializer(): ComposeInitializer {
|
||||||
|
throwComposeUnavailableError()
|
||||||
|
}
|
||||||
|
|
||||||
override fun setPeopleSpaceActivityContent(
|
override fun setPeopleSpaceActivityContent(
|
||||||
activity: ComponentActivity,
|
activity: ComponentActivity,
|
||||||
viewModel: PeopleViewModel,
|
viewModel: PeopleViewModel,
|
||||||
@@ -32,7 +40,15 @@ object ComposeFacade : BaseComposeFacade {
|
|||||||
throwComposeUnavailableError()
|
throwComposeUnavailableError()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun throwComposeUnavailableError() {
|
override fun createFooterActionsView(
|
||||||
|
context: Context,
|
||||||
|
viewModel: FooterActionsViewModel,
|
||||||
|
qsVisibilityLifecycleOwner: LifecycleOwner
|
||||||
|
): View {
|
||||||
|
throwComposeUnavailableError()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun throwComposeUnavailableError(): Nothing {
|
||||||
error(
|
error(
|
||||||
"Compose is not available. Make sure to check isComposeAvailable() before calling any" +
|
"Compose is not available. Make sure to check isComposeAvailable() before calling any" +
|
||||||
" other function on ComposeFacade."
|
" other function on ComposeFacade."
|
||||||
|
|||||||
@@ -16,16 +16,24 @@
|
|||||||
|
|
||||||
package com.android.systemui.compose
|
package com.android.systemui.compose
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.view.View
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
|
import androidx.compose.ui.platform.ComposeView
|
||||||
|
import androidx.lifecycle.LifecycleOwner
|
||||||
import com.android.compose.theme.PlatformTheme
|
import com.android.compose.theme.PlatformTheme
|
||||||
import com.android.systemui.people.ui.compose.PeopleScreen
|
import com.android.systemui.people.ui.compose.PeopleScreen
|
||||||
import com.android.systemui.people.ui.viewmodel.PeopleViewModel
|
import com.android.systemui.people.ui.viewmodel.PeopleViewModel
|
||||||
|
import com.android.systemui.qs.footer.ui.compose.FooterActions
|
||||||
|
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
|
||||||
|
|
||||||
/** The Compose facade, when Compose is available. */
|
/** The Compose facade, when Compose is available. */
|
||||||
object ComposeFacade : BaseComposeFacade {
|
object ComposeFacade : BaseComposeFacade {
|
||||||
override fun isComposeAvailable(): Boolean = true
|
override fun isComposeAvailable(): Boolean = true
|
||||||
|
|
||||||
|
override fun composeInitializer(): ComposeInitializer = ComposeInitializerImpl
|
||||||
|
|
||||||
override fun setPeopleSpaceActivityContent(
|
override fun setPeopleSpaceActivityContent(
|
||||||
activity: ComponentActivity,
|
activity: ComponentActivity,
|
||||||
viewModel: PeopleViewModel,
|
viewModel: PeopleViewModel,
|
||||||
@@ -33,4 +41,14 @@ object ComposeFacade : BaseComposeFacade {
|
|||||||
) {
|
) {
|
||||||
activity.setContent { PlatformTheme { PeopleScreen(viewModel, onResult) } }
|
activity.setContent { PlatformTheme { PeopleScreen(viewModel, onResult) } }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun createFooterActionsView(
|
||||||
|
context: Context,
|
||||||
|
viewModel: FooterActionsViewModel,
|
||||||
|
qsVisibilityLifecycleOwner: LifecycleOwner,
|
||||||
|
): View {
|
||||||
|
return ComposeView(context).apply {
|
||||||
|
setContent { PlatformTheme { FooterActions(viewModel, qsVisibilityLifecycleOwner) } }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2023 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.compose
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
|
import androidx.lifecycle.ViewTreeLifecycleOwner
|
||||||
|
import androidx.savedstate.SavedStateRegistry
|
||||||
|
import androidx.savedstate.SavedStateRegistryController
|
||||||
|
import androidx.savedstate.SavedStateRegistryOwner
|
||||||
|
import com.android.compose.animation.ViewTreeSavedStateRegistryOwner
|
||||||
|
import com.android.systemui.lifecycle.ViewLifecycleOwner
|
||||||
|
|
||||||
|
internal object ComposeInitializerImpl : ComposeInitializer {
|
||||||
|
override fun onAttachedToWindow(root: View) {
|
||||||
|
if (ViewTreeLifecycleOwner.get(root) != null) {
|
||||||
|
error("root $root already has a LifecycleOwner")
|
||||||
|
}
|
||||||
|
|
||||||
|
val parent = root.parent
|
||||||
|
if (parent is View && parent.id != android.R.id.content) {
|
||||||
|
error(
|
||||||
|
"ComposeInitializer.onAttachedToWindow(View) must be called on the content child." +
|
||||||
|
"Outside of activities and dialogs, this is usually the top-most View of a " +
|
||||||
|
"window."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The lifecycle owner, which is STARTED when [root] is visible and RESUMED when [root] is
|
||||||
|
// both visible and focused.
|
||||||
|
val lifecycleOwner = ViewLifecycleOwner(root)
|
||||||
|
|
||||||
|
// We create a trivial implementation of [SavedStateRegistryOwner] that does not do any save
|
||||||
|
// or restore because SystemUI process is always running and top-level windows using this
|
||||||
|
// initializer are created once, when the process is started.
|
||||||
|
val savedStateRegistryOwner =
|
||||||
|
object : SavedStateRegistryOwner {
|
||||||
|
private val savedStateRegistry =
|
||||||
|
SavedStateRegistryController.create(this).apply { performRestore(null) }
|
||||||
|
|
||||||
|
override fun getLifecycle(): Lifecycle = lifecycleOwner.lifecycle
|
||||||
|
|
||||||
|
override fun getSavedStateRegistry(): SavedStateRegistry {
|
||||||
|
return savedStateRegistry.savedStateRegistry
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We must call [ViewLifecycleOwner.onCreate] after creating the [SavedStateRegistryOwner]
|
||||||
|
// because `onCreate` might move the lifecycle state to STARTED which will make
|
||||||
|
// [SavedStateRegistryController.performRestore] throw.
|
||||||
|
lifecycleOwner.onCreate()
|
||||||
|
|
||||||
|
// Set the owners on the root. They will be reused by any ComposeView inside the root
|
||||||
|
// hierarchy.
|
||||||
|
ViewTreeLifecycleOwner.set(root, lifecycleOwner)
|
||||||
|
ViewTreeSavedStateRegistryOwner.set(root, savedStateRegistryOwner)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDetachedFromWindow(root: View) {
|
||||||
|
(ViewTreeLifecycleOwner.get(root) as ViewLifecycleOwner).onDestroy()
|
||||||
|
ViewTreeLifecycleOwner.set(root, null)
|
||||||
|
ViewTreeSavedStateRegistryOwner.set(root, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2023 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.compose.modifiers
|
||||||
|
|
||||||
|
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.testTag
|
||||||
|
import androidx.compose.ui.semantics.semantics
|
||||||
|
import androidx.compose.ui.semantics.testTagsAsResourceId
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a test tag on this node so that it is associated with [resId]. This node will then be
|
||||||
|
* accessible by integration tests using `sysuiResSelector(resId)`.
|
||||||
|
*/
|
||||||
|
@OptIn(ExperimentalComposeUiApi::class)
|
||||||
|
fun Modifier.sysuiResTag(resId: String): Modifier {
|
||||||
|
return this.semantics { testTagsAsResourceId = true }.testTag("com.android.systemui:id/$resId")
|
||||||
|
}
|
||||||
@@ -51,6 +51,7 @@ import androidx.lifecycle.Lifecycle
|
|||||||
import androidx.lifecycle.repeatOnLifecycle
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
import com.android.compose.theme.LocalAndroidColorScheme
|
import com.android.compose.theme.LocalAndroidColorScheme
|
||||||
import com.android.systemui.R
|
import com.android.systemui.R
|
||||||
|
import com.android.systemui.compose.modifiers.sysuiResTag
|
||||||
import com.android.systemui.people.ui.viewmodel.PeopleTileViewModel
|
import com.android.systemui.people.ui.viewmodel.PeopleTileViewModel
|
||||||
import com.android.systemui.people.ui.viewmodel.PeopleViewModel
|
import com.android.systemui.people.ui.viewmodel.PeopleViewModel
|
||||||
|
|
||||||
@@ -110,7 +111,9 @@ private fun PeopleScreenWithConversations(
|
|||||||
recentTiles: List<PeopleTileViewModel>,
|
recentTiles: List<PeopleTileViewModel>,
|
||||||
onTileClicked: (PeopleTileViewModel) -> Unit,
|
onTileClicked: (PeopleTileViewModel) -> Unit,
|
||||||
) {
|
) {
|
||||||
Column {
|
Column(
|
||||||
|
Modifier.sysuiResTag("top_level_with_conversations"),
|
||||||
|
) {
|
||||||
Column(
|
Column(
|
||||||
Modifier.fillMaxWidth().padding(PeopleSpacePadding),
|
Modifier.fillMaxWidth().padding(PeopleSpacePadding),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
@@ -132,7 +135,7 @@ private fun PeopleScreenWithConversations(
|
|||||||
}
|
}
|
||||||
|
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
Modifier.fillMaxWidth(),
|
Modifier.fillMaxWidth().sysuiResTag("scroll_view"),
|
||||||
contentPadding =
|
contentPadding =
|
||||||
PaddingValues(
|
PaddingValues(
|
||||||
top = 16.dp,
|
top = 16.dp,
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ import com.android.systemui.R
|
|||||||
import com.android.systemui.animation.Expandable
|
import com.android.systemui.animation.Expandable
|
||||||
import com.android.systemui.common.shared.model.Icon
|
import com.android.systemui.common.shared.model.Icon
|
||||||
import com.android.systemui.common.ui.compose.Icon
|
import com.android.systemui.common.ui.compose.Icon
|
||||||
|
import com.android.systemui.compose.modifiers.sysuiResTag
|
||||||
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsButtonViewModel
|
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsButtonViewModel
|
||||||
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsForegroundServicesButtonViewModel
|
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsForegroundServicesButtonViewModel
|
||||||
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsSecurityButtonViewModel
|
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsSecurityButtonViewModel
|
||||||
@@ -180,9 +181,9 @@ fun FooterActions(
|
|||||||
|
|
||||||
security?.let { SecurityButton(it, Modifier.weight(1f)) }
|
security?.let { SecurityButton(it, Modifier.weight(1f)) }
|
||||||
foregroundServices?.let { ForegroundServicesButton(it) }
|
foregroundServices?.let { ForegroundServicesButton(it) }
|
||||||
userSwitcher?.let { IconButton(it) }
|
userSwitcher?.let { IconButton(it, Modifier.sysuiResTag("multi_user_switch")) }
|
||||||
IconButton(viewModel.settings)
|
IconButton(viewModel.settings, Modifier.sysuiResTag("settings_button_container"))
|
||||||
viewModel.power?.let { IconButton(it) }
|
viewModel.power?.let { IconButton(it, Modifier.sysuiResTag("pm_lite")) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,8 +17,12 @@
|
|||||||
|
|
||||||
package com.android.systemui.compose
|
package com.android.systemui.compose
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.view.View
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.lifecycle.LifecycleOwner
|
||||||
import com.android.systemui.people.ui.viewmodel.PeopleViewModel
|
import com.android.systemui.people.ui.viewmodel.PeopleViewModel
|
||||||
|
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A facade to interact with Compose, when it is available.
|
* A facade to interact with Compose, when it is available.
|
||||||
@@ -35,10 +39,22 @@ interface BaseComposeFacade {
|
|||||||
*/
|
*/
|
||||||
fun isComposeAvailable(): Boolean
|
fun isComposeAvailable(): Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the [ComposeInitializer] to make Compose usable in windows outside normal activities.
|
||||||
|
*/
|
||||||
|
fun composeInitializer(): ComposeInitializer
|
||||||
|
|
||||||
/** Bind the content of [activity] to [viewModel]. */
|
/** Bind the content of [activity] to [viewModel]. */
|
||||||
fun setPeopleSpaceActivityContent(
|
fun setPeopleSpaceActivityContent(
|
||||||
activity: ComponentActivity,
|
activity: ComponentActivity,
|
||||||
viewModel: PeopleViewModel,
|
viewModel: PeopleViewModel,
|
||||||
onResult: (PeopleViewModel.Result) -> Unit,
|
onResult: (PeopleViewModel.Result) -> Unit,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/** Create a [View] to represent [viewModel] on screen. */
|
||||||
|
fun createFooterActionsView(
|
||||||
|
context: Context,
|
||||||
|
viewModel: FooterActionsViewModel,
|
||||||
|
qsVisibilityLifecycleOwner: LifecycleOwner,
|
||||||
|
): View
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* 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.compose
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An initializer to use Compose outside of an Activity, e.g. inside a window added directly using
|
||||||
|
* [android.view.WindowManager.addView] (like the shade or status bar) or inside a dialog.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* ```
|
||||||
|
* windowManager.addView(MyWindowRootView(context), /* layoutParams */)
|
||||||
|
*
|
||||||
|
* class MyWindowRootView(context: Context) : FrameLayout(context) {
|
||||||
|
* override fun onAttachedToWindow() {
|
||||||
|
* super.onAttachedToWindow()
|
||||||
|
* ComposeInitializer.onAttachedToWindow(this)
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* override fun onDetachedFromWindow() {
|
||||||
|
* super.onDetachedFromWindow()
|
||||||
|
* ComposeInitializer.onDetachedFromWindow(this)
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
interface ComposeInitializer {
|
||||||
|
/** Function to be called on your window root view's [View.onAttachedToWindow] function. */
|
||||||
|
fun onAttachedToWindow(root: View)
|
||||||
|
|
||||||
|
/** Function to be called on your window root view's [View.onDetachedFromWindow] function. */
|
||||||
|
fun onDetachedFromWindow(root: View)
|
||||||
|
}
|
||||||
@@ -145,7 +145,7 @@ private fun createLifecycleOwnerAndRun(
|
|||||||
* └───────────────┴───────────────────┴──────────────┴─────────────────┘
|
* └───────────────┴───────────────────┴──────────────┴─────────────────┘
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
private class ViewLifecycleOwner(
|
class ViewLifecycleOwner(
|
||||||
private val view: View,
|
private val view: View,
|
||||||
) : LifecycleOwner {
|
) : LifecycleOwner {
|
||||||
|
|
||||||
|
|||||||
@@ -68,8 +68,10 @@ public class PeopleSpaceActivity extends ComponentActivity {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (ComposeFacade.INSTANCE.isComposeAvailable()) {
|
if (ComposeFacade.INSTANCE.isComposeAvailable()) {
|
||||||
|
Log.d(TAG, "Using the Compose implementation of the PeopleSpaceActivity");
|
||||||
ComposeFacade.INSTANCE.setPeopleSpaceActivityContent(this, viewModel, onResult);
|
ComposeFacade.INSTANCE.setPeopleSpaceActivityContent(this, viewModel, onResult);
|
||||||
} else {
|
} else {
|
||||||
|
Log.d(TAG, "Using the View implementation of the PeopleSpaceActivity");
|
||||||
ViewGroup view = PeopleViewBinder.create(this);
|
ViewGroup view = PeopleViewBinder.create(this);
|
||||||
PeopleViewBinder.bind(view, viewModel, /* lifecycleOwner= */ this, onResult);
|
PeopleViewBinder.bind(view, viewModel, /* lifecycleOwner= */ this, onResult);
|
||||||
setContentView(view);
|
setContentView(view);
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ import com.android.systemui.Dumpable;
|
|||||||
import com.android.systemui.R;
|
import com.android.systemui.R;
|
||||||
import com.android.systemui.animation.Interpolators;
|
import com.android.systemui.animation.Interpolators;
|
||||||
import com.android.systemui.animation.ShadeInterpolation;
|
import com.android.systemui.animation.ShadeInterpolation;
|
||||||
|
import com.android.systemui.compose.ComposeFacade;
|
||||||
import com.android.systemui.dump.DumpManager;
|
import com.android.systemui.dump.DumpManager;
|
||||||
import com.android.systemui.flags.FeatureFlags;
|
import com.android.systemui.flags.FeatureFlags;
|
||||||
import com.android.systemui.media.controls.ui.MediaHost;
|
import com.android.systemui.media.controls.ui.MediaHost;
|
||||||
@@ -228,9 +229,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
|
|||||||
|
|
||||||
mQSFooterActionsViewModel = mFooterActionsViewModelFactory.create(/* lifecycleOwner */
|
mQSFooterActionsViewModel = mFooterActionsViewModelFactory.create(/* lifecycleOwner */
|
||||||
this);
|
this);
|
||||||
LinearLayout footerActionsView = view.findViewById(R.id.qs_footer_actions);
|
bindFooterActionsView(view);
|
||||||
FooterActionsViewBinder.bind(footerActionsView, mQSFooterActionsViewModel,
|
|
||||||
mListeningAndVisibilityLifecycleOwner);
|
|
||||||
mFooterActionsController.init();
|
mFooterActionsController.init();
|
||||||
|
|
||||||
mQSPanelScrollView = view.findViewById(R.id.expanded_qs_scroll_view);
|
mQSPanelScrollView = view.findViewById(R.id.expanded_qs_scroll_view);
|
||||||
@@ -291,6 +290,33 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void bindFooterActionsView(View root) {
|
||||||
|
LinearLayout footerActionsView = root.findViewById(R.id.qs_footer_actions);
|
||||||
|
|
||||||
|
if (!ComposeFacade.INSTANCE.isComposeAvailable()) {
|
||||||
|
Log.d(TAG, "Binding the View implementation of the QS footer actions");
|
||||||
|
FooterActionsViewBinder.bind(footerActionsView, mQSFooterActionsViewModel,
|
||||||
|
mListeningAndVisibilityLifecycleOwner);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compose is available, so let's use the Compose implementation of the footer actions.
|
||||||
|
Log.d(TAG, "Binding the Compose implementation of the QS footer actions");
|
||||||
|
View composeView = ComposeFacade.INSTANCE.createFooterActionsView(root.getContext(),
|
||||||
|
mQSFooterActionsViewModel, mListeningAndVisibilityLifecycleOwner);
|
||||||
|
|
||||||
|
// The id R.id.qs_footer_actions is used by QSContainerImpl to set the horizontal margin
|
||||||
|
// to all views except for qs_footer_actions, so we set it to the Compose view.
|
||||||
|
composeView.setId(R.id.qs_footer_actions);
|
||||||
|
|
||||||
|
// Replace the View by the Compose provided one.
|
||||||
|
ViewGroup parent = (ViewGroup) footerActionsView.getParent();
|
||||||
|
ViewGroup.LayoutParams layoutParams = footerActionsView.getLayoutParams();
|
||||||
|
int index = parent.indexOfChild(footerActionsView);
|
||||||
|
parent.removeViewAt(index);
|
||||||
|
parent.addView(composeView, index, layoutParams);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setScrollListener(ScrollListener listener) {
|
public void setScrollListener(ScrollListener listener) {
|
||||||
mScrollListener = listener;
|
mScrollListener = listener;
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ import android.widget.FrameLayout;
|
|||||||
import com.android.internal.view.FloatingActionMode;
|
import com.android.internal.view.FloatingActionMode;
|
||||||
import com.android.internal.widget.floatingtoolbar.FloatingToolbar;
|
import com.android.internal.widget.floatingtoolbar.FloatingToolbar;
|
||||||
import com.android.systemui.R;
|
import com.android.systemui.R;
|
||||||
|
import com.android.systemui.compose.ComposeFacade;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Combined keyguard and notification panel view. Also holding backdrop and scrims.
|
* Combined keyguard and notification panel view. Also holding backdrop and scrims.
|
||||||
@@ -149,6 +150,18 @@ public class NotificationShadeWindowView extends FrameLayout {
|
|||||||
protected void onAttachedToWindow() {
|
protected void onAttachedToWindow() {
|
||||||
super.onAttachedToWindow();
|
super.onAttachedToWindow();
|
||||||
setWillNotDraw(!DEBUG);
|
setWillNotDraw(!DEBUG);
|
||||||
|
|
||||||
|
if (ComposeFacade.INSTANCE.isComposeAvailable()) {
|
||||||
|
ComposeFacade.INSTANCE.composeInitializer().onAttachedToWindow(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDetachedFromWindow() {
|
||||||
|
super.onDetachedFromWindow();
|
||||||
|
if (ComposeFacade.INSTANCE.isComposeAvailable()) {
|
||||||
|
ComposeFacade.INSTANCE.composeInitializer().onDetachedFromWindow(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
* 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.compose
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.testing.AndroidTestingRunner
|
||||||
|
import android.testing.ViewUtils
|
||||||
|
import android.widget.FrameLayout
|
||||||
|
import androidx.compose.ui.platform.ComposeView
|
||||||
|
import androidx.test.filters.SmallTest
|
||||||
|
import com.android.systemui.SysuiTestCase
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
|
@SmallTest
|
||||||
|
@RunWith(AndroidTestingRunner::class)
|
||||||
|
class ComposeInitializerTest : SysuiTestCase() {
|
||||||
|
@Test
|
||||||
|
fun testCanAddComposeViewInInitializedWindow() {
|
||||||
|
if (!ComposeFacade.isComposeAvailable()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val root = TestWindowRoot(context)
|
||||||
|
try {
|
||||||
|
runOnMainThreadAndWaitForIdleSync { ViewUtils.attachView(root) }
|
||||||
|
assertThat(root.isAttachedToWindow).isTrue()
|
||||||
|
|
||||||
|
runOnMainThreadAndWaitForIdleSync { root.addView(ComposeView(context)) }
|
||||||
|
} finally {
|
||||||
|
runOnMainThreadAndWaitForIdleSync { ViewUtils.detachView(root) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun runOnMainThreadAndWaitForIdleSync(f: () -> Unit) {
|
||||||
|
mContext.mainExecutor.execute(f)
|
||||||
|
waitForIdleSync()
|
||||||
|
}
|
||||||
|
|
||||||
|
class TestWindowRoot(context: Context) : FrameLayout(context) {
|
||||||
|
override fun onAttachedToWindow() {
|
||||||
|
super.onAttachedToWindow()
|
||||||
|
ComposeFacade.composeInitializer().onAttachedToWindow(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDetachedFromWindow() {
|
||||||
|
super.onDetachedFromWindow()
|
||||||
|
ComposeFacade.composeInitializer().onDetachedFromWindow(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user