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

Commit dcc8804c authored by Candice Lo's avatar Candice Lo Committed by Automerger Merge Worker
Browse files

Merge changes I8da85eb9,I5f91a776 into udc-dev am: 22632e0f am: d796ce69

parents 2cc92f03 d796ce69
Loading
Loading
Loading
Loading
+64 −20
Original line number Original line Diff line number Diff line
@@ -15,11 +15,12 @@
 */
 */
package com.android.systemui.accessibility.fontscaling
package com.android.systemui.accessibility.fontscaling


import android.annotation.WorkerThread
import android.content.Context
import android.content.Context
import android.content.pm.ActivityInfo
import android.content.pm.ActivityInfo
import android.content.res.Configuration
import android.content.res.Configuration
import android.database.ContentObserver
import android.os.Bundle
import android.os.Bundle
import android.os.Handler
import android.provider.Settings
import android.provider.Settings
import android.util.TypedValue
import android.util.TypedValue
import android.view.LayoutInflater
import android.view.LayoutInflater
@@ -27,13 +28,18 @@ import android.widget.Button
import android.widget.SeekBar
import android.widget.SeekBar
import android.widget.SeekBar.OnSeekBarChangeListener
import android.widget.SeekBar.OnSeekBarChangeListener
import android.widget.TextView
import android.widget.TextView
import androidx.annotation.MainThread
import androidx.annotation.WorkerThread
import com.android.systemui.R
import com.android.systemui.R
import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView
import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.settings.SecureSettings
import com.android.systemui.util.settings.SecureSettings
import com.android.systemui.util.settings.SystemSettings
import com.android.systemui.util.settings.SystemSettings
import java.util.concurrent.Executor
import com.android.systemui.util.time.SystemClock
import java.util.concurrent.atomic.AtomicInteger
import kotlin.math.roundToInt
import kotlin.math.roundToInt


/** The Dialog that contains a seekbar for changing the font size. */
/** The Dialog that contains a seekbar for changing the font size. */
@@ -41,17 +47,31 @@ class FontScalingDialog(
    context: Context,
    context: Context,
    private val systemSettings: SystemSettings,
    private val systemSettings: SystemSettings,
    private val secureSettings: SecureSettings,
    private val secureSettings: SecureSettings,
    @Background private val backgroundExecutor: Executor
    private val systemClock: SystemClock,
    @Main mainHandler: Handler,
    @Background private val backgroundDelayableExecutor: DelayableExecutor
) : SystemUIDialog(context) {
) : SystemUIDialog(context) {
    private val MIN_UPDATE_INTERVAL_MS: Long = 800
    private val CHANGE_BY_SEEKBAR_DELAY_MS: Long = 100
    private val CHANGE_BY_BUTTON_DELAY_MS: Long = 300
    private val strEntryValues: Array<String> =
    private val strEntryValues: Array<String> =
        context.resources.getStringArray(com.android.settingslib.R.array.entryvalues_font_size)
        context.resources.getStringArray(com.android.settingslib.R.array.entryvalues_font_size)
    private lateinit var title: TextView
    private lateinit var title: TextView
    private lateinit var doneButton: Button
    private lateinit var doneButton: Button
    private lateinit var seekBarWithIconButtonsView: SeekBarWithIconButtonsView
    private lateinit var seekBarWithIconButtonsView: SeekBarWithIconButtonsView
    private var lastProgress: Int = -1
    private var lastProgress: AtomicInteger = AtomicInteger(-1)
    private var lastUpdateTime: Long = 0
    private var cancelUpdateFontScaleRunnable: Runnable? = null


    private val configuration: Configuration = Configuration(context.resources.configuration)
    private val configuration: Configuration = Configuration(context.resources.configuration)


    private val fontSizeObserver =
        object : ContentObserver(mainHandler) {
            override fun onChange(selfChange: Boolean) {
                lastUpdateTime = systemClock.elapsedRealtime()
            }
        }

    override fun onCreate(savedInstanceState: Bundle?) {
    override fun onCreate(savedInstanceState: Bundle?) {
        setTitle(R.string.font_scaling_dialog_title)
        setTitle(R.string.font_scaling_dialog_title)
        setView(LayoutInflater.from(context).inflate(R.layout.font_scaling_dialog, null))
        setView(LayoutInflater.from(context).inflate(R.layout.font_scaling_dialog, null))
@@ -79,21 +99,21 @@ class FontScalingDialog(
        seekBarWithIconButtonsView.setMax((strEntryValues).size - 1)
        seekBarWithIconButtonsView.setMax((strEntryValues).size - 1)


        val currentScale = systemSettings.getFloat(Settings.System.FONT_SCALE, 1.0f)
        val currentScale = systemSettings.getFloat(Settings.System.FONT_SCALE, 1.0f)
        lastProgress = fontSizeValueToIndex(currentScale)
        lastProgress.set(fontSizeValueToIndex(currentScale))
        seekBarWithIconButtonsView.setProgress(lastProgress)
        seekBarWithIconButtonsView.setProgress(lastProgress.get())


        seekBarWithIconButtonsView.setOnSeekBarChangeListener(
        seekBarWithIconButtonsView.setOnSeekBarChangeListener(
            object : OnSeekBarChangeListener {
            object : OnSeekBarChangeListener {
                var isTrackingTouch = false
                var isTrackingTouch = false


                override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
                override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
                    // Always provide preview configuration for text first when there is a change
                    // in the seekbar progress.
                    createTextPreview(progress)

                    if (!isTrackingTouch) {
                    if (!isTrackingTouch) {
                        // The seekbar progress is changed by icon buttons
                        // The seekbar progress is changed by icon buttons
                        changeFontSize(progress)
                        changeFontSize(progress, CHANGE_BY_BUTTON_DELAY_MS)
                    } else {
                        // Provide preview configuration for text instead of changing the system
                        // font scale before users release their finger from the seekbar.
                        createTextPreview(progress)
                    }
                    }
                }
                }


@@ -103,26 +123,50 @@ class FontScalingDialog(


                override fun onStopTrackingTouch(seekBar: SeekBar) {
                override fun onStopTrackingTouch(seekBar: SeekBar) {
                    isTrackingTouch = false
                    isTrackingTouch = false
                    changeFontSize(seekBar.progress)
                    changeFontSize(seekBar.progress, CHANGE_BY_SEEKBAR_DELAY_MS)
                }
                }
            }
            }
        )
        )
        doneButton.setOnClickListener { dismiss() }
        doneButton.setOnClickListener { dismiss() }
        systemSettings.registerContentObserver(Settings.System.FONT_SCALE, fontSizeObserver)
    }
    }


    private fun changeFontSize(progress: Int) {
    /**
        if (progress != lastProgress) {
     * Avoid SeekBar flickers when changing font scale. See the description from Setting at {@link
     * TextReadingPreviewController#postCommitDelayed} for the reasons of flickers.
     */
    @MainThread
    fun updateFontScaleDelayed(delayMsFromSource: Long) {
        var delayMs = delayMsFromSource
        if (systemClock.elapsedRealtime() - lastUpdateTime < MIN_UPDATE_INTERVAL_MS) {
            delayMs += MIN_UPDATE_INTERVAL_MS
        }
        cancelUpdateFontScaleRunnable?.run()
        cancelUpdateFontScaleRunnable =
            backgroundDelayableExecutor.executeDelayed({ updateFontScale() }, delayMs)
    }

    override fun stop() {
        cancelUpdateFontScaleRunnable?.run()
        cancelUpdateFontScaleRunnable = null
        systemSettings.unregisterContentObserver(fontSizeObserver)
    }

    @MainThread
    private fun changeFontSize(progress: Int, changedWithDelay: Long) {
        if (progress != lastProgress.get()) {
            lastProgress.set(progress)

            if (!fontSizeHasBeenChangedFromTile) {
            if (!fontSizeHasBeenChangedFromTile) {
                backgroundExecutor.execute { updateSecureSettingsIfNeeded() }
                backgroundDelayableExecutor.execute { updateSecureSettingsIfNeeded() }
                fontSizeHasBeenChangedFromTile = true
                fontSizeHasBeenChangedFromTile = true
            }
            }


            backgroundExecutor.execute { updateFontScale(strEntryValues[progress]) }
            updateFontScaleDelayed(changedWithDelay)

            lastProgress = progress
        }
        }
    }
    }


    @WorkerThread
    private fun fontSizeValueToIndex(value: Float): Int {
    private fun fontSizeValueToIndex(value: Float): Int {
        var lastValue = strEntryValues[0].toFloat()
        var lastValue = strEntryValues[0].toFloat()
        for (i in 1 until strEntryValues.size) {
        for (i in 1 until strEntryValues.size) {
@@ -150,8 +194,8 @@ class FontScalingDialog(
    }
    }


    @WorkerThread
    @WorkerThread
    fun updateFontScale(newScale: String) {
    fun updateFontScale() {
        systemSettings.putString(Settings.System.FONT_SCALE, newScale)
        systemSettings.putString(Settings.System.FONT_SCALE, strEntryValues[lastProgress.get()])
    }
    }


    @WorkerThread
    @WorkerThread
+9 −4
Original line number Original line Diff line number Diff line
@@ -17,7 +17,6 @@ package com.android.systemui.qs.tiles


import android.content.Intent
import android.content.Intent
import android.os.Handler
import android.os.Handler
import android.os.HandlerExecutor
import android.os.Looper
import android.os.Looper
import android.provider.Settings
import android.provider.Settings
import android.view.View
import android.view.View
@@ -40,8 +39,10 @@ import com.android.systemui.qs.QsEventLogger
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.qs.tileimpl.QSTileImpl
import com.android.systemui.qs.tileimpl.QSTileImpl
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.settings.SecureSettings
import com.android.systemui.util.settings.SecureSettings
import com.android.systemui.util.settings.SystemSettings
import com.android.systemui.util.settings.SystemSettings
import com.android.systemui.util.time.SystemClock
import javax.inject.Inject
import javax.inject.Inject


class FontScalingTile
class FontScalingTile
@@ -50,7 +51,7 @@ constructor(
    host: QSHost,
    host: QSHost,
    uiEventLogger: QsEventLogger,
    uiEventLogger: QsEventLogger,
    @Background backgroundLooper: Looper,
    @Background backgroundLooper: Looper,
    @Main mainHandler: Handler,
    @Main private val mainHandler: Handler,
    falsingManager: FalsingManager,
    falsingManager: FalsingManager,
    metricsLogger: MetricsLogger,
    metricsLogger: MetricsLogger,
    statusBarStateController: StatusBarStateController,
    statusBarStateController: StatusBarStateController,
@@ -59,7 +60,9 @@ constructor(
    private val dialogLaunchAnimator: DialogLaunchAnimator,
    private val dialogLaunchAnimator: DialogLaunchAnimator,
    private val systemSettings: SystemSettings,
    private val systemSettings: SystemSettings,
    private val secureSettings: SecureSettings,
    private val secureSettings: SecureSettings,
    private val featureFlags: FeatureFlags
    private val systemClock: SystemClock,
    private val featureFlags: FeatureFlags,
    @Background private val backgroundDelayableExecutor: DelayableExecutor
) :
) :
    QSTileImpl<QSTile.State?>(
    QSTileImpl<QSTile.State?>(
        host,
        host,
@@ -89,7 +92,9 @@ constructor(
                    mContext,
                    mContext,
                    systemSettings,
                    systemSettings,
                    secureSettings,
                    secureSettings,
                    HandlerExecutor(mHandler)
                    systemClock,
                    mainHandler,
                    backgroundDelayableExecutor
                )
                )
            if (view != null) {
            if (view != null) {
                dialogLaunchAnimator.showFromView(
                dialogLaunchAnimator.showFromView(
+40 −9
Original line number Original line Diff line number Diff line
@@ -50,10 +50,14 @@ private const val OFF: Int = 0
@RunWith(AndroidTestingRunner::class)
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
class FontScalingDialogTest : SysuiTestCase() {
class FontScalingDialogTest : SysuiTestCase() {
    private val MIN_UPDATE_INTERVAL_MS: Long = 800
    private val CHANGE_BY_SEEKBAR_DELAY_MS: Long = 100
    private val CHANGE_BY_BUTTON_DELAY_MS: Long = 300
    private lateinit var fontScalingDialog: FontScalingDialog
    private lateinit var fontScalingDialog: FontScalingDialog
    private lateinit var systemSettings: SystemSettings
    private lateinit var systemSettings: SystemSettings
    private lateinit var secureSettings: SecureSettings
    private lateinit var secureSettings: SecureSettings
    private lateinit var backgroundExecutor: FakeExecutor
    private lateinit var systemClock: FakeSystemClock
    private lateinit var backgroundDelayableExecutor: FakeExecutor
    private val fontSizeValueArray: Array<String> =
    private val fontSizeValueArray: Array<String> =
        mContext
        mContext
            .getResources()
            .getResources()
@@ -67,10 +71,20 @@ class FontScalingDialogTest : SysuiTestCase() {
        MockitoAnnotations.initMocks(this)
        MockitoAnnotations.initMocks(this)
        val mainHandler = Handler(TestableLooper.get(this).getLooper())
        val mainHandler = Handler(TestableLooper.get(this).getLooper())
        systemSettings = FakeSettings()
        systemSettings = FakeSettings()
        // Guarantee that the systemSettings always starts with the default font scale.
        systemSettings.putFloat(Settings.System.FONT_SCALE, 1.0f)
        secureSettings = FakeSettings()
        secureSettings = FakeSettings()
        backgroundExecutor = FakeExecutor(FakeSystemClock())
        systemClock = FakeSystemClock()
        backgroundDelayableExecutor = FakeExecutor(systemClock)
        fontScalingDialog =
        fontScalingDialog =
            spy(FontScalingDialog(mContext, systemSettings, secureSettings, backgroundExecutor))
            FontScalingDialog(
                mContext,
                systemSettings,
                secureSettings,
                systemClock,
                mainHandler,
                backgroundDelayableExecutor
            )
    }
    }


    @Test
    @Test
@@ -96,9 +110,14 @@ class FontScalingDialogTest : SysuiTestCase() {
        val seekBar: SeekBar = fontScalingDialog.findViewById(R.id.seekbar)!!
        val seekBar: SeekBar = fontScalingDialog.findViewById(R.id.seekbar)!!


        seekBarWithIconButtonsView.setProgress(0)
        seekBarWithIconButtonsView.setProgress(0)
        backgroundDelayableExecutor.runAllReady()
        backgroundDelayableExecutor.advanceClockToNext()
        backgroundDelayableExecutor.runAllReady()


        iconEndFrame.performClick()
        iconEndFrame.performClick()
        backgroundExecutor.runAllReady()
        backgroundDelayableExecutor.runAllReady()
        backgroundDelayableExecutor.advanceClockToNext()
        backgroundDelayableExecutor.runAllReady()


        val currentScale = systemSettings.getFloat(Settings.System.FONT_SCALE, /* def= */ 1.0f)
        val currentScale = systemSettings.getFloat(Settings.System.FONT_SCALE, /* def= */ 1.0f)
        assertThat(seekBar.getProgress()).isEqualTo(1)
        assertThat(seekBar.getProgress()).isEqualTo(1)
@@ -117,9 +136,14 @@ class FontScalingDialogTest : SysuiTestCase() {
        val seekBar: SeekBar = fontScalingDialog.findViewById(R.id.seekbar)!!
        val seekBar: SeekBar = fontScalingDialog.findViewById(R.id.seekbar)!!


        seekBarWithIconButtonsView.setProgress(fontSizeValueArray.size - 1)
        seekBarWithIconButtonsView.setProgress(fontSizeValueArray.size - 1)
        backgroundDelayableExecutor.runAllReady()
        backgroundDelayableExecutor.advanceClockToNext()
        backgroundDelayableExecutor.runAllReady()


        iconStartFrame.performClick()
        iconStartFrame.performClick()
        backgroundExecutor.runAllReady()
        backgroundDelayableExecutor.runAllReady()
        backgroundDelayableExecutor.advanceClockToNext()
        backgroundDelayableExecutor.runAllReady()


        val currentScale = systemSettings.getFloat(Settings.System.FONT_SCALE, /* def= */ 1.0f)
        val currentScale = systemSettings.getFloat(Settings.System.FONT_SCALE, /* def= */ 1.0f)
        assertThat(seekBar.getProgress()).isEqualTo(fontSizeValueArray.size - 2)
        assertThat(seekBar.getProgress()).isEqualTo(fontSizeValueArray.size - 2)
@@ -139,7 +163,7 @@ class FontScalingDialogTest : SysuiTestCase() {


        // Default seekbar progress for font size is 1, set it to another progress 0
        // Default seekbar progress for font size is 1, set it to another progress 0
        seekBarWithIconButtonsView.setProgress(0)
        seekBarWithIconButtonsView.setProgress(0)
        backgroundExecutor.runAllReady()
        backgroundDelayableExecutor.runAllReady()


        val currentSettings =
        val currentSettings =
            secureSettings.getInt(
            secureSettings.getInt(
@@ -153,6 +177,7 @@ class FontScalingDialogTest : SysuiTestCase() {


    @Test
    @Test
    fun dragSeekbar_systemFontSizeSettingsDoesNotChange() {
    fun dragSeekbar_systemFontSizeSettingsDoesNotChange() {
        fontScalingDialog = spy(fontScalingDialog)
        val slider: SeekBarWithIconButtonsView = spy(SeekBarWithIconButtonsView(mContext))
        val slider: SeekBarWithIconButtonsView = spy(SeekBarWithIconButtonsView(mContext))
        whenever(
        whenever(
                fontScalingDialog.findViewById<SeekBarWithIconButtonsView>(R.id.font_scaling_slider)
                fontScalingDialog.findViewById<SeekBarWithIconButtonsView>(R.id.font_scaling_slider)
@@ -169,7 +194,9 @@ class FontScalingDialogTest : SysuiTestCase() {
        // OnSeekBarChangeListener and the seekbar could get updated progress value
        // OnSeekBarChangeListener and the seekbar could get updated progress value
        // in onStopTrackingTouch.
        // in onStopTrackingTouch.
        seekBar.progress = 0
        seekBar.progress = 0
        backgroundExecutor.runAllReady()
        backgroundDelayableExecutor.runAllReady()
        backgroundDelayableExecutor.advanceClockToNext()
        backgroundDelayableExecutor.runAllReady()


        // Verify that the scale of font size remains the default value 1.0f.
        // Verify that the scale of font size remains the default value 1.0f.
        var systemScale = systemSettings.getFloat(Settings.System.FONT_SCALE, /* def= */ 1.0f)
        var systemScale = systemSettings.getFloat(Settings.System.FONT_SCALE, /* def= */ 1.0f)
@@ -177,7 +204,9 @@ class FontScalingDialogTest : SysuiTestCase() {


        // Simulate releasing the finger from the seekbar.
        // Simulate releasing the finger from the seekbar.
        seekBarChangeCaptor.value.onStopTrackingTouch(seekBar)
        seekBarChangeCaptor.value.onStopTrackingTouch(seekBar)
        backgroundExecutor.runAllReady()
        backgroundDelayableExecutor.runAllReady()
        backgroundDelayableExecutor.advanceClockToNext()
        backgroundDelayableExecutor.runAllReady()


        // Verify that the scale of font size has been updated.
        // Verify that the scale of font size has been updated.
        systemScale = systemSettings.getFloat(Settings.System.FONT_SCALE, /* def= */ 1.0f)
        systemScale = systemSettings.getFloat(Settings.System.FONT_SCALE, /* def= */ 1.0f)
@@ -188,6 +217,7 @@ class FontScalingDialogTest : SysuiTestCase() {


    @Test
    @Test
    fun dragSeekBar_createTextPreview() {
    fun dragSeekBar_createTextPreview() {
        fontScalingDialog = spy(fontScalingDialog)
        val slider: SeekBarWithIconButtonsView = spy(SeekBarWithIconButtonsView(mContext))
        val slider: SeekBarWithIconButtonsView = spy(SeekBarWithIconButtonsView(mContext))
        whenever(
        whenever(
                fontScalingDialog.findViewById<SeekBarWithIconButtonsView>(R.id.font_scaling_slider)
                fontScalingDialog.findViewById<SeekBarWithIconButtonsView>(R.id.font_scaling_slider)
@@ -205,7 +235,8 @@ class FontScalingDialogTest : SysuiTestCase() {
            /* progress= */ 0,
            /* progress= */ 0,
            /* fromUser= */ false
            /* fromUser= */ false
        )
        )
        backgroundExecutor.runAllReady()
        backgroundDelayableExecutor.advanceClockToNext()
        backgroundDelayableExecutor.runAllReady()


        verify(fontScalingDialog).createTextPreview(/* index= */ 0)
        verify(fontScalingDialog).createTextPreview(/* index= */ 0)
        fontScalingDialog.dismiss()
        fontScalingDialog.dismiss()
+9 −1
Original line number Original line Diff line number Diff line
@@ -33,10 +33,12 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.qs.QSHost
import com.android.systemui.qs.QSHost
import com.android.systemui.qs.QsEventLogger
import com.android.systemui.qs.QsEventLogger
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.settings.FakeSettings
import com.android.systemui.util.settings.FakeSettings
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.After
import org.junit.Before
import org.junit.Before
@@ -61,6 +63,8 @@ class FontScalingTileTest : SysuiTestCase() {
    @Mock private lateinit var uiEventLogger: QsEventLogger
    @Mock private lateinit var uiEventLogger: QsEventLogger


    private lateinit var testableLooper: TestableLooper
    private lateinit var testableLooper: TestableLooper
    private lateinit var systemClock: FakeSystemClock
    private lateinit var backgroundDelayableExecutor: FakeExecutor
    private lateinit var fontScalingTile: FontScalingTile
    private lateinit var fontScalingTile: FontScalingTile


    val featureFlags = FakeFeatureFlags()
    val featureFlags = FakeFeatureFlags()
@@ -70,6 +74,8 @@ class FontScalingTileTest : SysuiTestCase() {
        MockitoAnnotations.initMocks(this)
        MockitoAnnotations.initMocks(this)
        testableLooper = TestableLooper.get(this)
        testableLooper = TestableLooper.get(this)
        `when`(qsHost.getContext()).thenReturn(mContext)
        `when`(qsHost.getContext()).thenReturn(mContext)
        systemClock = FakeSystemClock()
        backgroundDelayableExecutor = FakeExecutor(systemClock)


        fontScalingTile =
        fontScalingTile =
            FontScalingTile(
            FontScalingTile(
@@ -85,7 +91,9 @@ class FontScalingTileTest : SysuiTestCase() {
                dialogLaunchAnimator,
                dialogLaunchAnimator,
                FakeSettings(),
                FakeSettings(),
                FakeSettings(),
                FakeSettings(),
                featureFlags
                FakeSystemClock(),
                featureFlags,
                backgroundDelayableExecutor,
            )
            )
        fontScalingTile.initialize()
        fontScalingTile.initialize()
        testableLooper.processAllMessages()
        testableLooper.processAllMessages()