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

Commit 96be44e8 authored by Darrell Shi's avatar Darrell Shi
Browse files

Restore work profile widgets on hub

- Delay restore widgets until user setup is complete
- Skip work profile widgets if work profile is not set up
- Manually bind work profile widgets if work profile is set up

Test: atest CommunalBackupRestoreStartableTest
Test: atest CommunalWidgetRepositoryImplTest
Test: build & flash and verified work profile widgets restored
Bug: 330945203
Flag: com.android.systemui.communal_hub
Change-Id: Ie6483de119cfe7dbd760cb79862046ce49aaeca4
parent 4986ebfb
Loading
Loading
Loading
Loading
+74 −7
Original line number Diff line number Diff line
@@ -20,6 +20,9 @@ import android.appwidget.AppWidgetManager
import android.content.Context
import android.content.Intent
import android.content.mockedContext
import android.os.Handler
import android.os.fakeExecutorHandler
import android.provider.Settings.Secure.USER_SETUP_COMPLETE
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -27,11 +30,13 @@ import com.android.systemui.broadcast.FakeBroadcastDispatcher
import com.android.systemui.broadcast.broadcastDispatcher
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.widgets.CommunalWidgetModule
import com.android.systemui.concurrency.fakeExecutor
import com.android.systemui.kosmos.testScope
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.kotlinArgumentCaptor
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.settings.SecureSettings
import com.android.systemui.util.settings.fakeSettings
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -41,6 +46,8 @@ import org.mockito.Mock
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
import org.mockito.kotlin.any
import org.mockito.kotlin.argumentCaptor

@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -50,10 +57,13 @@ class CommunalBackupRestoreStartableTest : SysuiTestCase() {

    @Mock private lateinit var communalInteractor: CommunalInteractor

    private val mapCaptor = kotlinArgumentCaptor<Map<Int, Int>>()
    private val mapCaptor = argumentCaptor<Map<Int, Int>>()

    private lateinit var context: Context
    private lateinit var broadcastDispatcher: FakeBroadcastDispatcher
    private lateinit var secureSettings: SecureSettings
    private lateinit var handler: Handler
    private lateinit var fakeExecutor: FakeExecutor
    private lateinit var underTest: CommunalBackupRestoreStartable

    @Before
@@ -62,18 +72,28 @@ class CommunalBackupRestoreStartableTest : SysuiTestCase() {

        context = kosmos.mockedContext
        broadcastDispatcher = kosmos.broadcastDispatcher
        secureSettings = kosmos.fakeSettings
        handler = kosmos.fakeExecutorHandler
        fakeExecutor = kosmos.fakeExecutor

        secureSettings.putInt(USER_SETUP_COMPLETE, 0)

        underTest =
            CommunalBackupRestoreStartable(
                broadcastDispatcher,
                communalInteractor,
                logcatLogBuffer("CommunalBackupRestoreStartable"),
                secureSettings,
                handler,
            )
    }

    @Test
    fun testRestoreWidgetsUponHostRestored() =
    fun restoreWidgets_userSetUpComplete_performRestore() =
        testScope.runTest {
            // User set up complete
            secureSettings.putInt(USER_SETUP_COMPLETE, 1)

            underTest.start()

            // Verify restore widgets not called
@@ -94,7 +114,7 @@ class CommunalBackupRestoreStartableTest : SysuiTestCase() {

            // Verify restore widgets called
            verify(communalInteractor).restoreWidgets(mapCaptor.capture())
            val oldToNewWidgetIdMap = mapCaptor.value
            val oldToNewWidgetIdMap = mapCaptor.firstValue
            assertThat(oldToNewWidgetIdMap)
                .containsExactlyEntriesIn(
                    mapOf(
@@ -106,10 +126,54 @@ class CommunalBackupRestoreStartableTest : SysuiTestCase() {
        }

    @Test
    fun testDoNotRestoreWidgetsIfNotForCommunalWidgetHost() =
    fun restoreWidgets_userSetUpNotComplete_restoreWhenUserSetupComplete() =
        testScope.runTest {
            underTest.start()

            // Verify restore widgets not called
            verify(communalInteractor, never()).restoreWidgets(any())

            // Trigger app widget host restored
            val intent =
                Intent().apply {
                    action = AppWidgetManager.ACTION_APPWIDGET_HOST_RESTORED
                    putExtra(
                        AppWidgetManager.EXTRA_HOST_ID,
                        CommunalWidgetModule.APP_WIDGET_HOST_ID
                    )
                    putExtra(AppWidgetManager.EXTRA_APPWIDGET_OLD_IDS, intArrayOf(1, 2, 3))
                    putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, intArrayOf(7, 8, 9))
                }
            broadcastDispatcher.sendIntentToMatchingReceiversOnly(context, intent)

            // Verify restore widgets not called because user setup not complete
            verify(communalInteractor, never()).restoreWidgets(any())

            // User setup complete
            secureSettings.putInt(USER_SETUP_COMPLETE, 1)
            fakeExecutor.runAllReady()

            // Verify restore widgets called
            verify(communalInteractor).restoreWidgets(mapCaptor.capture())
            val oldToNewWidgetIdMap = mapCaptor.firstValue
            assertThat(oldToNewWidgetIdMap)
                .containsExactlyEntriesIn(
                    mapOf(
                        Pair(1, 7),
                        Pair(2, 8),
                        Pair(3, 9),
                    )
                )
        }

    @Test
    fun restoreWidgets_broadcastNotForCommunalWidgetHost_doNotPerformRestore() =
        testScope.runTest {
            // User set up complete
            secureSettings.putInt(USER_SETUP_COMPLETE, 1)

            underTest.start()

            // Trigger app widget host restored, but for another host
            val hostId = CommunalWidgetModule.APP_WIDGET_HOST_ID + 1
            val intent =
@@ -126,8 +190,11 @@ class CommunalBackupRestoreStartableTest : SysuiTestCase() {
        }

    @Test
    fun testAbortRestoreWidgetsIfOldToNewIdsMappingInvalid() =
    fun restoreWidgets_oldToNewIdsMappingInvalid_abortRestore() =
        testScope.runTest {
            // User set up complete
            secureSettings.putInt(USER_SETUP_COMPLETE, 1)

            underTest.start()

            // Trigger app widget host restored, but new ids list is one too many for old ids
+160 −18
Original line number Diff line number Diff line
@@ -48,7 +48,6 @@ import com.android.systemui.log.LogBuffer
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.res.R
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.withArgCaptor
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
@@ -64,6 +63,7 @@ import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
import org.mockito.kotlin.any
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever

@@ -79,6 +79,9 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
    @Mock private lateinit var communalWidgetDao: CommunalWidgetDao
    @Mock private lateinit var backupManager: BackupManager

    private val communalHubStateCaptor = argumentCaptor<CommunalHubState>()
    private val componentNameCaptor = argumentCaptor<ComponentName>()

    private lateinit var backupUtils: CommunalBackupUtils
    private lateinit var logBuffer: LogBuffer
    private lateinit var fakeWidgets: MutableStateFlow<Map<CommunalItemRank, CommunalWidgetItem>>
@@ -90,6 +93,7 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
    private val userManager = kosmos.userManager

    private val mainUser = UserHandle(0)
    private val workProfile = UserHandle(10)

    private val fakeAllowlist =
        listOf(
@@ -114,8 +118,9 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {

        whenever(communalWidgetDao.getWidgets()).thenReturn(fakeWidgets)
        whenever(communalWidgetHost.appWidgetProviders).thenReturn(fakeProviders)
        whenever(userManager.getUserSerialNumber(mainUser.identifier))
            .thenReturn(testUserSerialNumber(mainUser))
        whenever(userManager.mainUser).thenReturn(mainUser)

        restoreUser(mainUser)

        underTest =
            CommunalWidgetRepositoryImpl(
@@ -452,11 +457,8 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
            runCurrent()

            // Verify state restored, and widget 2 skipped
            val restoredState =
                withArgCaptor<CommunalHubState> {
                    verify(communalWidgetDao).restoreCommunalHubState(capture())
                }
            val restoredWidgets = restoredState.widgets.toList()
            verify(communalWidgetDao).restoreCommunalHubState(communalHubStateCaptor.capture())
            val restoredWidgets = communalHubStateCaptor.firstValue.widgets.toList()
            assertThat(restoredWidgets).hasSize(1)

            val restoredWidget = restoredWidgets.first()
@@ -482,11 +484,8 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
            runCurrent()

            // Verify widget 1 and 2 are restored, and are now 11 and 12.
            val restoredState =
                withArgCaptor<CommunalHubState> {
                    verify(communalWidgetDao).restoreCommunalHubState(capture())
                }
            val restoredWidgets = restoredState.widgets.toList()
            verify(communalWidgetDao).restoreCommunalHubState(communalHubStateCaptor.capture())
            val restoredWidgets = communalHubStateCaptor.firstValue.widgets.toList()
            assertThat(restoredWidgets).hasSize(2)

            val restoredWidget1 = restoredWidgets[0]
@@ -520,11 +519,8 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
            runCurrent()

            // Verify widget 1 and 2 are restored, and are now 1 and 12.
            val restoredState =
                withArgCaptor<CommunalHubState> {
                    verify(communalWidgetDao).restoreCommunalHubState(capture())
                }
            val restoredWidgets = restoredState.widgets.toList()
            verify(communalWidgetDao).restoreCommunalHubState(communalHubStateCaptor.capture())
            val restoredWidgets = communalHubStateCaptor.firstValue.widgets.toList()
            assertThat(restoredWidgets).hasSize(2)

            val restoredWidget1 = restoredWidgets[0]
@@ -540,6 +536,126 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
            assertThat(restoredWidget2.rank).isEqualTo(expectedWidget2.rank)
        }

    @Test
    fun restoreWidgets_undefinedUser_restoredAsMain() =
        testScope.runTest {
            // Write two widgets to file, both of which have user serial number undefined.
            val fakeState =
                CommunalHubState().apply {
                    widgets =
                        listOf(
                                CommunalHubState.CommunalWidgetItem().apply {
                                    widgetId = 1
                                    componentName = "pk_name/fake_widget_1"
                                    rank = 1
                                    userSerialNumber =
                                        CommunalWidgetItem.USER_SERIAL_NUMBER_UNDEFINED
                                },
                                CommunalHubState.CommunalWidgetItem().apply {
                                    widgetId = 2
                                    componentName = "pk_name/fake_widget_2"
                                    rank = 2
                                    userSerialNumber =
                                        CommunalWidgetItem.USER_SERIAL_NUMBER_UNDEFINED
                                },
                            )
                            .toTypedArray()
                }
            backupUtils.writeBytesToDisk(fakeState.toByteArray())

            // Set up app widget host with widget ids.
            setAppWidgetIds(listOf(11, 12))

            // Restore widgets.
            underTest.restoreWidgets(mapOf(Pair(1, 11), Pair(2, 12)))
            runCurrent()

            // Verify widget 1 and 2 are restored with the main user.
            verify(communalWidgetDao).restoreCommunalHubState(communalHubStateCaptor.capture())
            val restoredWidgets = communalHubStateCaptor.firstValue.widgets.toList()
            assertThat(restoredWidgets).hasSize(2)

            val restoredWidget1 = restoredWidgets[0]
            assertThat(restoredWidget1.widgetId).isEqualTo(11)
            assertThat(restoredWidget1.userSerialNumber).isEqualTo(testUserSerialNumber(mainUser))

            val restoredWidget2 = restoredWidgets[1]
            assertThat(restoredWidget2.widgetId).isEqualTo(12)
            assertThat(restoredWidget2.userSerialNumber).isEqualTo(testUserSerialNumber(mainUser))
        }

    @Test
    fun restoreWidgets_workProfileNotRestored_widgetSkipped() =
        testScope.runTest {
            // Write fake state to file
            backupUtils.writeBytesToDisk(fakeStateWithWorkProfile.toByteArray())

            // Set up app widget host with widget ids.
            // (b/349852237) It's possible that the platform restores widgets even though their user
            // is not restored.
            setAppWidgetIds(listOf(11, 12))

            // Restore widgets.
            underTest.restoreWidgets(mapOf(Pair(1, 11), Pair(2, 12)))
            runCurrent()

            // Verify only widget 1 is restored. Widget 2 is skipped because it belongs to a work
            // profile, which is not restored.
            verify(communalWidgetDao).restoreCommunalHubState(communalHubStateCaptor.capture())
            val restoredWidgets = communalHubStateCaptor.firstValue.widgets.toList()
            assertThat(restoredWidgets).hasSize(1)

            val restoredWidget = restoredWidgets[0]
            assertThat(restoredWidget.widgetId).isEqualTo(11)
            assertThat(restoredWidget.userSerialNumber).isEqualTo(testUserSerialNumber(mainUser))
        }

    @Test
    fun restoreWidgets_workProfileRestored_manuallyBindWidget() =
        testScope.runTest {
            // Write fake state to file
            backupUtils.writeBytesToDisk(fakeStateWithWorkProfile.toByteArray())

            // Set up app widget host with widget ids.
            // (b/349852237) It's possible that the platform restores widgets even though their user
            // is not restored.
            setAppWidgetIds(listOf(11, 12))

            // Restore work profile.
            restoreUser(workProfile)

            val newWidgetId = 13
            whenever(communalWidgetHost.allocateIdAndBindWidget(any(), any()))
                .thenReturn(newWidgetId)

            // Restore widgets.
            underTest.restoreWidgets(mapOf(Pair(1, 11), Pair(2, 12)))
            runCurrent()

            // Verify widget 1 is restored.
            verify(communalWidgetDao).restoreCommunalHubState(communalHubStateCaptor.capture())
            val restoredWidgets = communalHubStateCaptor.firstValue.widgets.toList()
            assertThat(restoredWidgets).hasSize(1)

            val restoredWidget = restoredWidgets[0]
            assertThat(restoredWidget.widgetId).isEqualTo(11)
            assertThat(restoredWidget.userSerialNumber).isEqualTo(testUserSerialNumber(mainUser))

            // Verify widget 2 (now 12) is removed from platform
            verify(appWidgetHost).deleteAppWidgetId(12)

            // Verify work profile widget is manually bound
            verify(communalWidgetDao)
                .addWidget(
                    eq(newWidgetId),
                    componentNameCaptor.capture(),
                    eq(2),
                    eq(testUserSerialNumber(workProfile))
                )
            assertThat(componentNameCaptor.firstValue)
                .isEqualTo(ComponentName("pk_name", "fake_widget_2"))
        }

    @Test
    fun pendingWidgets() =
        testScope.runTest {
@@ -648,6 +764,13 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
        return user.identifier + 100
    }

    private fun restoreUser(user: UserHandle) {
        whenever(backupManager.getUserForAncestralSerialNumber(user.identifier.toLong()))
            .thenReturn(user)
        whenever(userManager.getUserSerialNumber(user.identifier))
            .thenReturn(testUserSerialNumber(user))
    }

    private companion object {
        val PROVIDER_INFO_REQUIRES_CONFIGURATION =
            AppWidgetProviderInfo().apply { configure = ComponentName("test.pkg", "test.cmp") }
@@ -676,5 +799,24 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
                        )
                        .toTypedArray()
            }
        val fakeStateWithWorkProfile =
            CommunalHubState().apply {
                widgets =
                    listOf(
                            CommunalHubState.CommunalWidgetItem().apply {
                                widgetId = 1
                                componentName = "pk_name/fake_widget_1"
                                rank = 1
                                userSerialNumber = 0
                            },
                            CommunalHubState.CommunalWidgetItem().apply {
                                widgetId = 2
                                componentName = "pk_name/fake_widget_2"
                                rank = 2
                                userSerialNumber = 10
                            },
                        )
                        .toTypedArray()
            }
    }
}
+56 −2
Original line number Diff line number Diff line
@@ -21,6 +21,9 @@ import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.database.ContentObserver
import android.os.Handler
import android.provider.Settings.Secure.USER_SETUP_COMPLETE
import com.android.systemui.CoreStartable
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.communal.domain.interactor.CommunalInteractor
@@ -29,6 +32,7 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.Logger
import com.android.systemui.log.dagger.CommunalLog
import com.android.systemui.util.settings.SecureSettings
import javax.inject.Inject

@SysUISingleton
@@ -38,10 +42,15 @@ constructor(
    private val broadcastDispatcher: BroadcastDispatcher,
    private val communalInteractor: CommunalInteractor,
    @CommunalLog logBuffer: LogBuffer,
    private val secureSettings: SecureSettings,
    handler: Handler,
) : CoreStartable, BroadcastReceiver() {

    private val logger = Logger(logBuffer, TAG)

    private var oldToNewWidgetIdMap = emptyMap<Int, Int>()
    private var userSetupComplete = false

    override fun start() {
        broadcastDispatcher.registerReceiver(
            receiver = this,
@@ -73,8 +82,53 @@ constructor(
            return
        }

        val oldToNewWidgetIdMap = oldIds.zip(newIds).toMap()
        communalInteractor.restoreWidgets(oldToNewWidgetIdMap)
        oldToNewWidgetIdMap = oldIds.zip(newIds).toMap()

        logger.i({ "On old to new widget ids mapping updated: $str1" }) {
            str1 = oldToNewWidgetIdMap.toString()
        }

        maybeRestoreWidgets()

        // Start observing if user setup is not complete
        if (!userSetupComplete) {
            startObservingUserSetupComplete()
        }
    }

    private val userSetupObserver =
        object : ContentObserver(handler) {
            override fun onChange(selfChange: Boolean) {
                maybeRestoreWidgets()

                // Stop observing once user setup is complete
                if (userSetupComplete) {
                    stopObservingUserSetupComplete()
                }
            }
        }

    private fun maybeRestoreWidgets() {
        val newValue = secureSettings.getInt(USER_SETUP_COMPLETE) > 0

        if (userSetupComplete != newValue) {
            userSetupComplete = newValue
            logger.i({ "User setup complete: $bool1" }) { bool1 = userSetupComplete }
        }

        if (userSetupComplete && oldToNewWidgetIdMap.isNotEmpty()) {
            logger.i("Starting to restore widgets")
            communalInteractor.restoreWidgets(oldToNewWidgetIdMap.toMap())
            oldToNewWidgetIdMap = emptyMap()
        }
    }

    private fun startObservingUserSetupComplete() {
        secureSettings.registerContentObserverSync(USER_SETUP_COMPLETE, userSetupObserver)
    }

    private fun stopObservingUserSetupComplete() {
        secureSettings.unregisterContentObserverSync(userSetupObserver)
    }

    companion object {
+75 −2
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import com.android.systemui.common.data.repository.PackageChangeRepository
import com.android.systemui.common.shared.model.PackageInstallSession
import com.android.systemui.communal.data.backup.CommunalBackupUtils
import com.android.systemui.communal.data.db.CommunalWidgetDao
import com.android.systemui.communal.data.db.CommunalWidgetItem
import com.android.systemui.communal.nano.CommunalHubState
import com.android.systemui.communal.proto.toCommunalHubState
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
@@ -231,9 +232,38 @@ constructor(
                return@launch
            }

            // Abort restoring widgets if this code is somehow run on a device that does not have
            // a main user, e.g. auto.
            val mainUser = userManager.mainUser
            if (mainUser == null) {
                logger.w("Skipped restoring widgets because device does not have a main user")
                return@launch
            }

            val widgetsWithHost = appWidgetHost.appWidgetIds.toList()
            val widgetsToRemove = widgetsWithHost.toMutableList()

            val oldUserSerialNumbers = state.widgets.map { it.userSerialNumber }.distinct()
            val usersMap =
                oldUserSerialNumbers.associateWith { oldUserSerialNumber ->
                    if (oldUserSerialNumber == CommunalWidgetItem.USER_SERIAL_NUMBER_UNDEFINED) {
                        // If user serial number from the backup is undefined, the widget was added
                        // to the hub before user serial numbers are stored in the database. In this
                        // case, we restore the widget with the main user.
                        mainUser
                    } else {
                        // If the user serial number is defined, look up whether the user is
                        // restored. This API returns a user handle matching its backed up user
                        // serial number, if the user is restored. Otherwise, null is returned.
                        backupManager.getUserForAncestralSerialNumber(oldUserSerialNumber.toLong())
                            ?: null
                    }
                }
            logger.d({ "Restored users map: $str1" }) { str1 = usersMap.toString() }

            // A set to hold all widgets that belong to non-main users
            val secondaryUserWidgets = mutableSetOf<CommunalHubState.CommunalWidgetItem>()

            // Produce a new state to be restored, skipping invalid widgets
            val newWidgets =
                state.widgets.mapNotNull { restoredWidget ->
@@ -252,21 +282,64 @@ constructor(
                        return@mapNotNull null
                    }

                    // Skip if user / profile is not registered
                    val newUser = usersMap[restoredWidget.userSerialNumber]
                    if (newUser == null) {
                        logger.d({
                            "Skipped restoring widget $int1 because its user $int2 is not " +
                                "registered"
                        }) {
                            int1 = restoredWidget.widgetId
                            int2 = restoredWidget.userSerialNumber
                        }
                        return@mapNotNull null
                    }

                    // Place secondary user widgets in a bucket to be manually bound later because
                    // of a platform bug (b/349852237) that backs up work profile widgets as
                    // personal.
                    if (newUser.identifier != mainUser.identifier) {
                        logger.d({
                            "Skipped restoring widget $int1 for now because its new user $int2 " +
                                "is secondary. This widget will be bound later."
                        }) {
                            int1 = restoredWidget.widgetId
                            int2 = newUser.identifier
                        }
                        secondaryUserWidgets.add(restoredWidget)
                        return@mapNotNull null
                    }

                    widgetsToRemove.remove(newWidgetId)

                    CommunalHubState.CommunalWidgetItem().apply {
                        widgetId = newWidgetId
                        componentName = restoredWidget.componentName
                        rank = restoredWidget.rank
                        userSerialNumber = restoredWidget.userSerialNumber
                        userSerialNumber = userManager.getUserSerialNumber(newUser.identifier)
                    }
                }
            val newState = CommunalHubState().apply { widgets = newWidgets.toTypedArray() }

            // Restore database
            logger.i("Restoring communal database $newState")
            logger.i("Restoring communal database:\n$newState")
            communalWidgetDao.restoreCommunalHubState(newState)

            // Manually bind each secondary user widget due to platform bug b/349852237
            secondaryUserWidgets.forEach { widget ->
                val newUser = usersMap[widget.userSerialNumber]!!
                logger.i({ "Binding secondary user ($int1) widget $int2: $str1" }) {
                    int1 = newUser.identifier
                    int2 = widget.widgetId
                    str1 = widget.componentName
                }
                addWidget(
                    provider = ComponentName.unflattenFromString(widget.componentName)!!,
                    user = newUser,
                    priority = widget.rank,
                )
            }

            // Delete restored state file from disk
            backupUtils.clear()