Extract unfold classes to a separate library
No behaviour changes in this CL. Extracted classes related to the fold/unfold animation (that plays on foldable devices) to a separate library that does not depend on system classes. This is necessary to ease reusing these classes in live wallpapers. Bug: 231936088 Test: fold/unfold device Change-Id: Ic1e1ec742867fca9c1209b930accd5738de18e39
This commit is contained in:
@@ -47,6 +47,7 @@ android_library {
|
||||
],
|
||||
static_libs: [
|
||||
"PluginCoreLib",
|
||||
"SystemUIUnfoldLib",
|
||||
"androidx.dynamicanimation_dynamicanimation",
|
||||
"androidx.concurrent_concurrent-futures",
|
||||
"dagger2",
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.unfold.config
|
||||
|
||||
import android.content.Context
|
||||
import android.os.SystemProperties
|
||||
|
||||
internal class ResourceUnfoldTransitionConfig(private val context: Context) :
|
||||
UnfoldTransitionConfig {
|
||||
|
||||
override val isEnabled: Boolean
|
||||
get() = readIsEnabledResource() && isPropertyEnabled
|
||||
|
||||
override val isHingeAngleEnabled: Boolean
|
||||
get() = readIsHingeAngleEnabled()
|
||||
|
||||
private val isPropertyEnabled: Boolean
|
||||
get() =
|
||||
SystemProperties.getInt(
|
||||
UNFOLD_TRANSITION_MODE_PROPERTY_NAME, UNFOLD_TRANSITION_PROPERTY_ENABLED) ==
|
||||
UNFOLD_TRANSITION_PROPERTY_ENABLED
|
||||
|
||||
private fun readIsEnabledResource(): Boolean =
|
||||
context.resources.getBoolean(com.android.internal.R.bool.config_unfoldTransitionEnabled)
|
||||
|
||||
private fun readIsHingeAngleEnabled(): Boolean =
|
||||
context.resources.getBoolean(com.android.internal.R.bool.config_unfoldTransitionHingeAngle)
|
||||
}
|
||||
|
||||
/**
|
||||
* Temporary persistent property to control unfold transition mode.
|
||||
*
|
||||
* See [com.android.unfold.config.AnimationMode].
|
||||
*/
|
||||
private const val UNFOLD_TRANSITION_MODE_PROPERTY_NAME = "persist.unfold.transition_enabled"
|
||||
private const val UNFOLD_TRANSITION_PROPERTY_ENABLED = 1
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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.unfold.system
|
||||
|
||||
import android.app.ActivityManager
|
||||
import android.app.WindowConfiguration
|
||||
import com.android.systemui.unfold.util.CurrentActivityTypeProvider
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class ActivityManagerActivityTypeProvider @Inject constructor(
|
||||
private val activityManager: ActivityManager
|
||||
) : CurrentActivityTypeProvider {
|
||||
|
||||
override val isHomeActivity: Boolean?
|
||||
get() {
|
||||
val activityType = activityManager.getRunningTasks(/* maxNum= */ 1)
|
||||
?.getOrNull(0)?.topActivityType ?: return null
|
||||
|
||||
return activityType == WindowConfiguration.ACTIVITY_TYPE_HOME
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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.unfold.system
|
||||
|
||||
import android.content.Context
|
||||
import android.hardware.devicestate.DeviceStateManager
|
||||
import com.android.systemui.unfold.updates.FoldProvider
|
||||
import com.android.systemui.unfold.updates.FoldProvider.FoldCallback
|
||||
import java.util.concurrent.Executor
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class DeviceStateManagerFoldProvider @Inject constructor(
|
||||
private val deviceStateManager: DeviceStateManager,
|
||||
private val context: Context
|
||||
) : FoldProvider {
|
||||
|
||||
private val callbacks: MutableMap<FoldCallback,
|
||||
DeviceStateManager.DeviceStateCallback> = hashMapOf()
|
||||
|
||||
override fun registerCallback(callback: FoldCallback, executor: Executor) {
|
||||
val listener = FoldStateListener(context, callback)
|
||||
deviceStateManager.registerCallback(executor, listener)
|
||||
callbacks[callback] = listener
|
||||
}
|
||||
|
||||
override fun unregisterCallback(callback: FoldCallback) {
|
||||
val listener = callbacks.remove(callback)
|
||||
listener?.let {
|
||||
deviceStateManager.unregisterCallback(it)
|
||||
}
|
||||
}
|
||||
|
||||
private inner class FoldStateListener(
|
||||
context: Context,
|
||||
listener: FoldCallback
|
||||
) : DeviceStateManager.FoldStateListener(context, { listener.onFoldUpdated(it) })
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* 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.unfold.system
|
||||
|
||||
import android.os.Handler
|
||||
import com.android.systemui.dagger.qualifiers.Main
|
||||
import com.android.systemui.dagger.qualifiers.UiBackground
|
||||
import com.android.systemui.unfold.config.ResourceUnfoldTransitionConfig
|
||||
import com.android.systemui.unfold.config.UnfoldTransitionConfig
|
||||
import com.android.systemui.unfold.dagger.UnfoldBackground
|
||||
import com.android.systemui.unfold.dagger.UnfoldMain
|
||||
import com.android.systemui.unfold.updates.FoldProvider
|
||||
import com.android.systemui.unfold.util.CurrentActivityTypeProvider
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import java.util.concurrent.Executor
|
||||
|
||||
/**
|
||||
* Dagger module with system-only dependencies for the unfold animation.
|
||||
* The code that is used to calculate unfold transition progress
|
||||
* depends on some hidden APIs that are not available in normal
|
||||
* apps. In order to re-use this code and use alternative implementations
|
||||
* of these classes in other apps and hidden APIs here.
|
||||
*/
|
||||
@Module
|
||||
abstract class SystemUnfoldSharedModule {
|
||||
|
||||
@Binds
|
||||
abstract fun activityTypeProvider(executor: ActivityManagerActivityTypeProvider):
|
||||
CurrentActivityTypeProvider
|
||||
|
||||
@Binds
|
||||
abstract fun config(config: ResourceUnfoldTransitionConfig): UnfoldTransitionConfig
|
||||
|
||||
@Binds
|
||||
abstract fun foldState(provider: DeviceStateManagerFoldProvider): FoldProvider
|
||||
|
||||
@Binds
|
||||
@UnfoldMain
|
||||
abstract fun mainExecutor(@Main executor: Executor): Executor
|
||||
|
||||
@Binds
|
||||
@UnfoldMain
|
||||
abstract fun mainHandler(@Main handler: Handler): Handler
|
||||
|
||||
@Binds
|
||||
@UnfoldBackground
|
||||
abstract fun backgroundExecutor(@UiBackground executor: Executor): Executor
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package com.android.systemui.unfold.updates.hinge
|
||||
|
||||
import androidx.core.util.Consumer
|
||||
|
||||
internal object EmptyHingeAngleProvider : HingeAngleProvider {
|
||||
override fun start() {}
|
||||
|
||||
override fun stop() {}
|
||||
|
||||
override fun removeCallback(listener: Consumer<Float>) {}
|
||||
|
||||
override fun addCallback(listener: Consumer<Float>) {}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package com.android.systemui.unfold.updates.hinge
|
||||
|
||||
import androidx.core.util.Consumer
|
||||
import com.android.systemui.statusbar.policy.CallbackController
|
||||
|
||||
/**
|
||||
* Emits device hinge angle values (angle between two integral parts of the device).
|
||||
*
|
||||
* The hinge angle could be from 0 to 360 degrees inclusive. For foldable devices usually 0
|
||||
* corresponds to fully closed (folded) state and 180 degrees corresponds to fully open (flat)
|
||||
* state.
|
||||
*/
|
||||
interface HingeAngleProvider : CallbackController<Consumer<Float>> {
|
||||
fun start()
|
||||
fun stop()
|
||||
}
|
||||
|
||||
const val FULLY_OPEN_DEGREES = 180f
|
||||
const val FULLY_CLOSED_DEGREES = 0f
|
||||
@@ -1,3 +1,17 @@
|
||||
/*
|
||||
* 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.unfold.util
|
||||
|
||||
import android.content.Context
|
||||
|
||||
@@ -20,6 +20,7 @@ import android.content.Context
|
||||
import android.view.IWindowManager
|
||||
import com.android.systemui.keyguard.LifecycleScreenStatusProvider
|
||||
import com.android.systemui.unfold.config.UnfoldTransitionConfig
|
||||
import com.android.systemui.unfold.system.SystemUnfoldSharedModule
|
||||
import com.android.systemui.unfold.updates.FoldStateProvider
|
||||
import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
|
||||
import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider
|
||||
@@ -34,7 +35,7 @@ import java.util.Optional
|
||||
import javax.inject.Named
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module(includes = [UnfoldSharedModule::class])
|
||||
@Module(includes = [UnfoldSharedModule::class, SystemUnfoldSharedModule::class])
|
||||
class UnfoldTransitionModule {
|
||||
|
||||
@Provides @UnfoldTransitionATracePrefix fun tracingTagPrefix() = "systemui"
|
||||
@@ -60,11 +61,6 @@ class UnfoldTransitionModule {
|
||||
FoldStateLogger(FoldStateLoggingProvider)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideUnfoldTransitionConfig(context: Context): UnfoldTransitionConfig =
|
||||
createConfig(context)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideNaturalRotationProgressProvider(
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
/*
|
||||
* 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.shared.animation
|
||||
|
||||
import android.testing.AndroidTestingRunner
|
||||
@@ -7,31 +21,24 @@ import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction
|
||||
import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.ViewIdToTranslate
|
||||
import com.android.systemui.unfold.UnfoldTransitionProgressProvider
|
||||
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
|
||||
import com.android.systemui.unfold.TestUnfoldTransitionProvider
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.ArgumentCaptor
|
||||
import org.mockito.Captor
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.Mockito.`when` as whenever
|
||||
import org.mockito.MockitoAnnotations
|
||||
import org.mockito.Mockito.`when` as whenever
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner::class)
|
||||
class UnfoldConstantTranslateAnimatorTest : SysuiTestCase() {
|
||||
|
||||
@Mock private lateinit var progressProvider: UnfoldTransitionProgressProvider
|
||||
private val progressProvider = TestUnfoldTransitionProvider()
|
||||
|
||||
@Mock private lateinit var parent: ViewGroup
|
||||
|
||||
@Captor private lateinit var progressListenerCaptor: ArgumentCaptor<TransitionProgressListener>
|
||||
|
||||
private lateinit var animator: UnfoldConstantTranslateAnimator
|
||||
private lateinit var progressListener: TransitionProgressListener
|
||||
|
||||
private val viewsIdToRegister =
|
||||
setOf(
|
||||
@@ -46,17 +53,14 @@ class UnfoldConstantTranslateAnimatorTest : SysuiTestCase() {
|
||||
UnfoldConstantTranslateAnimator(viewsIdToRegister, progressProvider)
|
||||
|
||||
animator.init(parent, MAX_TRANSLATION)
|
||||
|
||||
verify(progressProvider).addCallback(progressListenerCaptor.capture())
|
||||
progressListener = progressListenerCaptor.value
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onTransition_noMatchingIds() {
|
||||
// GIVEN no views matching any ids
|
||||
// WHEN the transition starts
|
||||
progressListener.onTransitionStarted()
|
||||
progressListener.onTransitionProgress(.1f)
|
||||
progressProvider.onTransitionStarted()
|
||||
progressProvider.onTransitionProgress(.1f)
|
||||
|
||||
// THEN nothing... no exceptions
|
||||
}
|
||||
@@ -86,22 +90,22 @@ class UnfoldConstantTranslateAnimatorTest : SysuiTestCase() {
|
||||
// Compare values as ints because -0f != 0f
|
||||
|
||||
// WHEN the transition starts
|
||||
progressListener.onTransitionStarted()
|
||||
progressListener.onTransitionProgress(0f)
|
||||
progressProvider.onTransitionStarted()
|
||||
progressProvider.onTransitionProgress(0f)
|
||||
|
||||
list.forEach { (view, direction) ->
|
||||
assertEquals((-MAX_TRANSLATION * direction).toInt(), view.translationX.toInt())
|
||||
}
|
||||
|
||||
// WHEN the transition progresses, translation is updated
|
||||
progressListener.onTransitionProgress(.5f)
|
||||
progressProvider.onTransitionProgress(.5f)
|
||||
list.forEach { (view, direction) ->
|
||||
assertEquals((-MAX_TRANSLATION / 2f * direction).toInt(), view.translationX.toInt())
|
||||
}
|
||||
|
||||
// WHEN the transition ends, translation is completed
|
||||
progressListener.onTransitionProgress(1f)
|
||||
progressListener.onTransitionFinished()
|
||||
progressProvider.onTransitionProgress(1f)
|
||||
progressProvider.onTransitionFinished()
|
||||
list.forEach { (view, _) -> assertEquals(0, view.translationX.toInt()) }
|
||||
}
|
||||
|
||||
|
||||
@@ -136,6 +136,7 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() {
|
||||
private class UnfoldConfig : UnfoldTransitionConfig {
|
||||
override var isEnabled: Boolean = false
|
||||
override var isHingeAngleEnabled: Boolean = false
|
||||
override val halfFoldedTimeoutMillis: Int = 0
|
||||
}
|
||||
|
||||
private class TestTouchEventHandler : PhoneStatusBarView.TouchEventHandler {
|
||||
|
||||
@@ -25,28 +25,20 @@ import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_FULL_OPEN
|
||||
import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_HALF_OPEN
|
||||
import com.android.systemui.unfold.updates.FOLD_UPDATE_START_CLOSING
|
||||
import com.android.systemui.unfold.updates.FOLD_UPDATE_START_OPENING
|
||||
import com.android.systemui.unfold.updates.FoldStateProvider
|
||||
import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdate
|
||||
import com.android.systemui.unfold.util.TestFoldStateProvider
|
||||
import com.android.systemui.util.time.FakeSystemClock
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.ArgumentCaptor
|
||||
import org.mockito.Captor
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.MockitoAnnotations
|
||||
|
||||
@RunWith(AndroidTestingRunner::class)
|
||||
@SmallTest
|
||||
class FoldStateLoggingProviderTest : SysuiTestCase() {
|
||||
|
||||
@Captor
|
||||
private lateinit var foldUpdatesListener: ArgumentCaptor<FoldStateProvider.FoldUpdatesListener>
|
||||
|
||||
@Mock private lateinit var foldStateProvider: FoldStateProvider
|
||||
|
||||
private val testFoldStateProvider = TestFoldStateProvider()
|
||||
private val fakeClock = FakeSystemClock()
|
||||
|
||||
private lateinit var foldStateLoggingProvider: FoldStateLoggingProvider
|
||||
@@ -65,12 +57,10 @@ class FoldStateLoggingProviderTest : SysuiTestCase() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
|
||||
foldStateLoggingProvider =
|
||||
FoldStateLoggingProviderImpl(foldStateProvider, fakeClock).apply {
|
||||
FoldStateLoggingProviderImpl(testFoldStateProvider, fakeClock).apply {
|
||||
addCallback(foldStateLoggingListener)
|
||||
init()
|
||||
}
|
||||
|
||||
verify(foldStateProvider).addCallback(foldUpdatesListener.capture())
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -183,10 +173,10 @@ class FoldStateLoggingProviderTest : SysuiTestCase() {
|
||||
fun uninit_removesCallback() {
|
||||
foldStateLoggingProvider.uninit()
|
||||
|
||||
verify(foldStateProvider).removeCallback(foldUpdatesListener.value)
|
||||
assertThat(testFoldStateProvider.hasListeners).isFalse()
|
||||
}
|
||||
|
||||
private fun sendFoldUpdate(@FoldUpdate foldUpdate: Int) {
|
||||
foldUpdatesListener.value.onFoldUpdate(foldUpdate)
|
||||
testFoldStateProvider.sendFoldUpdate(foldUpdate)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* 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.unfold.config
|
||||
|
||||
import android.testing.AndroidTestingRunner
|
||||
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
|
||||
|
||||
|
||||
/**
|
||||
* A test that checks that we load correct resources in
|
||||
* ResourceUnfoldTransitionConfig as we use strings there instead of R constants.
|
||||
* Internal Android resource constants are not available in public APIs,
|
||||
* so we can't use them there directly.
|
||||
*/
|
||||
@RunWith(AndroidTestingRunner::class)
|
||||
@SmallTest
|
||||
class ResourceUnfoldTransitionConfigTest : SysuiTestCase() {
|
||||
|
||||
private val config = ResourceUnfoldTransitionConfig()
|
||||
|
||||
@Test
|
||||
fun testIsEnabled() {
|
||||
assertThat(config.isEnabled).isEqualTo(mContext.resources
|
||||
.getBoolean(com.android.internal.R.bool.config_unfoldTransitionEnabled))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testHingeAngleEnabled() {
|
||||
assertThat(config.isHingeAngleEnabled).isEqualTo(mContext.resources
|
||||
.getBoolean(com.android.internal.R.bool.config_unfoldTransitionHingeAngle))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testHalfFoldedTimeout() {
|
||||
assertThat(config.halfFoldedTimeoutMillis).isEqualTo(mContext.resources
|
||||
.getInteger(com.android.internal.R.integer.config_unfoldTransitionHalfFoldedTimeout))
|
||||
}
|
||||
}
|
||||
@@ -16,82 +16,69 @@
|
||||
|
||||
package com.android.systemui.unfold.updates
|
||||
|
||||
import android.app.ActivityManager
|
||||
import android.app.ActivityManager.RunningTaskInfo
|
||||
import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
|
||||
import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
|
||||
import android.app.WindowConfiguration.ActivityType
|
||||
import android.hardware.devicestate.DeviceStateManager
|
||||
import android.hardware.devicestate.DeviceStateManager.FoldStateListener
|
||||
import android.os.Handler
|
||||
import android.testing.AndroidTestingRunner
|
||||
import androidx.core.util.Consumer
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.unfold.config.ResourceUnfoldTransitionConfig
|
||||
import com.android.systemui.unfold.config.UnfoldTransitionConfig
|
||||
import com.android.systemui.unfold.system.ActivityManagerActivityTypeProvider
|
||||
import com.android.systemui.unfold.updates.FoldProvider.FoldCallback
|
||||
import com.android.systemui.unfold.updates.hinge.HingeAngleProvider
|
||||
import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
|
||||
import com.android.systemui.unfold.updates.screen.ScreenStatusProvider.ScreenListener
|
||||
import com.android.systemui.unfold.util.FoldableDeviceStates
|
||||
import com.android.systemui.unfold.util.FoldableTestUtils
|
||||
import com.android.systemui.util.mockito.any
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.ArgumentCaptor
|
||||
import org.mockito.Captor
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.Mockito.`when` as whenever
|
||||
import org.mockito.MockitoAnnotations
|
||||
import java.util.concurrent.Executor
|
||||
import org.mockito.Mockito.`when` as whenever
|
||||
|
||||
@RunWith(AndroidTestingRunner::class)
|
||||
@SmallTest
|
||||
class DeviceFoldStateProviderTest : SysuiTestCase() {
|
||||
|
||||
@Mock private lateinit var hingeAngleProvider: HingeAngleProvider
|
||||
@Mock
|
||||
private lateinit var activityTypeProvider: ActivityManagerActivityTypeProvider
|
||||
|
||||
@Mock private lateinit var screenStatusProvider: ScreenStatusProvider
|
||||
@Mock
|
||||
private lateinit var handler: Handler
|
||||
|
||||
@Mock private lateinit var deviceStateManager: DeviceStateManager
|
||||
|
||||
@Mock private lateinit var activityManager: ActivityManager
|
||||
|
||||
@Mock private lateinit var handler: Handler
|
||||
|
||||
@Captor private lateinit var foldStateListenerCaptor: ArgumentCaptor<FoldStateListener>
|
||||
|
||||
@Captor private lateinit var screenOnListenerCaptor: ArgumentCaptor<ScreenListener>
|
||||
|
||||
@Captor private lateinit var hingeAngleCaptor: ArgumentCaptor<Consumer<Float>>
|
||||
private val foldProvider = TestFoldProvider()
|
||||
private val screenOnStatusProvider = TestScreenOnStatusProvider()
|
||||
private val testHingeAngleProvider = TestHingeAngleProvider()
|
||||
|
||||
private lateinit var foldStateProvider: DeviceFoldStateProvider
|
||||
|
||||
private val foldUpdates: MutableList<Int> = arrayListOf()
|
||||
private val hingeAngleUpdates: MutableList<Float> = arrayListOf()
|
||||
|
||||
private lateinit var deviceStates: FoldableDeviceStates
|
||||
|
||||
private var scheduledRunnable: Runnable? = null
|
||||
private var scheduledRunnableDelay: Long? = null
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
overrideResource(
|
||||
com.android.internal.R.integer.config_unfoldTransitionHalfFoldedTimeout,
|
||||
HALF_OPENED_TIMEOUT_MILLIS.toInt())
|
||||
deviceStates = FoldableTestUtils.findDeviceStates(context)
|
||||
|
||||
val config = object : UnfoldTransitionConfig by ResourceUnfoldTransitionConfig() {
|
||||
override val halfFoldedTimeoutMillis: Int
|
||||
get() = HALF_OPENED_TIMEOUT_MILLIS.toInt()
|
||||
}
|
||||
|
||||
foldStateProvider =
|
||||
DeviceFoldStateProvider(
|
||||
context,
|
||||
hingeAngleProvider,
|
||||
screenStatusProvider,
|
||||
deviceStateManager,
|
||||
activityManager,
|
||||
config,
|
||||
testHingeAngleProvider,
|
||||
screenOnStatusProvider,
|
||||
foldProvider,
|
||||
activityTypeProvider,
|
||||
context.mainExecutor,
|
||||
handler)
|
||||
handler
|
||||
)
|
||||
|
||||
foldStateProvider.addCallback(
|
||||
object : FoldStateProvider.FoldUpdatesListener {
|
||||
@@ -105,10 +92,6 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
|
||||
})
|
||||
foldStateProvider.start()
|
||||
|
||||
verify(deviceStateManager).registerCallback(any(), foldStateListenerCaptor.capture())
|
||||
verify(screenStatusProvider).addCallback(screenOnListenerCaptor.capture())
|
||||
verify(hingeAngleProvider).addCallback(hingeAngleCaptor.capture())
|
||||
|
||||
whenever(handler.postDelayed(any<Runnable>(), any())).then { invocationOnMock ->
|
||||
scheduledRunnable = invocationOnMock.getArgument<Runnable>(0)
|
||||
scheduledRunnableDelay = invocationOnMock.getArgument<Long>(1)
|
||||
@@ -125,7 +108,7 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
|
||||
}
|
||||
|
||||
// By default, we're on launcher.
|
||||
setupForegroundActivityType(ACTIVITY_TYPE_HOME)
|
||||
setupForegroundActivityType(isHomeActivity = true)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -146,14 +129,14 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
|
||||
fun testOnFolded_stopsHingeAngleProvider() {
|
||||
setFoldState(folded = true)
|
||||
|
||||
verify(hingeAngleProvider).stop()
|
||||
assertThat(testHingeAngleProvider.isStarted).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnUnfolded_startsHingeAngleProvider() {
|
||||
setFoldState(folded = false)
|
||||
|
||||
verify(hingeAngleProvider).start()
|
||||
assertThat(testHingeAngleProvider.isStarted).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -310,7 +293,7 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
|
||||
|
||||
@Test
|
||||
fun startClosingEvent_whileNotOnLauncher_doesNotTriggerBeforeThreshold() {
|
||||
setupForegroundActivityType(ACTIVITY_TYPE_STANDARD)
|
||||
setupForegroundActivityType(isHomeActivity = false)
|
||||
sendHingeAngleEvent(180)
|
||||
|
||||
sendHingeAngleEvent(START_CLOSING_ON_APPS_THRESHOLD_DEGREES + 1)
|
||||
@@ -318,9 +301,29 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
|
||||
assertThat(foldUpdates).isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun startClosingEvent_whileActivityTypeNotAvailable_triggerBeforeThreshold() {
|
||||
setupForegroundActivityType(isHomeActivity = null)
|
||||
sendHingeAngleEvent(180)
|
||||
|
||||
sendHingeAngleEvent(START_CLOSING_ON_APPS_THRESHOLD_DEGREES + 1)
|
||||
|
||||
assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun startClosingEvent_whileOnLauncher_doesTriggerBeforeThreshold() {
|
||||
setupForegroundActivityType(isHomeActivity = true)
|
||||
sendHingeAngleEvent(180)
|
||||
|
||||
sendHingeAngleEvent(START_CLOSING_ON_APPS_THRESHOLD_DEGREES + 1)
|
||||
|
||||
assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun startClosingEvent_whileNotOnLauncher_triggersAfterThreshold() {
|
||||
setupForegroundActivityType(ACTIVITY_TYPE_STANDARD)
|
||||
setupForegroundActivityType(isHomeActivity = false)
|
||||
sendHingeAngleEvent(START_CLOSING_ON_APPS_THRESHOLD_DEGREES)
|
||||
|
||||
sendHingeAngleEvent(START_CLOSING_ON_APPS_THRESHOLD_DEGREES - 1)
|
||||
@@ -328,9 +331,8 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
|
||||
assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING)
|
||||
}
|
||||
|
||||
private fun setupForegroundActivityType(@ActivityType type: Int) {
|
||||
val taskInfo = RunningTaskInfo().apply { topActivityType = type }
|
||||
whenever(activityManager.getRunningTasks(1)).thenReturn(listOf(taskInfo))
|
||||
private fun setupForegroundActivityType(isHomeActivity: Boolean?) {
|
||||
whenever(activityTypeProvider.isHomeActivity).thenReturn(isHomeActivity)
|
||||
}
|
||||
|
||||
private fun simulateTimeout(waitTime: Long = HALF_OPENED_TIMEOUT_MILLIS) {
|
||||
@@ -348,16 +350,72 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
|
||||
}
|
||||
|
||||
private fun setFoldState(folded: Boolean) {
|
||||
val state = if (folded) deviceStates.folded else deviceStates.unfolded
|
||||
foldStateListenerCaptor.value.onStateChanged(state)
|
||||
foldProvider.notifyFolded(folded)
|
||||
}
|
||||
|
||||
private fun fireScreenOnEvent() {
|
||||
screenOnListenerCaptor.value.onScreenTurnedOn()
|
||||
screenOnStatusProvider.notifyScreenTurnedOn()
|
||||
}
|
||||
|
||||
private fun sendHingeAngleEvent(angle: Int) {
|
||||
hingeAngleCaptor.value.accept(angle.toFloat())
|
||||
testHingeAngleProvider.notifyAngle(angle.toFloat())
|
||||
}
|
||||
|
||||
private class TestFoldProvider : FoldProvider {
|
||||
private val callbacks = arrayListOf<FoldCallback>()
|
||||
|
||||
override fun registerCallback(callback: FoldCallback, executor: Executor) {
|
||||
callbacks += callback
|
||||
}
|
||||
|
||||
override fun unregisterCallback(callback: FoldCallback) {
|
||||
callbacks -= callback
|
||||
}
|
||||
|
||||
fun notifyFolded(isFolded: Boolean) {
|
||||
callbacks.forEach { it.onFoldUpdated(isFolded) }
|
||||
}
|
||||
}
|
||||
|
||||
private class TestScreenOnStatusProvider : ScreenStatusProvider {
|
||||
private val callbacks = arrayListOf<ScreenListener>()
|
||||
|
||||
override fun addCallback(listener: ScreenListener) {
|
||||
callbacks += listener
|
||||
}
|
||||
|
||||
override fun removeCallback(listener: ScreenListener) {
|
||||
callbacks -= listener
|
||||
}
|
||||
|
||||
fun notifyScreenTurnedOn() {
|
||||
callbacks.forEach { it.onScreenTurnedOn() }
|
||||
}
|
||||
}
|
||||
|
||||
private class TestHingeAngleProvider : HingeAngleProvider {
|
||||
private val callbacks = arrayListOf<Consumer<Float>>()
|
||||
var isStarted: Boolean = false
|
||||
|
||||
override fun start() {
|
||||
isStarted = true;
|
||||
}
|
||||
|
||||
override fun stop() {
|
||||
isStarted = false;
|
||||
}
|
||||
|
||||
override fun addCallback(listener: Consumer<Float>) {
|
||||
callbacks += listener
|
||||
}
|
||||
|
||||
override fun removeCallback(listener: Consumer<Float>) {
|
||||
callbacks -= listener
|
||||
}
|
||||
|
||||
fun notifyAngle(angle: Float) {
|
||||
callbacks.forEach { it.accept(angle) }
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -20,18 +20,18 @@ import android.view.IRotationWatcher
|
||||
import android.view.IWindowManager
|
||||
import android.view.Surface
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.unfold.UnfoldTransitionProgressProvider
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.unfold.TestUnfoldTransitionProvider
|
||||
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
|
||||
import com.android.systemui.util.mockito.any
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.ArgumentCaptor
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.Mockito.clearInvocations
|
||||
import org.mockito.Mockito.never
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.MockitoAnnotations
|
||||
|
||||
@RunWith(AndroidTestingRunner::class)
|
||||
@@ -41,16 +41,13 @@ class NaturalRotationUnfoldProgressProviderTest : SysuiTestCase() {
|
||||
@Mock
|
||||
lateinit var windowManager: IWindowManager
|
||||
|
||||
@Mock
|
||||
lateinit var sourceProvider: UnfoldTransitionProgressProvider
|
||||
private val sourceProvider = TestUnfoldTransitionProvider()
|
||||
|
||||
@Mock
|
||||
lateinit var transitionListener: TransitionProgressListener
|
||||
|
||||
lateinit var progressProvider: NaturalRotationUnfoldProgressProvider
|
||||
|
||||
private val sourceProviderListenerCaptor =
|
||||
ArgumentCaptor.forClass(TransitionProgressListener::class.java)
|
||||
private val rotationWatcherCaptor =
|
||||
ArgumentCaptor.forClass(IRotationWatcher.Stub::class.java)
|
||||
|
||||
@@ -66,7 +63,6 @@ class NaturalRotationUnfoldProgressProviderTest : SysuiTestCase() {
|
||||
|
||||
progressProvider.init()
|
||||
|
||||
verify(sourceProvider).addCallback(sourceProviderListenerCaptor.capture())
|
||||
verify(windowManager).watchRotation(rotationWatcherCaptor.capture(), any())
|
||||
|
||||
progressProvider.addCallback(transitionListener)
|
||||
@@ -76,7 +72,7 @@ class NaturalRotationUnfoldProgressProviderTest : SysuiTestCase() {
|
||||
fun testNaturalRotation0_sendTransitionStartedEvent_eventReceived() {
|
||||
onRotationChanged(Surface.ROTATION_0)
|
||||
|
||||
source.onTransitionStarted()
|
||||
sourceProvider.onTransitionStarted()
|
||||
|
||||
verify(transitionListener).onTransitionStarted()
|
||||
}
|
||||
@@ -85,7 +81,7 @@ class NaturalRotationUnfoldProgressProviderTest : SysuiTestCase() {
|
||||
fun testNaturalRotation0_sendTransitionProgressEvent_eventReceived() {
|
||||
onRotationChanged(Surface.ROTATION_0)
|
||||
|
||||
source.onTransitionProgress(0.5f)
|
||||
sourceProvider.onTransitionProgress(0.5f)
|
||||
|
||||
verify(transitionListener).onTransitionProgress(0.5f)
|
||||
}
|
||||
@@ -94,7 +90,7 @@ class NaturalRotationUnfoldProgressProviderTest : SysuiTestCase() {
|
||||
fun testNotNaturalRotation90_sendTransitionStartedEvent_eventNotReceived() {
|
||||
onRotationChanged(Surface.ROTATION_90)
|
||||
|
||||
source.onTransitionStarted()
|
||||
sourceProvider.onTransitionStarted()
|
||||
|
||||
verify(transitionListener, never()).onTransitionStarted()
|
||||
}
|
||||
@@ -103,7 +99,7 @@ class NaturalRotationUnfoldProgressProviderTest : SysuiTestCase() {
|
||||
fun testNaturalRotation90_sendTransitionProgressEvent_eventNotReceived() {
|
||||
onRotationChanged(Surface.ROTATION_90)
|
||||
|
||||
source.onTransitionProgress(0.5f)
|
||||
sourceProvider.onTransitionProgress(0.5f)
|
||||
|
||||
verify(transitionListener, never()).onTransitionProgress(0.5f)
|
||||
}
|
||||
@@ -111,7 +107,7 @@ class NaturalRotationUnfoldProgressProviderTest : SysuiTestCase() {
|
||||
@Test
|
||||
fun testRotationBecameUnnaturalDuringTransition_sendsTransitionFinishedEvent() {
|
||||
onRotationChanged(Surface.ROTATION_0)
|
||||
source.onTransitionStarted()
|
||||
sourceProvider.onTransitionStarted()
|
||||
clearInvocations(transitionListener)
|
||||
|
||||
onRotationChanged(Surface.ROTATION_90)
|
||||
@@ -122,7 +118,7 @@ class NaturalRotationUnfoldProgressProviderTest : SysuiTestCase() {
|
||||
@Test
|
||||
fun testRotationBecameNaturalDuringTransition_sendsTransitionStartedEvent() {
|
||||
onRotationChanged(Surface.ROTATION_90)
|
||||
source.onTransitionStarted()
|
||||
sourceProvider.onTransitionStarted()
|
||||
clearInvocations(transitionListener)
|
||||
|
||||
onRotationChanged(Surface.ROTATION_0)
|
||||
@@ -133,7 +129,4 @@ class NaturalRotationUnfoldProgressProviderTest : SysuiTestCase() {
|
||||
private fun onRotationChanged(rotation: Int) {
|
||||
rotationWatcherCaptor.value.onRotationChanged(rotation)
|
||||
}
|
||||
|
||||
private val source: TransitionProgressListener
|
||||
get() = sourceProviderListenerCaptor.value
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import android.database.ContentObserver
|
||||
import android.testing.AndroidTestingRunner
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.unfold.TestUnfoldTransitionProvider
|
||||
import com.android.systemui.unfold.UnfoldTransitionProgressProvider
|
||||
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
|
||||
import com.android.systemui.util.mockito.any
|
||||
@@ -40,16 +41,12 @@ class ScaleAwareUnfoldProgressProviderTest : SysuiTestCase() {
|
||||
@Mock
|
||||
lateinit var contentResolver: ContentResolver
|
||||
|
||||
@Mock
|
||||
lateinit var sourceProvider: UnfoldTransitionProgressProvider
|
||||
|
||||
@Mock
|
||||
lateinit var sinkProvider: TransitionProgressListener
|
||||
|
||||
lateinit var progressProvider: ScaleAwareTransitionProgressProvider
|
||||
private val sourceProvider = TestUnfoldTransitionProvider()
|
||||
|
||||
private val sourceProviderListenerCaptor =
|
||||
ArgumentCaptor.forClass(TransitionProgressListener::class.java)
|
||||
lateinit var progressProvider: ScaleAwareTransitionProgressProvider
|
||||
|
||||
private val animatorDurationScaleListenerCaptor =
|
||||
ArgumentCaptor.forClass(ContentObserver::class.java)
|
||||
@@ -63,7 +60,6 @@ class ScaleAwareUnfoldProgressProviderTest : SysuiTestCase() {
|
||||
contentResolver
|
||||
)
|
||||
|
||||
verify(sourceProvider).addCallback(sourceProviderListenerCaptor.capture())
|
||||
verify(contentResolver).registerContentObserver(any(), any(),
|
||||
animatorDurationScaleListenerCaptor.capture())
|
||||
|
||||
@@ -74,7 +70,7 @@ class ScaleAwareUnfoldProgressProviderTest : SysuiTestCase() {
|
||||
fun onTransitionStarted_animationsEnabled_eventReceived() {
|
||||
setAnimationsEnabled(true)
|
||||
|
||||
source.onTransitionStarted()
|
||||
sourceProvider.onTransitionStarted()
|
||||
|
||||
verify(sinkProvider).onTransitionStarted()
|
||||
}
|
||||
@@ -83,7 +79,7 @@ class ScaleAwareUnfoldProgressProviderTest : SysuiTestCase() {
|
||||
fun onTransitionStarted_animationsNotEnabled_eventNotReceived() {
|
||||
setAnimationsEnabled(false)
|
||||
|
||||
source.onTransitionStarted()
|
||||
sourceProvider.onTransitionStarted()
|
||||
|
||||
verifyNoMoreInteractions(sinkProvider)
|
||||
}
|
||||
@@ -92,7 +88,7 @@ class ScaleAwareUnfoldProgressProviderTest : SysuiTestCase() {
|
||||
fun onTransitionEnd_animationsEnabled_eventReceived() {
|
||||
setAnimationsEnabled(true)
|
||||
|
||||
source.onTransitionFinished()
|
||||
sourceProvider.onTransitionFinished()
|
||||
|
||||
verify(sinkProvider).onTransitionFinished()
|
||||
}
|
||||
@@ -101,7 +97,7 @@ class ScaleAwareUnfoldProgressProviderTest : SysuiTestCase() {
|
||||
fun onTransitionEnd_animationsNotEnabled_eventNotReceived() {
|
||||
setAnimationsEnabled(false)
|
||||
|
||||
source.onTransitionFinished()
|
||||
sourceProvider.onTransitionFinished()
|
||||
|
||||
verifyNoMoreInteractions(sinkProvider)
|
||||
}
|
||||
@@ -110,7 +106,7 @@ class ScaleAwareUnfoldProgressProviderTest : SysuiTestCase() {
|
||||
fun onTransitionProgress_animationsEnabled_eventReceived() {
|
||||
setAnimationsEnabled(true)
|
||||
|
||||
source.onTransitionProgress(42f)
|
||||
sourceProvider.onTransitionProgress(42f)
|
||||
|
||||
verify(sinkProvider).onTransitionProgress(42f)
|
||||
}
|
||||
@@ -119,7 +115,7 @@ class ScaleAwareUnfoldProgressProviderTest : SysuiTestCase() {
|
||||
fun onTransitionProgress_animationsNotEnabled_eventNotReceived() {
|
||||
setAnimationsEnabled(false)
|
||||
|
||||
source.onTransitionProgress(42f)
|
||||
sourceProvider.onTransitionProgress(42f)
|
||||
|
||||
verifyNoMoreInteractions(sinkProvider)
|
||||
}
|
||||
@@ -133,7 +129,4 @@ class ScaleAwareUnfoldProgressProviderTest : SysuiTestCase() {
|
||||
ValueAnimator.setDurationScale(durationScale)
|
||||
animatorDurationScaleListenerCaptor.value.dispatchChange(/* selfChange= */false)
|
||||
}
|
||||
|
||||
private val source: TransitionProgressListener
|
||||
get() = sourceProviderListenerCaptor.value
|
||||
}
|
||||
|
||||
@@ -24,6 +24,8 @@ import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdatesListener
|
||||
class TestFoldStateProvider : FoldStateProvider {
|
||||
|
||||
private val listeners: MutableList<FoldUpdatesListener> = arrayListOf()
|
||||
val hasListeners: Boolean
|
||||
get() = listeners.isNotEmpty()
|
||||
|
||||
override fun start() {
|
||||
}
|
||||
|
||||
39
packages/SystemUI/unfold/Android.bp
Normal file
39
packages/SystemUI/unfold/Android.bp
Normal file
@@ -0,0 +1,39 @@
|
||||
// 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 {
|
||||
// See: http://go/android-license-faq
|
||||
// A large-scale-change added 'default_applicable_licenses' to import
|
||||
// all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
|
||||
// to get the below license kinds:
|
||||
// SPDX-license-identifier-Apache-2.0
|
||||
default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
|
||||
}
|
||||
|
||||
android_library {
|
||||
name: "SystemUIUnfoldLib",
|
||||
srcs: [
|
||||
"src/**/*.java",
|
||||
"src/**/*.kt",
|
||||
"src/**/*.aidl",
|
||||
],
|
||||
static_libs: [
|
||||
"androidx.dynamicanimation_dynamicanimation",
|
||||
"dagger2",
|
||||
"jsr330",
|
||||
],
|
||||
java_version: "1.8",
|
||||
min_sdk_version: "current",
|
||||
plugins: ["dagger2-compiler"],
|
||||
}
|
||||
22
packages/SystemUI/unfold/AndroidManifest.xml
Normal file
22
packages/SystemUI/unfold/AndroidManifest.xml
Normal file
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2017 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.
|
||||
-->
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.android.systemui.unfold">
|
||||
|
||||
|
||||
</manifest>
|
||||
3
packages/SystemUI/unfold/lint-baseline.xml
Normal file
3
packages/SystemUI/unfold/lint-baseline.xml
Normal file
@@ -0,0 +1,3 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<issues format="6" by="lint 7.1.0-dev" type="baseline" client="" name="" variant="all" version="7.1.0-dev">
|
||||
</issues>
|
||||
@@ -16,16 +16,16 @@
|
||||
|
||||
package com.android.systemui.unfold
|
||||
|
||||
import android.app.ActivityManager
|
||||
import android.content.ContentResolver
|
||||
import android.content.Context
|
||||
import android.hardware.SensorManager
|
||||
import android.hardware.devicestate.DeviceStateManager
|
||||
import android.os.Handler
|
||||
import com.android.systemui.dagger.qualifiers.Main
|
||||
import com.android.systemui.dagger.qualifiers.UiBackground
|
||||
import com.android.systemui.unfold.config.UnfoldTransitionConfig
|
||||
import com.android.systemui.unfold.dagger.UnfoldBackground
|
||||
import com.android.systemui.unfold.dagger.UnfoldMain
|
||||
import com.android.systemui.unfold.updates.FoldProvider
|
||||
import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
|
||||
import com.android.systemui.unfold.util.CurrentActivityTypeProvider
|
||||
import com.android.systemui.unfold.util.UnfoldTransitionATracePrefix
|
||||
import dagger.BindsInstance
|
||||
import dagger.Component
|
||||
@@ -51,12 +51,12 @@ internal interface UnfoldSharedComponent {
|
||||
@BindsInstance context: Context,
|
||||
@BindsInstance config: UnfoldTransitionConfig,
|
||||
@BindsInstance screenStatusProvider: ScreenStatusProvider,
|
||||
@BindsInstance deviceStateManager: DeviceStateManager,
|
||||
@BindsInstance activityManager: ActivityManager,
|
||||
@BindsInstance foldProvider: FoldProvider,
|
||||
@BindsInstance activityTypeProvider: CurrentActivityTypeProvider,
|
||||
@BindsInstance sensorManager: SensorManager,
|
||||
@BindsInstance @Main handler: Handler,
|
||||
@BindsInstance @Main executor: Executor,
|
||||
@BindsInstance @UiBackground backgroundExecutor: Executor,
|
||||
@BindsInstance @UnfoldMain handler: Handler,
|
||||
@BindsInstance @UnfoldMain executor: Executor,
|
||||
@BindsInstance @UnfoldBackground backgroundExecutor: Executor,
|
||||
@BindsInstance @UnfoldTransitionATracePrefix tracingTagPrefix: String,
|
||||
@BindsInstance contentResolver: ContentResolver = context.contentResolver
|
||||
): UnfoldSharedComponent
|
||||
@@ -17,8 +17,8 @@
|
||||
package com.android.systemui.unfold
|
||||
|
||||
import android.hardware.SensorManager
|
||||
import com.android.systemui.dagger.qualifiers.UiBackground
|
||||
import com.android.systemui.unfold.config.UnfoldTransitionConfig
|
||||
import com.android.systemui.unfold.dagger.UnfoldBackground
|
||||
import com.android.systemui.unfold.progress.FixedTimingTransitionProgressProvider
|
||||
import com.android.systemui.unfold.progress.PhysicsBasedUnfoldTransitionProgressProvider
|
||||
import com.android.systemui.unfold.updates.DeviceFoldStateProvider
|
||||
@@ -70,7 +70,7 @@ class UnfoldSharedModule {
|
||||
fun hingeAngleProvider(
|
||||
config: UnfoldTransitionConfig,
|
||||
sensorManager: SensorManager,
|
||||
@UiBackground executor: Executor
|
||||
@UnfoldBackground executor: Executor
|
||||
): HingeAngleProvider =
|
||||
if (config.isHingeAngleEnabled) {
|
||||
HingeSensorAngleProvider(sensorManager, executor)
|
||||
@@ -17,14 +17,13 @@
|
||||
|
||||
package com.android.systemui.unfold
|
||||
|
||||
import android.app.ActivityManager
|
||||
import android.content.Context
|
||||
import android.hardware.SensorManager
|
||||
import android.hardware.devicestate.DeviceStateManager
|
||||
import android.os.Handler
|
||||
import com.android.systemui.unfold.config.ResourceUnfoldTransitionConfig
|
||||
import com.android.systemui.unfold.config.UnfoldTransitionConfig
|
||||
import com.android.systemui.unfold.updates.FoldProvider
|
||||
import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
|
||||
import com.android.systemui.unfold.util.CurrentActivityTypeProvider
|
||||
import java.util.concurrent.Executor
|
||||
|
||||
/**
|
||||
@@ -39,8 +38,8 @@ fun createUnfoldTransitionProgressProvider(
|
||||
context: Context,
|
||||
config: UnfoldTransitionConfig,
|
||||
screenStatusProvider: ScreenStatusProvider,
|
||||
deviceStateManager: DeviceStateManager,
|
||||
activityManager: ActivityManager,
|
||||
foldProvider: FoldProvider,
|
||||
activityTypeProvider: CurrentActivityTypeProvider,
|
||||
sensorManager: SensorManager,
|
||||
mainHandler: Handler,
|
||||
mainExecutor: Executor,
|
||||
@@ -52,8 +51,8 @@ fun createUnfoldTransitionProgressProvider(
|
||||
context,
|
||||
config,
|
||||
screenStatusProvider,
|
||||
deviceStateManager,
|
||||
activityManager,
|
||||
foldProvider,
|
||||
activityTypeProvider,
|
||||
sensorManager,
|
||||
mainHandler,
|
||||
mainExecutor,
|
||||
@@ -64,5 +63,3 @@ fun createUnfoldTransitionProgressProvider(
|
||||
?: throw IllegalStateException(
|
||||
"Trying to create " +
|
||||
"UnfoldTransitionProgressProvider when the transition is disabled")
|
||||
|
||||
fun createConfig(context: Context): UnfoldTransitionConfig = ResourceUnfoldTransitionConfig(context)
|
||||
@@ -15,9 +15,9 @@
|
||||
*/
|
||||
package com.android.systemui.unfold
|
||||
|
||||
import android.annotation.FloatRange
|
||||
import com.android.systemui.statusbar.policy.CallbackController
|
||||
import androidx.annotation.FloatRange
|
||||
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
|
||||
import com.android.systemui.unfold.util.CallbackController
|
||||
|
||||
/**
|
||||
* Interface that allows to receive unfold transition progress updates.
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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.unfold.compat
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
import com.android.systemui.unfold.updates.FoldProvider
|
||||
import com.android.systemui.unfold.updates.FoldProvider.FoldCallback
|
||||
import java.util.concurrent.Executor
|
||||
|
||||
/**
|
||||
* Fold provider that notifies about fold state based on the screen size
|
||||
* It could be used when no activity context is available
|
||||
* TODO(b/232369816): use Jetpack WM library when non-activity contexts supported b/169740873
|
||||
*/
|
||||
class ScreenSizeFoldProvider(private val context: Context) : FoldProvider {
|
||||
|
||||
private var callbacks: MutableList<FoldCallback> = arrayListOf()
|
||||
private var lastWidth: Int = 0
|
||||
|
||||
override fun registerCallback(callback: FoldCallback, executor: Executor) {
|
||||
callbacks += callback
|
||||
onConfigurationChange(context.resources.configuration)
|
||||
}
|
||||
|
||||
override fun unregisterCallback(callback: FoldCallback) {
|
||||
callbacks -= callback
|
||||
}
|
||||
|
||||
fun onConfigurationChange(newConfig: Configuration) {
|
||||
if (lastWidth == newConfig.smallestScreenWidthDp) {
|
||||
return
|
||||
}
|
||||
|
||||
if (newConfig.smallestScreenWidthDp > INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP) {
|
||||
callbacks.forEach { it.onFoldUpdated(false) }
|
||||
} else {
|
||||
callbacks.forEach { it.onFoldUpdated(true) }
|
||||
}
|
||||
lastWidth = newConfig.smallestScreenWidthDp
|
||||
}
|
||||
}
|
||||
|
||||
private const val INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP = 600
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* 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.unfold.compat
|
||||
|
||||
import com.android.systemui.unfold.updates.FoldProvider
|
||||
import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
|
||||
import com.android.systemui.unfold.updates.screen.ScreenStatusProvider.ScreenListener
|
||||
import java.util.concurrent.Executor
|
||||
|
||||
class SizeScreenStatusProvider(
|
||||
private val foldProvider: FoldProvider,
|
||||
private val executor: Executor
|
||||
) : ScreenStatusProvider {
|
||||
|
||||
private val listeners: MutableList<ScreenListener> = arrayListOf()
|
||||
private val callback = object : FoldProvider.FoldCallback {
|
||||
override fun onFoldUpdated(isFolded: Boolean) {
|
||||
if (!isFolded) {
|
||||
listeners.forEach { it.onScreenTurnedOn() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun start() {
|
||||
foldProvider.registerCallback(
|
||||
callback,
|
||||
executor
|
||||
)
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
foldProvider.unregisterCallback(callback)
|
||||
}
|
||||
|
||||
override fun addCallback(listener: ScreenListener) {
|
||||
listeners.add(listener)
|
||||
}
|
||||
|
||||
override fun removeCallback(listener: ScreenListener) {
|
||||
listeners.remove(listener)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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.unfold.config
|
||||
|
||||
import android.content.res.Resources
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class ResourceUnfoldTransitionConfig @Inject constructor() : UnfoldTransitionConfig {
|
||||
|
||||
override val isEnabled: Boolean by lazy {
|
||||
val id = Resources.getSystem()
|
||||
.getIdentifier("config_unfoldTransitionEnabled", "bool", "android")
|
||||
Resources.getSystem().getBoolean(id)
|
||||
}
|
||||
|
||||
override val isHingeAngleEnabled: Boolean by lazy {
|
||||
val id = Resources.getSystem()
|
||||
.getIdentifier("config_unfoldTransitionHingeAngle", "bool", "android")
|
||||
Resources.getSystem().getBoolean(id)
|
||||
}
|
||||
|
||||
override val halfFoldedTimeoutMillis: Int by lazy {
|
||||
val id = Resources.getSystem()
|
||||
.getIdentifier("config_unfoldTransitionHalfFoldedTimeout", "integer", "android")
|
||||
Resources.getSystem().getInteger(id)
|
||||
}
|
||||
}
|
||||
@@ -18,4 +18,5 @@ package com.android.systemui.unfold.config
|
||||
interface UnfoldTransitionConfig {
|
||||
val isEnabled: Boolean
|
||||
val isHingeAngleEnabled: Boolean
|
||||
val halfFoldedTimeoutMillis: Int
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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.unfold.dagger
|
||||
|
||||
import javax.inject.Qualifier
|
||||
|
||||
/**
|
||||
* Alternative to [UiBackground] qualifier annotation in unfold module.
|
||||
* It is needed as we can't depend on SystemUI code in this module.
|
||||
*/
|
||||
@Qualifier
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class UnfoldBackground
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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.unfold.dagger
|
||||
|
||||
import javax.inject.Qualifier
|
||||
|
||||
/**
|
||||
* Alternative to [Main] qualifier annotation in unfold module.
|
||||
* It is needed as we can't depend on SystemUI code in this module.
|
||||
*/
|
||||
@Qualifier
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class UnfoldMain
|
||||
@@ -16,7 +16,6 @@
|
||||
package com.android.systemui.unfold.progress
|
||||
|
||||
import android.util.Log
|
||||
import android.util.MathUtils.saturate
|
||||
import androidx.dynamicanimation.animation.DynamicAnimation
|
||||
import androidx.dynamicanimation.animation.FloatPropertyCompat
|
||||
import androidx.dynamicanimation.animation.SpringAnimation
|
||||
@@ -70,6 +69,9 @@ class PhysicsBasedUnfoldTransitionProgressProvider(
|
||||
springAnimation.animateToFinalPosition(progress)
|
||||
}
|
||||
|
||||
private fun saturate(amount: Float, low: Float = 0f, high: Float = 1f): Float =
|
||||
if (amount < low) low else if (amount > high) high else amount
|
||||
|
||||
override fun onFoldUpdate(@FoldUpdate update: Int) {
|
||||
when (update) {
|
||||
FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE -> {
|
||||
@@ -15,46 +15,46 @@
|
||||
*/
|
||||
package com.android.systemui.unfold.updates
|
||||
|
||||
import android.annotation.FloatRange
|
||||
import android.app.ActivityManager
|
||||
import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
|
||||
import android.content.Context
|
||||
import android.hardware.devicestate.DeviceStateManager
|
||||
import android.os.Handler
|
||||
import android.util.Log
|
||||
import androidx.annotation.FloatRange
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.core.util.Consumer
|
||||
import com.android.systemui.dagger.qualifiers.Main
|
||||
import com.android.systemui.unfold.config.UnfoldTransitionConfig
|
||||
import com.android.systemui.unfold.dagger.UnfoldMain
|
||||
import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdate
|
||||
import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdatesListener
|
||||
import com.android.systemui.unfold.updates.hinge.FULLY_CLOSED_DEGREES
|
||||
import com.android.systemui.unfold.updates.hinge.FULLY_OPEN_DEGREES
|
||||
import com.android.systemui.unfold.updates.hinge.HingeAngleProvider
|
||||
import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
|
||||
import com.android.systemui.unfold.util.CurrentActivityTypeProvider
|
||||
import java.util.concurrent.Executor
|
||||
import javax.inject.Inject
|
||||
|
||||
class DeviceFoldStateProvider
|
||||
@Inject
|
||||
constructor(
|
||||
context: Context,
|
||||
config: UnfoldTransitionConfig,
|
||||
private val hingeAngleProvider: HingeAngleProvider,
|
||||
private val screenStatusProvider: ScreenStatusProvider,
|
||||
private val deviceStateManager: DeviceStateManager,
|
||||
private val activityManager: ActivityManager,
|
||||
@Main private val mainExecutor: Executor,
|
||||
@Main private val handler: Handler
|
||||
private val foldProvider: FoldProvider,
|
||||
private val activityTypeProvider: CurrentActivityTypeProvider,
|
||||
@UnfoldMain private val mainExecutor: Executor,
|
||||
@UnfoldMain private val handler: Handler
|
||||
) : FoldStateProvider {
|
||||
|
||||
private val outputListeners: MutableList<FoldUpdatesListener> = mutableListOf()
|
||||
|
||||
@FoldUpdate private var lastFoldUpdate: Int? = null
|
||||
@FoldUpdate
|
||||
private var lastFoldUpdate: Int? = null
|
||||
|
||||
@FloatRange(from = 0.0, to = 180.0) private var lastHingeAngle: Float = 0f
|
||||
@FloatRange(from = 0.0, to = 180.0)
|
||||
private var lastHingeAngle: Float = 0f
|
||||
|
||||
private val hingeAngleListener = HingeAngleListener()
|
||||
private val screenListener = ScreenStatusListener()
|
||||
private val foldStateListener = FoldStateListener(context)
|
||||
private val foldStateListener = FoldStateListener()
|
||||
private val timeoutRunnable = TimeoutRunnable()
|
||||
|
||||
/**
|
||||
@@ -62,22 +62,20 @@ constructor(
|
||||
* [FOLD_UPDATE_START_CLOSING] or [FOLD_UPDATE_START_OPENING] event, if an end state is not
|
||||
* reached.
|
||||
*/
|
||||
private val halfOpenedTimeoutMillis: Int =
|
||||
context.resources.getInteger(
|
||||
com.android.internal.R.integer.config_unfoldTransitionHalfFoldedTimeout)
|
||||
private val halfOpenedTimeoutMillis: Int = config.halfFoldedTimeoutMillis
|
||||
|
||||
private var isFolded = false
|
||||
private var isUnfoldHandled = true
|
||||
|
||||
override fun start() {
|
||||
deviceStateManager.registerCallback(mainExecutor, foldStateListener)
|
||||
foldProvider.registerCallback(foldStateListener, mainExecutor)
|
||||
screenStatusProvider.addCallback(screenListener)
|
||||
hingeAngleProvider.addCallback(hingeAngleListener)
|
||||
}
|
||||
|
||||
override fun stop() {
|
||||
screenStatusProvider.removeCallback(screenListener)
|
||||
deviceStateManager.unregisterCallback(foldStateListener)
|
||||
foldProvider.unregisterCallback(foldStateListener)
|
||||
hingeAngleProvider.removeCallback(hingeAngleListener)
|
||||
hingeAngleProvider.stop()
|
||||
}
|
||||
@@ -92,13 +90,13 @@ constructor(
|
||||
|
||||
override val isFinishedOpening: Boolean
|
||||
get() = !isFolded &&
|
||||
(lastFoldUpdate == FOLD_UPDATE_FINISH_FULL_OPEN ||
|
||||
lastFoldUpdate == FOLD_UPDATE_FINISH_HALF_OPEN)
|
||||
(lastFoldUpdate == FOLD_UPDATE_FINISH_FULL_OPEN ||
|
||||
lastFoldUpdate == FOLD_UPDATE_FINISH_HALF_OPEN)
|
||||
|
||||
private val isTransitionInProgress: Boolean
|
||||
get() =
|
||||
lastFoldUpdate == FOLD_UPDATE_START_OPENING ||
|
||||
lastFoldUpdate == FOLD_UPDATE_START_CLOSING
|
||||
lastFoldUpdate == FOLD_UPDATE_START_CLOSING
|
||||
|
||||
private fun onHingeAngle(angle: Float) {
|
||||
if (DEBUG) {
|
||||
@@ -136,39 +134,36 @@ constructor(
|
||||
* apps that support table-top/HALF_FOLDED mode. Only for launcher, there is no threshold.
|
||||
*/
|
||||
private fun getClosingThreshold(): Int? {
|
||||
val activityType =
|
||||
activityManager.getRunningTasks(/* maxNum= */ 1)?.getOrNull(0)?.topActivityType
|
||||
?: return null
|
||||
val isHomeActivity = activityTypeProvider.isHomeActivity ?: return null
|
||||
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "activityType=" + activityType)
|
||||
Log.d(TAG, "isHomeActivity=$isHomeActivity")
|
||||
}
|
||||
|
||||
return if (activityType == ACTIVITY_TYPE_HOME) {
|
||||
return if (isHomeActivity) {
|
||||
null
|
||||
} else {
|
||||
START_CLOSING_ON_APPS_THRESHOLD_DEGREES
|
||||
}
|
||||
}
|
||||
|
||||
private inner class FoldStateListener(context: Context) :
|
||||
DeviceStateManager.FoldStateListener(
|
||||
context,
|
||||
{ folded: Boolean ->
|
||||
isFolded = folded
|
||||
lastHingeAngle = FULLY_CLOSED_DEGREES
|
||||
private inner class FoldStateListener : FoldProvider.FoldCallback {
|
||||
override fun onFoldUpdated(isFolded: Boolean) {
|
||||
this@DeviceFoldStateProvider.isFolded = isFolded
|
||||
lastHingeAngle = FULLY_CLOSED_DEGREES
|
||||
|
||||
if (folded) {
|
||||
hingeAngleProvider.stop()
|
||||
notifyFoldUpdate(FOLD_UPDATE_FINISH_CLOSED)
|
||||
cancelTimeout()
|
||||
isUnfoldHandled = false
|
||||
} else {
|
||||
notifyFoldUpdate(FOLD_UPDATE_START_OPENING)
|
||||
rescheduleAbortAnimationTimeout()
|
||||
hingeAngleProvider.start()
|
||||
}
|
||||
})
|
||||
if (isFolded) {
|
||||
hingeAngleProvider.stop()
|
||||
notifyFoldUpdate(FOLD_UPDATE_FINISH_CLOSED)
|
||||
cancelTimeout()
|
||||
isUnfoldHandled = false
|
||||
} else {
|
||||
notifyFoldUpdate(FOLD_UPDATE_START_OPENING)
|
||||
rescheduleAbortAnimationTimeout()
|
||||
hingeAngleProvider.start()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun notifyFoldUpdate(@FoldUpdate update: Int) {
|
||||
if (DEBUG) {
|
||||
@@ -234,7 +229,9 @@ private const val TAG = "DeviceFoldProvider"
|
||||
private const val DEBUG = false
|
||||
|
||||
/** Threshold after which we consider the device fully unfolded. */
|
||||
@VisibleForTesting const val FULLY_OPEN_THRESHOLD_DEGREES = 15f
|
||||
@VisibleForTesting
|
||||
const val FULLY_OPEN_THRESHOLD_DEGREES = 15f
|
||||
|
||||
/** Fold animation on top of apps only when the angle exceeds this threshold. */
|
||||
@VisibleForTesting const val START_CLOSING_ON_APPS_THRESHOLD_DEGREES = 60
|
||||
@VisibleForTesting
|
||||
const val START_CLOSING_ON_APPS_THRESHOLD_DEGREES = 60
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* 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.unfold.updates
|
||||
|
||||
import java.util.concurrent.Executor
|
||||
|
||||
interface FoldProvider {
|
||||
fun registerCallback(callback: FoldCallback, executor: Executor)
|
||||
fun unregisterCallback(callback: FoldCallback)
|
||||
|
||||
interface FoldCallback {
|
||||
fun onFoldUpdated(isFolded: Boolean)
|
||||
}
|
||||
}
|
||||
@@ -15,10 +15,10 @@
|
||||
*/
|
||||
package com.android.systemui.unfold.updates
|
||||
|
||||
import android.annotation.FloatRange
|
||||
import android.annotation.IntDef
|
||||
import com.android.systemui.statusbar.policy.CallbackController
|
||||
import androidx.annotation.FloatRange
|
||||
import androidx.annotation.IntDef
|
||||
import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdatesListener
|
||||
import com.android.systemui.unfold.util.CallbackController
|
||||
|
||||
/**
|
||||
* Allows to subscribe to main events related to fold/unfold process such as hinge angle update,
|
||||
@@ -36,7 +36,6 @@ interface FoldStateProvider : CallbackController<FoldUpdatesListener> {
|
||||
}
|
||||
|
||||
@IntDef(
|
||||
prefix = ["FOLD_UPDATE_"],
|
||||
value =
|
||||
[
|
||||
FOLD_UPDATE_START_OPENING,
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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.unfold.updates.hinge
|
||||
|
||||
import androidx.core.util.Consumer
|
||||
|
||||
internal object EmptyHingeAngleProvider : HingeAngleProvider {
|
||||
override fun start() {}
|
||||
|
||||
override fun stop() {}
|
||||
|
||||
override fun removeCallback(listener: Consumer<Float>) {}
|
||||
|
||||
override fun addCallback(listener: Consumer<Float>) {}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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.unfold.updates.hinge
|
||||
|
||||
import androidx.core.util.Consumer
|
||||
import com.android.systemui.unfold.util.CallbackController
|
||||
|
||||
/**
|
||||
* Emits device hinge angle values (angle between two integral parts of the device).
|
||||
*
|
||||
* The hinge angle could be from 0 to 360 degrees inclusive. For foldable devices usually 0
|
||||
* corresponds to fully closed (folded) state and 180 degrees corresponds to fully open (flat)
|
||||
* state.
|
||||
*/
|
||||
interface HingeAngleProvider : CallbackController<Consumer<Float>> {
|
||||
fun start()
|
||||
fun stop()
|
||||
}
|
||||
|
||||
const val FULLY_OPEN_DEGREES = 180f
|
||||
const val FULLY_CLOSED_DEGREES = 0f
|
||||
@@ -1,3 +1,17 @@
|
||||
/*
|
||||
* 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.unfold.updates.hinge
|
||||
|
||||
import android.hardware.Sensor
|
||||
@@ -15,8 +15,8 @@
|
||||
*/
|
||||
package com.android.systemui.unfold.updates.screen
|
||||
|
||||
import com.android.systemui.statusbar.policy.CallbackController
|
||||
import com.android.systemui.unfold.updates.screen.ScreenStatusProvider.ScreenListener
|
||||
import com.android.systemui.unfold.util.CallbackController
|
||||
|
||||
interface ScreenStatusProvider : CallbackController<ScreenListener> {
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
/*
|
||||
* 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.unfold.util
|
||||
|
||||
import android.os.Trace
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* 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.unfold.util
|
||||
|
||||
interface CallbackController<T> {
|
||||
fun addCallback(listener: T)
|
||||
fun removeCallback(listener: T)
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* 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.unfold.util
|
||||
|
||||
interface CurrentActivityTypeProvider {
|
||||
val isHomeActivity: Boolean?
|
||||
}
|
||||
|
||||
class EmptyCurrentActivityTypeProvider(override val isHomeActivity: Boolean? = null) :
|
||||
CurrentActivityTypeProvider
|
||||
@@ -1,3 +1,17 @@
|
||||
/*
|
||||
* 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.unfold.util
|
||||
|
||||
import android.animation.ValueAnimator
|
||||
Reference in New Issue
Block a user