Create a Custom Panel Type

Last edit: 2023.10.06

Knowledge of the system UI will help you to understand this guide. If you are not yet familiar with it, feel free to take a look at the system UI overview.

The system UI in the TomTom Digital Cockpit visualizes data represented by Panels. They are the main building blocks of the system UI. Each Frontend that has a UI adds one or more new Panels to visualize itself. The System UI Overview page introduces the anatomy of the system UI and contains examples of built-in Panel types. Their implementations can be found in the package com.tomtom.ivi.platform.frontend.api.common.frontend.panels.

Panels are displayed in a panel container. A panel container defines both the location on the screen and the "height" a panel is displayed at (z-index). Moving panel containers to a different location on the screen can be done in the system UI layout, a custom panel type is not needed in this case. However, when you want to change the z-index of some information, or want to split information currently displayed at one location, a custom panel type can be the solution. If none of the built-in Panel types are suitable for your use case, you can implement a custom Panel type for your system UI. This tutorial shows how to do this.

Create a custom panel type base class

Create a CustomSystemUiPanel.kt file containing the base class for a custom panel type and a variable that declares a full set of panel types.

1abstract class CustomSystemUiPanel(frontendContext: FrontendContext) : Panel(frontendContext)
2
3val CUSTOM_SYSTEM_UI_PANEL_TYPES: PanelTypeSet = PLATFORM_PANEL_TYPES + CustomSystemUiPanel::class

Create a custom frontend

Create a custom frontend in four steps.

Create a custom frontend implementation

Create an ExampleFrontend.kt file containing a custom Frontend. Note how a custom panel is added when this frontend is created.

1internal class ExampleFrontend(frontendContext: FrontendContext) : Frontend(frontendContext) {
2
3 override fun onCreate() {
4 super.onCreate()
5
6 val isTaskPanelOpened = panels.map { it.any { panel -> panel is TaskPanel } }
7 addPanel(ExampleCustomPanel(frontendContext, isTaskPanelOpened))
8 }
9}

Create a custom frontend builder

Create an ExampleFrontendBuilder.kt file containing a custom frontend builder implementation.

1class ExampleFrontendBuilder : FrontendBuilder() {
2
3 override fun build(frontendContext: FrontendContext): Frontend =
4 ExampleFrontend(frontendContext)
5}

Create a custom menu item

Create an ExampleMenuItem.kt file containing a custom menu item.

1val exampleMenuItem =
2 MenuItem(
3 ExampleFrontend::class.qualifiedName!!,
4 R.drawable.ttivi_example_menuitem,
5 R.string.ttivi_systemui_custompaneltype_frontend_menuitem_name
6 )

Create an implementation of a custom panel type

Create an ExampleCustomPanel.kt file containing an implementation of the abstract CustomSystemUiPanel, its fragment and a view model.

1internal class ExampleCustomPanel(
2 frontendContext: FrontendContext,
3 val isTaskPanelOpened: LiveData<Boolean>
4) : CustomSystemUiPanel(frontendContext) {
5 override fun createInitialFragmentInitializer(): IviFragment.Initializer<*, *> =
6 IviFragment.Initializer(ExampleCustomFragment(), this)
7}
8
9internal class ExampleCustomFragment :
10 IviFragment<ExampleCustomPanel, ExampleCustomViewModel>(ExampleCustomViewModel::class) {
11
12 override val viewFactory: ViewFactory<*>? =
13 ViewFactory(TtiviCustompaneltypeCustompanelBinding::inflate)
14}
15
16internal class ExampleCustomViewModel(panel: ExampleCustomPanel) :
17 FrontendViewModel<ExampleCustomPanel>(panel) {
18
19 val isTaskPanelOpened = panel.isTaskPanelOpened
20}

Create a layout of a custom fragment

Create a ttivi_custompaneltype_custompanel.xml layout file for a custom fragment.

1<layout xmlns:android="http://schemas.android.com/apk/res/android"
2 xmlns:auto="http://schemas.android.com/apk/res-auto"
3 xmlns:tools="http://schemas.android.com/tools">
4
5 <data>
6 <variable
7 name="viewModel"
8 type="com.example.ivi.example.systemui.custompaneltype.frontend.ExampleCustomViewModel" />
9 </data>
10
11 <LinearLayout
12 android:layout_width="wrap_content"
13 android:layout_height="match_parent"
14 android:orientation="vertical">
15
16 <com.tomtom.tools.android.api.uicontrols.textview.TtTextView
17 android:layout_width="wrap_content"
18 android:layout_height="wrap_content"
19 android:layout_margin="?attr/tt_spacing_16"
20 android:gravity="start"
21 android:text="@string/ttivi_systemui_custompaneltype_frontend_panel_header"
22 android:textAlignment="textStart"
23 android:textAppearance="?attr/tt_headline_text_style_l"
24 android:textColor="?attr/tt_surface_content_color_emphasis_high" />
25
26 <com.tomtom.tools.android.api.uicontrols.textview.TtTextView
27 android:layout_width="wrap_content"
28 android:layout_height="wrap_content"
29 android:layout_margin="?attr/tt_spacing_16"
30 android:gravity="start"
31 android:text="@string/ttivi_systemui_custompaneltype_frontend_opened"
32 android:textAlignment="textStart"
33 android:textAppearance="?attr/tt_headline_text_style_m"
34 android:textColor="?attr/tt_surface_content_color_emphasis_high"
35 auto:visibleIf="@{viewModel.isTaskPanelOpened}" />
36
37 <com.tomtom.tools.android.api.uicontrols.textview.TtTextView
38 android:layout_width="wrap_content"
39 android:layout_height="wrap_content"
40 android:layout_margin="?attr/tt_spacing_16"
41 android:gravity="start"
42 android:text="@string/ttivi_systemui_custompaneltype_frontend_closed"
43 android:textAlignment="textStart"
44 android:textAppearance="?attr/tt_headline_text_style_m"
45 android:textColor="?attr/tt_surface_content_color_emphasis_high"
46 auto:visibleIfNot="@{viewModel.isTaskPanelOpened}" />
47
48 </LinearLayout>
49</layout>

Add the custom frontend to an IVI instance

Add the following to the build.gradle.kts.

1val module = ExampleModuleReference("examples_customization_custompaneltype")
2
3val customPanelTypes = PanelTypesConfig(
4 "CUSTOM_SYSTEM_UI_PANEL_TYPES",
5 module,
6 "common"
7)
8
9val exampleFrontend =
10 FrontendConfig(
11 frontendBuilderName = "ExampleFrontendBuilder",
12 implementationModule = module,
13 subPackageName = "frontend",
14 availablePanelTypes = customPanelTypes
15 )
16
17val exampleMenuItem = exampleFrontend.toMenuItem("exampleMenuItem")
18
19ivi {
20 optInToExperimentalApis = true
21 application {
22 enabled = true
23 iviInstances {
24 create(IviInstanceIdentifier.default) {
25 applyGroups {
26 includeDefaultPlatformGroups()
27 includeDefaultAppsuiteGroups()
28 }
29 frontends {
30 add(exampleFrontend)
31 }
32 menuItems {
33 addFirst(exampleMenuItem to exampleFrontend)
34 }
35 }
36 }
37 }
38}

Create a custom system UI host

Create a custom panel registry

Create a CustomPanelRegistry.kt file containing a custom panel registry implementation.

1internal class CustomPanelRegistry(
2 val customPanel: LiveData<CustomSystemUiPanel?>,
3 val iviPanelRegistry: IviPanelRegistry
4) : PanelRegistry {
5
6 companion object {
7 fun create(
8 frontendRegistry: FrontendRegistry,
9 lifecycleOwner: LifecycleOwner,
10 iviServiceProvider: IviInstanceBoundIviServiceProvider
11 ) = CustomPanelRegistry(
12 customPanel = frontendRegistry.frontends.flatMap { it.panels }.mapToSingle(),
13 iviPanelRegistry = IviPanelRegistry.build(
14 frontendRegistry.extractPanels(),
15 lifecycleOwner,
16 iviServiceProvider
17 )
18 )
19 }
20}

Create a custom system UI view model

Create a CustomSystemUiViewModel.kt file containing a custom system UI model implementation.

1internal class CustomSystemUiViewModel(
2 coreViewModel: CoreSystemUiViewModel
3) : LifecycleViewModel() {
4
5 val frontendCoordinator = FrontendCoordinator(
6 lifecycleOwner = this,
7 coreViewModel.iviServiceProvider,
8 coreViewModel.frontendMetadata,
9 coreViewModel.defaultFrontendContextFactory,
10 { frontendRegistry ->
11 CustomPanelRegistry.create(
12 frontendRegistry,
13 lifecycleOwner = this,
14 coreViewModel.iviServiceProvider
15 )
16 },
17 { frontendRegistry, panelRegistry ->
18 DefaultFrontendCoordinationRules
19 .create(frontendRegistry, panelRegistry.iviPanelRegistry)
20 }
21 )
22
23 val panelRegistry = frontendCoordinator.panelRegistry
24
25 val menuPanel = panelRegistry.iviPanelRegistry.mainMenuPanel
26
27 val customPanel = panelRegistry.customPanel
28}

Create a custom system UI host

Create a CustomSystemUiHost.kt file containing a custom system UI host implementation. Note that CustomSystemUiPanel is listed in the supported panel types.

1internal class CustomSystemUiHost(systemUiHostContext: SystemUiHostContext) :
2 SystemUiHost(systemUiHostContext) {
3
4 private lateinit var viewModel: CustomSystemUiViewModel
5
6 override val viewFactory: ViewFactory =
7 BindingViewFactory(TtiviCustompaneltypeCustomsystemuiBinding::inflate, ::bindSystemUiView)
8
9 override val supportedPanelTypes: PanelTypeSet = panelTypeSetOf(
10 CustomSystemUiPanel::class,
11 MainMenuPanel::class,
12 TaskPanel::class,
13 TaskProcessPanel::class,
14 )
15
16 override val unsupportedPanelTypes: PanelTypeSet = panelTypeSetOf(
17 ControlCenterPanel::class,
18 DebugPanel::class,
19 ExpandedProcessPanel::class,
20 HomePanel::class,
21 MainProcessPanel::class,
22 ModalPanel::class,
23 NavigationPanel::class,
24 NotificationPanel::class,
25 OverlayPanel::class,
26 SettingsPanel::class,
27 )
28
29 override fun onCreate() {
30 viewModel = ViewModelProvider(
31 viewModelStoreOwner,
32 FixedConstructorFactory(coreViewModel)
33 )[CustomSystemUiViewModel::class.java]
34 }
35
36 override fun onSystemUiPresented() {
37 viewModel.frontendCoordinator.onSystemUiPresented()
38 }
39
40 private fun bindSystemUiView(binding: TtiviCustompaneltypeCustomsystemuiBinding) {
41 binding.viewModel = viewModel
42 binding.panelRegistry = viewModel.panelRegistry
43
44 setIviOnBackPressedCallbacks(listOf(binding.exampleSystemuiTaskPanelStackContainer))
45 }
46}

Create a custom system UI layout

Create a ttivi_custompaneltype_customsystemui.xml file containing a custom system UI layout.

1<?xml version="1.0" encoding="utf-8"?>
2<layout xmlns:android="http://schemas.android.com/apk/res/android"
3 xmlns:auto="http://schemas.android.com/apk/res-auto"
4 xmlns:tools="http://schemas.android.com/tools">
5
6 <data>
7 <variable
8 name="viewModel"
9 type="com.example.ivi.example.systemui.custompaneltype.systemui.CustomSystemUiViewModel" />
10
11 <variable
12 name="panelRegistry"
13 type="com.example.ivi.example.systemui.custompaneltype.systemui.CustomPanelRegistry" />
14
15 <variable
16 name="panelContainerContext"
17 type="com.tomtom.ivi.platform.systemui.api.common.systemuihost.panelcontainer.PanelContainerContext" />
18 </data>
19
20 <LinearLayout
21 android:layout_width="match_parent"
22 android:layout_height="match_parent"
23 android:orientation="horizontal">
24
25 <com.tomtom.ivi.platform.systemui.api.common.systemuihost.panelcontainer.SinglePanelContainer
26 android:id="@+id/example_systemui_custompanel_container"
27 android:layout_width="wrap_content"
28 android:layout_height="match_parent"
29 auto:ttiviPanelContainerContext="@{panelContainerContext}"
30 auto:ttiviPanelContainerData="@{viewModel.customPanel}" />
31
32 <com.tomtom.ivi.platform.systemui.api.common.systemuihost.panelcontainer.SinglePanelContainer
33 android:id="@+id/example_systemui_menu_container"
34 android:layout_width="?attr/ttivi_mainmenu_panel_size"
35 android:layout_height="match_parent"
36 auto:ttiviPanelContainerContext="@{panelContainerContext}"
37 auto:ttiviPanelContainerData="@{viewModel.menuPanel}" />
38
39 <com.tomtom.ivi.platform.systemui.api.common.systemuihost.panelcontainer.TaskPanelStackContainer
40 android:id="@+id/example_systemui_task_panel_stack_container"
41 android:layout_width="0dp"
42 android:layout_height="match_parent"
43 android:layout_weight="1"
44 android:minWidth="800dp"
45 auto:ttiviPanelContainerContext="@{panelContainerContext}"
46 auto:ttiviPanelContainerData="@{panelRegistry.iviPanelRegistry.taskPanelStackData}" />
47 </LinearLayout>
48</layout>

Create a custom activity

Create a CustomPanelTypeActivity.kt file containing a sub class of the DefaultActivity.

1class CustomPanelTypeActivity : DefaultActivity() {
2 override fun createSystemUiHost(iviInstanceId: IviInstanceId): SystemUiHost =
3 CustomSystemUiHost(getDefaultSystemUiHostContext(iviInstanceId))
4}

See the custom panel type

After the application is built and deployed to a target device, the custom panel type is displayed on the screen.

Custom panel type