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

Commit d8ef1818 authored by Coco Duan's avatar Coco Duan
Browse files

Store communal widget info in database and support editing widgets

- Use the Room database to store default and user selected
  widgets and their orders, so they can be persisted to provide
  a customizable glaceable hub.
- Updated the CommunalWidgetRepository to return a flow of
  active widgets stored in db. The communal hub will collect
  a transformed flow of this to render the cards.
- Added a + and x button to prototype adding a stopwatch
  widget and removing a widget from the grid. This will
  be updated when we integrate with the widget picker.

Bug: b/306500486
Flag: ACONFIG com.android.systemui.communal.communal_hub DEVELOPMENT
Test: atest CommunalWidgetRepositoryImplTest
Test: atest CommunalWidgetDaoTest
Test: on device
Change-Id: Ie9d1d60e50fe669a7b9cfbc0ee12282a28170cbd
parent 10614f64
Loading
Loading
Loading
Loading
+16 −3
Original line number Original line Diff line number Diff line
@@ -187,6 +187,8 @@ android_library {
        "androidx.dynamicanimation_dynamicanimation",
        "androidx.dynamicanimation_dynamicanimation",
        "androidx-constraintlayout_constraintlayout",
        "androidx-constraintlayout_constraintlayout",
        "androidx.exifinterface_exifinterface",
        "androidx.exifinterface_exifinterface",
        "androidx.room_room-runtime",
        "androidx.room_room-ktx",
        "com.google.android.material_material",
        "com.google.android.material_material",
        "kotlinx_coroutines_android",
        "kotlinx_coroutines_android",
        "kotlinx_coroutines",
        "kotlinx_coroutines",
@@ -207,10 +209,16 @@ android_library {
    ],
    ],
    manifest: "AndroidManifest.xml",
    manifest: "AndroidManifest.xml",


    javacflags: ["-Adagger.fastInit=enabled"],
    javacflags: [
        "-Adagger.fastInit=enabled",
        "-Aroom.schemaLocation=frameworks/base/packages/SystemUI/schemas",
    ],
    kotlincflags: ["-Xjvm-default=all"],
    kotlincflags: ["-Xjvm-default=all"],


    plugins: ["dagger2-compiler"],
    plugins: [
        "androidx.room_room-compiler-plugin",
        "dagger2-compiler",
    ],


    lint: {
    lint: {
        extra_check_modules: ["SystemUILintChecker"],
        extra_check_modules: ["SystemUILintChecker"],
@@ -466,6 +474,8 @@ android_library {
        "androidx.dynamicanimation_dynamicanimation",
        "androidx.dynamicanimation_dynamicanimation",
        "androidx-constraintlayout_constraintlayout",
        "androidx-constraintlayout_constraintlayout",
        "androidx.exifinterface_exifinterface",
        "androidx.exifinterface_exifinterface",
        "androidx.room_room-runtime",
        "androidx.room_room-ktx",
        "kotlinx-coroutines-android",
        "kotlinx-coroutines-android",
        "kotlinx-coroutines-core",
        "kotlinx-coroutines-core",
        "kotlinx_coroutines_test",
        "kotlinx_coroutines_test",
@@ -530,7 +540,10 @@ android_library {
        "--extra-packages",
        "--extra-packages",
        "com.android.systemui",
        "com.android.systemui",
    ],
    ],
    plugins: ["dagger2-compiler"],
    plugins: [
        "androidx.room_room-compiler-plugin",
        "dagger2-compiler",
    ],
    lint: {
    lint: {
        test: true,
        test: true,
        extra_check_modules: ["SystemUILintChecker"],
        extra_check_modules: ["SystemUILintChecker"],
+61 −10
Original line number Original line Diff line number Diff line
/*
 * 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.communal.ui.compose
package com.android.systemui.communal.ui.compose


import android.appwidget.AppWidgetHostView
import android.appwidget.AppWidgetHostView
@@ -12,19 +28,26 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.GridItemSpan
import androidx.compose.foundation.lazy.grid.GridItemSpan
import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid
import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Close
import androidx.compose.material3.Card
import androidx.compose.material3.Card
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.compose.ui.viewinterop.AndroidView
import com.android.systemui.communal.shared.model.CommunalContentSize
import com.android.systemui.communal.shared.model.CommunalContentSize
import com.android.systemui.communal.ui.model.CommunalContentUiModel
import com.android.systemui.communal.ui.model.CommunalContentUiModel
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.res.R


@Composable
@Composable
fun CommunalHub(
fun CommunalHub(
@@ -64,10 +87,17 @@ fun CommunalHub(
                    ContentCard(
                    ContentCard(
                        modifier = Modifier.size(Dimensions.CardWidth, widget.size.dp()),
                        modifier = Modifier.size(Dimensions.CardWidth, widget.size.dp()),
                        model = widget,
                        model = widget,
                        deleteOnClick = viewModel::onDeleteWidget
                    )
                    )
                }
                }
            }
            }
        }
        }
        IconButton(onClick = viewModel::onOpenWidgetPicker) {
            Icon(
                Icons.Default.Add,
                LocalContext.current.getString(R.string.button_to_open_widget_picker)
            )
        }
    }
    }
}
}


@@ -80,8 +110,24 @@ private fun TutorialCard(modifier: Modifier = Modifier) {
@Composable
@Composable
private fun ContentCard(
private fun ContentCard(
    model: CommunalContentUiModel,
    model: CommunalContentUiModel,
    deleteOnClick: (id: Int) -> Unit,
    modifier: Modifier = Modifier,
    modifier: Modifier = Modifier,
) {
) {
    // TODO(b/309009246): update background color
    Box(
        modifier = modifier.fillMaxSize().background(Color.White),
    ) {
        // TODO(b/308148193): this will be cleaned up soon once the change to convert to
        // CommunalContentUiModel interface is merged
        val widgetId = getWidgetId(model.id)
        widgetId?.let {
            IconButton(onClick = { deleteOnClick(it) }) {
                Icon(
                    Icons.Default.Close,
                    LocalContext.current.getString(R.string.button_to_remove_widget)
                )
            }
        }
        AndroidView(
        AndroidView(
            modifier = modifier,
            modifier = modifier,
            factory = {
            factory = {
@@ -94,6 +140,7 @@ private fun ContentCard(
            },
            },
        )
        )
    }
    }
}


private fun CommunalContentSize.dp(): Dp {
private fun CommunalContentSize.dp(): Dp {
    return when (this) {
    return when (this) {
@@ -103,6 +150,10 @@ private fun CommunalContentSize.dp(): Dp {
    }
    }
}
}


private fun getWidgetId(id: String): Int? {
    return if (id.startsWith("widget_")) id.substring("widget_".length).toInt() else null
}

// Sizes for the tutorial placeholders.
// Sizes for the tutorial placeholders.
private val tutorialContentSizes =
private val tutorialContentSizes =
    listOf(
    listOf(
+2 −0
Original line number Original line Diff line number Diff line
@@ -736,6 +736,8 @@
    <!-- Whether the communal service should be enabled -->
    <!-- Whether the communal service should be enabled -->
    <bool name="config_communalServiceEnabled">false</bool>
    <bool name="config_communalServiceEnabled">false</bool>


    <!-- Name of the database that stores info of widgets shown on glanceable hub -->
    <string name="config_communalDatabase" translatable="false">communal_db</string>
    <!-- Component names of allowed communal widgets -->
    <!-- Component names of allowed communal widgets -->
    <string-array name="config_communalWidgetAllowlist" translatable="false" />
    <string-array name="config_communalWidgetAllowlist" translatable="false" />


+5 −0
Original line number Original line Diff line number Diff line
@@ -1048,6 +1048,11 @@
    <!-- Indicator on keyguard to start the communal tutorial. [CHAR LIMIT=100] -->
    <!-- Indicator on keyguard to start the communal tutorial. [CHAR LIMIT=100] -->
    <string name="communal_tutorial_indicator_text">Swipe left to start the communal tutorial</string>
    <string name="communal_tutorial_indicator_text">Swipe left to start the communal tutorial</string>


    <!-- Description for the button that opens the widget picker on click. [CHAR LIMIT=50] -->
    <string name="button_to_open_widget_picker">Open the widget picker</string>
    <!-- Description for the button that removes a widget on click. [CHAR LIMIT=50] -->
    <string name="button_to_remove_widget">Remove a widget</string>

    <!-- Related to user switcher --><skip/>
    <!-- Related to user switcher --><skip/>


    <!-- Accessibility label for the button that opens the user switcher. -->
    <!-- Accessibility label for the button that opens the user switcher. -->
+79 −0
Original line number Original line Diff line number Diff line
{
  "formatVersion": 1,
  "database": {
    "version": 1,
    "identityHash": "38f223811a414587ee1b6445ae19385d",
    "entities": [
      {
        "tableName": "communal_widget_table",
        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `widget_id` INTEGER NOT NULL, `component_name` TEXT NOT NULL, `item_id` INTEGER NOT NULL)",
        "fields": [
          {
            "fieldPath": "uid",
            "columnName": "uid",
            "affinity": "INTEGER",
            "notNull": true
          },
          {
            "fieldPath": "widgetId",
            "columnName": "widget_id",
            "affinity": "INTEGER",
            "notNull": true
          },
          {
            "fieldPath": "componentName",
            "columnName": "component_name",
            "affinity": "TEXT",
            "notNull": true
          },
          {
            "fieldPath": "itemId",
            "columnName": "item_id",
            "affinity": "INTEGER",
            "notNull": true
          }
        ],
        "primaryKey": {
          "autoGenerate": true,
          "columnNames": [
            "uid"
          ]
        },
        "indices": [],
        "foreignKeys": []
      },
      {
        "tableName": "communal_item_rank_table",
        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `rank` INTEGER NOT NULL DEFAULT 0)",
        "fields": [
          {
            "fieldPath": "uid",
            "columnName": "uid",
            "affinity": "INTEGER",
            "notNull": true
          },
          {
            "fieldPath": "rank",
            "columnName": "rank",
            "affinity": "INTEGER",
            "notNull": true,
            "defaultValue": "0"
          }
        ],
        "primaryKey": {
          "autoGenerate": true,
          "columnNames": [
            "uid"
          ]
        },
        "indices": [],
        "foreignKeys": []
      }
    ],
    "views": [],
    "setupQueries": [
      "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '38f223811a414587ee1b6445ae19385d')"
    ]
  }
}
 No newline at end of file
Loading