diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1677ff5a53..09c9f34110 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -52,7 +52,7 @@ jobs: echo ${{ secrets.KEYSTORE }} | base64 --decode > ${{ github.workspace }}/key.jks fi - name: Build debug APK - run: ./gradlew assembleLawnWithQuickstepGithubDebug assembleLawnWithQuickstepPlayDebug + run: ./gradlew assembleLawnWithQuickstepGithubRelease assembleLawnWithQuickstepGithubRelease - name: Upload artifact uses: actions/upload-artifact@v4 with: diff --git a/build.gradle b/build.gradle index a312c728b9..374f6d9763 100644 --- a/build.gradle +++ b/build.gradle @@ -7,11 +7,11 @@ plugins { id 'com.android.library' version "8.7.0" apply false id 'com.android.test' version '8.7.0' apply false id 'androidx.baselineprofile' version '1.3.2' - id 'org.jetbrains.kotlin.kapt' version "1.9.20" apply false - id 'org.jetbrains.kotlin.android' version "2.0.20" - id 'org.jetbrains.kotlin.plugin.compose' version "2.0.20" - id 'org.jetbrains.kotlin.plugin.parcelize' version "2.0.20" - id 'org.jetbrains.kotlin.plugin.serialization' version "2.0.20" + id 'org.jetbrains.kotlin.android' version "2.0.21" + id 'org.jetbrains.kotlin.plugin.compose' version "2.0.21" + id 'org.jetbrains.kotlin.plugin.parcelize' version "2.0.21" + id 'org.jetbrains.kotlin.plugin.serialization' version "2.0.21" + id "com.google.devtools.ksp" version "2.0.21-1.0.25" id 'com.google.protobuf' version "0.9.4" id 'app.cash.licensee' version "1.11.0" id 'dev.rikka.tools.refine' version "4.4.0" @@ -19,8 +19,6 @@ plugins { id 'com.diffplug.spotless' version '6.25.0' } -apply plugin: 'kotlin-android' -apply plugin: 'kotlin-kapt' allprojects { plugins.withType(AndroidBasePlugin).configureEach { @@ -105,6 +103,7 @@ allprojects { compileOnly fileTree(dir: FRAMEWORK_PREBUILTS_DIR, include: 'SystemUI-core.jar') compileOnly fileTree(dir: FRAMEWORK_PREBUILTS_DIR, include: 'SystemUI-statsd.jar') compileOnly fileTree(dir: FRAMEWORK_PREBUILTS_DIR, include: 'WindowManager-Shell-15.jar') + compileOnly fileTree(dir: FRAMEWORK_PREBUILTS_DIR, include: 'QuickstepResLib.jar') compileOnly projects.compatLib compileOnly projects.compatLib.compatLibVQ @@ -337,13 +336,13 @@ dependencies { // Recents lib dependency withQuickstepCompileOnly projects.hiddenApi withQuickstepImplementation projects.systemUIShared - withQuickstepImplementation projects.systemUIAnim +// withQuickstepImplementation projects.systemUIAnim withQuickstepImplementation projects.systemUnFold - withQuickstepImplementation projects.systemUIViewCapture - withQuickstepImplementation projects.systemUILog - withQuickstepCompileOnly projects.systemUIPlugin - withQuickstepImplementation projects.systemUIPluginCore - withQuickstepCompileOnly projects.systemUICommon +// withQuickstepImplementation projects.systemUIViewCapture +// withQuickstepImplementation projects.systemUILog +// withQuickstepCompileOnly projects.systemUIPlugin +// withQuickstepImplementation projects.systemUIPluginCore +// withQuickstepCompileOnly projects.systemUICommon // QuickSwitch Compat withQuickstepImplementation projects.compatLib @@ -355,6 +354,7 @@ dependencies { implementation fileTree(dir: FRAMEWORK_PREBUILTS_DIR, include: 'SystemUI-statsd-14.jar') implementation fileTree(dir: FRAMEWORK_PREBUILTS_DIR, include: 'WindowManager-Shell-15.jar') + withQuickstepCompileOnly fileTree(dir: FRAMEWORK_PREBUILTS_DIR, include: 'QuickstepResLib.jar') withQuickstepCompileOnly fileTree(dir: FRAMEWORK_PREBUILTS_DIR, include: 'framework-15.jar') coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.2' @@ -409,14 +409,9 @@ dependencies { implementation "com.squareup.retrofit2:converter-kotlinx-serialization:$retrofitVersion" def roomVersion = '2.6.1' - - implementation "androidx.room:room-runtime:$roomVersion" - annotationProcessor "androidx.room:room-compiler:$roomVersion" - implementation "androidx.room:room-runtime:$roomVersion" implementation "androidx.room:room-ktx:$roomVersion" - //noinspection KaptUsageInsteadOfKsp - kapt "androidx.room:room-compiler:$roomVersion" + ksp "androidx.room:room-compiler:$roomVersion" def core_version = "1.13.1" implementation "androidx.core:core:$core_version" @@ -439,12 +434,10 @@ dependencies { implementation("com.github.android:renderscript-intrinsics-replacement-toolkit:b6363490c3") } -kapt { - arguments { - arg("room.schemaLocation", "$projectDir/schemas") - arg("room.generateKotlin", "true") - arg("room.incremental", "true") - } +ksp { + arg("room.schemaLocation", "$projectDir/schemas") + arg("room.generateKotlin", "true") + arg("room.incremental", "true") } diff --git a/prebuilts/libs/QuickstepResLib.jar b/prebuilts/libs/QuickstepResLib.jar index d65b0a5ce9..bb61c3be84 100644 Binary files a/prebuilts/libs/QuickstepResLib.jar and b/prebuilts/libs/QuickstepResLib.jar differ diff --git a/settings.gradle b/settings.gradle index 2b6418a628..62190881b6 100644 --- a/settings.gradle +++ b/settings.gradle @@ -57,13 +57,8 @@ project(':animationlib').projectDir = new File(rootDir, 'platform_frameworks_lib include ':hidden-api' include ':systemUIShared' -include ':systemUIPlugin' -include ':systemUIPluginCore' -include ':systemUICommon' -include ':systemUILog' -include ':systemUIAnim' + include ':systemUnFold' -include ':systemUIViewCapture' include ':compatLib' include ':compatLib:compatLibVQ' diff --git a/systemUIAnim/Android.bp b/systemUIAnim/Android.bp deleted file mode 100644 index 6f53b42371..0000000000 --- a/systemUIAnim/Android.bp +++ /dev/null @@ -1,73 +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 { - // 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: "SystemUIAnimationLib", - - srcs: [ - "src/**/*.java", - "src/**/*.kt", - ], - exclude_srcs: [ - "src/com/android/systemui/surfaceeffects/**/*.java", - "src/com/android/systemui/surfaceeffects/**/*.kt", - ], - - resource_dirs: [ - "res", - ], - - static_libs: [ - "androidx.core_core-animation-nodeps", - "androidx.core_core-ktx", - "androidx.annotation_annotation", - "SystemUIShaderLib", - "animationlib", - ], - - manifest: "AndroidManifest.xml", - kotlincflags: ["-Xjvm-default=all"], -} - -android_library { - name: "SystemUIShaderLib", - - srcs: [ - "src/com/android/systemui/surfaceeffects/**/*.java", - "src/com/android/systemui/surfaceeffects/**/*.kt", - ], - - static_libs: [ - "androidx.core_core-animation-nodeps", - "androidx.core_core-ktx", - "androidx.annotation_annotation", - ], - - manifest: "AndroidManifest.xml", - kotlincflags: ["-Xjvm-default=all"], - - // sdk_version must be specified, otherwise it compiles against private APIs. - min_sdk_version: "33", - sdk_version: "current", -} diff --git a/systemUIAnim/AndroidManifest.xml b/systemUIAnim/AndroidManifest.xml deleted file mode 100644 index 321cc53142..0000000000 --- a/systemUIAnim/AndroidManifest.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - diff --git a/systemUIAnim/build.gradle b/systemUIAnim/build.gradle deleted file mode 100644 index 1d68590d4c..0000000000 --- a/systemUIAnim/build.gradle +++ /dev/null @@ -1,28 +0,0 @@ -plugins { - id 'com.android.library' - id 'org.jetbrains.kotlin.android' -} - -android { - namespace "com.android.systemui.animation" - buildFeatures { - aidl true - } - sourceSets { - main { - java.srcDirs = ['src'] - aidl.srcDirs = ['src'] - manifest.srcFile 'AndroidManifest.xml' - res.srcDirs = ['res'] - } - } -} - -addFrameworkJar('framework-15.jar') -compileOnlyCommonJars() - -dependencies { - implementation "androidx.core:core-animation:1.0.0" - compileOnly projects.animationlib - -} diff --git a/systemUIAnim/res/anim/launch_dialog_enter.xml b/systemUIAnim/res/anim/launch_dialog_enter.xml deleted file mode 100644 index c6b87d38f7..0000000000 --- a/systemUIAnim/res/anim/launch_dialog_enter.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/systemUIAnim/res/anim/launch_dialog_exit.xml b/systemUIAnim/res/anim/launch_dialog_exit.xml deleted file mode 100644 index a0f441eaee..0000000000 --- a/systemUIAnim/res/anim/launch_dialog_exit.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - \ No newline at end of file diff --git a/systemUIAnim/res/values/ids.xml b/systemUIAnim/res/values/ids.xml deleted file mode 100644 index 1a224ac7c1..0000000000 --- a/systemUIAnim/res/values/ids.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/systemUIAnim/res/values/styles.xml b/systemUIAnim/res/values/styles.xml deleted file mode 100644 index 3019eeef23..0000000000 --- a/systemUIAnim/res/values/styles.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/systemUIAnim/src/com/android/systemui/animation/ActivityTransitionAnimator.kt b/systemUIAnim/src/com/android/systemui/animation/ActivityTransitionAnimator.kt deleted file mode 100644 index c14ee62081..0000000000 --- a/systemUIAnim/src/com/android/systemui/animation/ActivityTransitionAnimator.kt +++ /dev/null @@ -1,1370 +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.animation - -import android.app.ActivityManager -import android.app.ActivityTaskManager -import android.app.PendingIntent -import android.app.TaskInfo -import android.app.WindowConfiguration -import android.content.ComponentName -import android.graphics.Color -import android.graphics.Matrix -import android.graphics.Rect -import android.graphics.RectF -import android.os.Binder -import android.os.Build -import android.os.Handler -import android.os.Looper -import android.os.RemoteException -import android.util.Log -import android.view.IRemoteAnimationFinishedCallback -import android.view.IRemoteAnimationRunner -import android.view.RemoteAnimationAdapter -import android.view.RemoteAnimationTarget -import android.view.SyncRtSurfaceTransactionApplier -import android.view.View -import android.view.ViewGroup -import android.view.WindowManager -import android.view.WindowManager.TRANSIT_CLOSE -import android.view.WindowManager.TRANSIT_OPEN -import android.view.WindowManager.TRANSIT_TO_BACK -import android.view.WindowManager.TRANSIT_TO_FRONT -import android.view.animation.PathInterpolator -import android.window.RemoteTransition -import android.window.TransitionFilter -import androidx.annotation.AnyThread -import androidx.annotation.BinderThread -import androidx.annotation.UiThread -import com.android.app.animation.Interpolators -import com.android.internal.annotations.VisibleForTesting -import com.android.internal.policy.ScreenDecorationsUtils -import com.android.systemui.Flags.activityTransitionUseLargestWindow -import com.android.systemui.Flags.translucentOccludingActivityFix -import com.android.systemui.shared.Flags.returnAnimationFrameworkLibrary -import com.android.wm.shell.shared.IShellTransitions -import com.android.wm.shell.shared.ShellTransitions -import java.util.concurrent.Executor -import kotlin.math.roundToInt - -private const val TAG = "ActivityTransitionAnimator" - -/** - * A class that allows activities to be started in a seamless way from a view that is transforming - * nicely into the starting window. - */ -class ActivityTransitionAnimator -@JvmOverloads -constructor( - /** The executor that runs on the main thread. */ - private val mainExecutor: Executor, - - /** The object used to register ephemeral returns and long-lived transitions. */ - private val transitionRegister: TransitionRegister? = null, - - /** The animator used when animating a View into an app. */ - private val transitionAnimator: TransitionAnimator = defaultTransitionAnimator(mainExecutor), - - /** The animator used when animating a Dialog into an app. */ - // TODO(b/218989950): Remove this animator and instead set the duration of the dim fade out to - // TIMINGS.contentBeforeFadeOutDuration. - private val dialogToAppAnimator: TransitionAnimator = defaultDialogToAppAnimator(mainExecutor), - - /** - * Whether we should disable the WindowManager timeout. This should be set to true in tests - * only. - */ - // TODO(b/301385865): Remove this flag. - private val disableWmTimeout: Boolean = false, -) { - @JvmOverloads - constructor( - mainExecutor: Executor, - shellTransitions: ShellTransitions, - transitionAnimator: TransitionAnimator = defaultTransitionAnimator(mainExecutor), - dialogToAppAnimator: TransitionAnimator = defaultDialogToAppAnimator(mainExecutor), - disableWmTimeout: Boolean = false, - ) : this( - mainExecutor, - TransitionRegister.fromShellTransitions(shellTransitions), - transitionAnimator, - dialogToAppAnimator, - disableWmTimeout, - ) - - @JvmOverloads - constructor( - mainExecutor: Executor, - iShellTransitions: IShellTransitions, - transitionAnimator: TransitionAnimator = defaultTransitionAnimator(mainExecutor), - dialogToAppAnimator: TransitionAnimator = defaultDialogToAppAnimator(mainExecutor), - disableWmTimeout: Boolean = false, - ) : this( - mainExecutor, - TransitionRegister.fromIShellTransitions(iShellTransitions), - transitionAnimator, - dialogToAppAnimator, - disableWmTimeout, - ) - - companion object { - /** The timings when animating a View into an app. */ - @JvmField - val TIMINGS = - TransitionAnimator.Timings( - totalDuration = 500L, - contentBeforeFadeOutDelay = 0L, - contentBeforeFadeOutDuration = 150L, - contentAfterFadeInDelay = 150L, - contentAfterFadeInDuration = 183L - ) - - /** - * The timings when animating a Dialog into an app. We need to wait at least 200ms before - * showing the app (which is under the dialog window) so that the dialog window dim is fully - * faded out, to avoid flicker. - */ - val DIALOG_TIMINGS = - TIMINGS.copy(contentBeforeFadeOutDuration = 200L, contentAfterFadeInDelay = 200L) - - /** The interpolators when animating a View or a dialog into an app. */ - val INTERPOLATORS = - TransitionAnimator.Interpolators( - positionInterpolator = Interpolators.EMPHASIZED, - positionXInterpolator = Interpolators.EMPHASIZED_COMPLEMENT, - contentBeforeFadeOutInterpolator = Interpolators.LINEAR_OUT_SLOW_IN, - contentAfterFadeInInterpolator = PathInterpolator(0f, 0f, 0.6f, 1f) - ) - - // TODO(b/288507023): Remove this flag. - @JvmField val DEBUG_TRANSITION_ANIMATION = Build.IS_DEBUGGABLE - - /** Durations & interpolators for the navigation bar fading in & out. */ - private const val ANIMATION_DURATION_NAV_FADE_IN = 266L - private const val ANIMATION_DURATION_NAV_FADE_OUT = 133L - private val ANIMATION_DELAY_NAV_FADE_IN = - TIMINGS.totalDuration - ANIMATION_DURATION_NAV_FADE_IN - - private val NAV_FADE_IN_INTERPOLATOR = Interpolators.STANDARD_DECELERATE - private val NAV_FADE_OUT_INTERPOLATOR = PathInterpolator(0.2f, 0f, 1f, 1f) - - /** The time we wait before timing out the remote animation after starting the intent. */ - private const val TRANSITION_TIMEOUT = 1_000L - - /** - * The time we wait before we Log.wtf because the remote animation was neither started or - * cancelled by WM. - */ - private const val LONG_TRANSITION_TIMEOUT = 5_000L - - private fun defaultTransitionAnimator(mainExecutor: Executor): TransitionAnimator { - return TransitionAnimator(mainExecutor, TIMINGS, INTERPOLATORS) - } - - private fun defaultDialogToAppAnimator(mainExecutor: Executor): TransitionAnimator { - return TransitionAnimator(mainExecutor, DIALOG_TIMINGS, INTERPOLATORS) - } - } - - /** - * The callback of this animator. This should be set before any call to - * [start(Pending)IntentWithAnimation]. - */ - var callback: Callback? = null - - /** The set of [Listener] that should be notified of any animation started by this animator. */ - private val listeners = LinkedHashSet() - - /** Top-level listener that can be used to notify all registered [listeners]. */ - private val lifecycleListener = - object : Listener { - override fun onTransitionAnimationStart() { - listeners.forEach { it.onTransitionAnimationStart() } - } - - override fun onTransitionAnimationEnd() { - listeners.forEach { it.onTransitionAnimationEnd() } - } - - override fun onTransitionAnimationProgress(linearProgress: Float) { - listeners.forEach { it.onTransitionAnimationProgress(linearProgress) } - } - - override fun onTransitionAnimationCancelled() { - listeners.forEach { it.onTransitionAnimationCancelled() } - } - } - - /** Book-keeping for long-lived transitions that are currently registered. */ - private val longLivedTransitions = - HashMap>() - - /** - * Start an intent and animate the opening window. The intent will be started by running - * [intentStarter], which should use the provided [RemoteAnimationAdapter] and return the launch - * result. [controller] is responsible from animating the view from which the intent was started - * in [Controller.onTransitionAnimationProgress]. No animation will start if there is no window - * opening. - * - * If [controller] is null or [animate] is false, then the intent will be started and no - * animation will run. - * - * If possible, you should pass the [packageName] of the intent that will be started so that - * trampoline activity launches will also be animated. - * - * If the device is currently locked, the user will have to unlock it before the intent is - * started unless [showOverLockscreen] is true. In that case, the activity will be started - * directly over the lockscreen. - * - * This method will throw any exception thrown by [intentStarter]. - */ - @JvmOverloads - fun startIntentWithAnimation( - controller: Controller?, - animate: Boolean = true, - packageName: String? = null, - showOverLockscreen: Boolean = false, - intentStarter: (RemoteAnimationAdapter?) -> Int - ) { - if (controller == null || !animate) { - Log.i(TAG, "Starting intent with no animation") - intentStarter(null) - controller?.callOnIntentStartedOnMainThread(willAnimate = false) - return - } - - val callback = - this.callback - ?: throw IllegalStateException( - "ActivityTransitionAnimator.callback must be set before using this animator" - ) - val runner = createRunner(controller) - val runnerDelegate = runner.delegate!! - val hideKeyguardWithAnimation = callback.isOnKeyguard() && !showOverLockscreen - - // Pass the RemoteAnimationAdapter to the intent starter only if we are not hiding the - // keyguard with the animation - val animationAdapter = - if (!hideKeyguardWithAnimation) { - RemoteAnimationAdapter( - runner, - TIMINGS.totalDuration, - TIMINGS.totalDuration - 150 /* statusBarTransitionDelay */ - ) - } else { - null - } - - // Register the remote animation for the given package to also animate trampoline - // activity launches. - if (packageName != null && animationAdapter != null) { - try { - ActivityTaskManager.getService() - .registerRemoteAnimationForNextActivityStart( - packageName, - animationAdapter, - null /* launchCookie */ - ) - } catch (e: RemoteException) { - Log.w(TAG, "Unable to register the remote animation", e) - } - } - - if (animationAdapter != null && controller.transitionCookie != null) { - registerEphemeralReturnAnimation(controller, transitionRegister) - } - - val launchResult = intentStarter(animationAdapter) - - // Only animate if the app is not already on top and will be opened, unless we are on the - // keyguard. - val willAnimate = - launchResult == ActivityManager.START_TASK_TO_FRONT || - launchResult == ActivityManager.START_SUCCESS || - (launchResult == ActivityManager.START_DELIVERED_TO_TOP && - hideKeyguardWithAnimation) - - Log.i( - TAG, - "launchResult=$launchResult willAnimate=$willAnimate " + - "hideKeyguardWithAnimation=$hideKeyguardWithAnimation" - ) - controller.callOnIntentStartedOnMainThread(willAnimate) - - // If we expect an animation, post a timeout to cancel it in case the remote animation is - // never started. - if (willAnimate) { - runnerDelegate.postTimeouts() - - // Hide the keyguard using the launch animation instead of the default unlock animation. - if (hideKeyguardWithAnimation) { - callback.hideKeyguardWithAnimation(runner) - } - } else { - // We need to make sure delegate references are dropped to avoid memory leaks. - runner.dispose() - } - } - - private fun Controller.callOnIntentStartedOnMainThread(willAnimate: Boolean) { - if (Looper.myLooper() != Looper.getMainLooper()) { - mainExecutor.execute { callOnIntentStartedOnMainThread(willAnimate) } - } else { - if (DEBUG_TRANSITION_ANIMATION) { - Log.d( - TAG, - "Calling controller.onIntentStarted(willAnimate=$willAnimate) " + - "[controller=$this]" - ) - } - this.onIntentStarted(willAnimate) - } - } - - /** - * Same as [startIntentWithAnimation] but allows [intentStarter] to throw a - * [PendingIntent.CanceledException] which must then be handled by the caller. This is useful - * for Java caller starting a [PendingIntent]. - * - * If possible, you should pass the [packageName] of the intent that will be started so that - * trampoline activity launches will also be animated. - */ - @Throws(PendingIntent.CanceledException::class) - @JvmOverloads - fun startPendingIntentWithAnimation( - controller: Controller?, - animate: Boolean = true, - packageName: String? = null, - showOverLockscreen: Boolean = false, - intentStarter: PendingIntentStarter - ) { - startIntentWithAnimation(controller, animate, packageName, showOverLockscreen) { - intentStarter.startPendingIntent(it) - } - } - - /** - * Uses [transitionRegister] to set up the return animation for the given [launchController]. - * - * De-registration is set up automatically once the return animation is run. - * - * TODO(b/339194555): automatically de-register when the launchable is detached. - */ - private fun registerEphemeralReturnAnimation( - launchController: Controller, - transitionRegister: TransitionRegister? - ) { - if (!returnAnimationFrameworkLibrary()) return - - var cleanUpRunnable: Runnable? = null - val returnRunner = - createRunner( - object : DelegateTransitionAnimatorController(launchController) { - override val isLaunching = false - - override fun onTransitionAnimationCancelled( - newKeyguardOccludedState: Boolean? - ) { - super.onTransitionAnimationCancelled(newKeyguardOccludedState) - cleanUp() - } - - override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) { - super.onTransitionAnimationEnd(isExpandingFullyAbove) - cleanUp() - } - - private fun cleanUp() { - cleanUpRunnable?.run() - } - } - ) - - // mTypeSet and mModes match back signals only, and not home. This is on purpose, because - // we only want ephemeral return animations triggered in these scenarios. - val filter = - TransitionFilter().apply { - mTypeSet = intArrayOf(TRANSIT_CLOSE, TRANSIT_TO_BACK) - mRequirements = - arrayOf( - TransitionFilter.Requirement().apply { - mLaunchCookie = launchController.transitionCookie - mModes = intArrayOf(TRANSIT_CLOSE, TRANSIT_TO_BACK) - } - ) - } - val transition = - RemoteTransition( - RemoteAnimationRunnerCompat.wrap(returnRunner), - "${launchController.transitionCookie}_returnTransition" - ) - - transitionRegister?.register(filter, transition) - cleanUpRunnable = Runnable { transitionRegister?.unregister(transition) } - } - - /** Add a [Listener] that can listen to transition animations. */ - fun addListener(listener: Listener) { - listeners.add(listener) - } - - /** Remove a [Listener]. */ - fun removeListener(listener: Listener) { - listeners.remove(listener) - } - - /** Create a new animation [Runner] controlled by [controller]. */ - @VisibleForTesting - fun createRunner(controller: Controller): Runner { - // Make sure we use the modified timings when animating a dialog into an app. - val transitionAnimator = - if (controller.isDialogLaunch) { - dialogToAppAnimator - } else { - transitionAnimator - } - - return Runner(controller, callback!!, transitionAnimator, lifecycleListener) - } - - interface PendingIntentStarter { - /** - * Start a pending intent using the provided [animationAdapter] and return the launch - * result. - */ - @Throws(PendingIntent.CanceledException::class) - fun startPendingIntent(animationAdapter: RemoteAnimationAdapter?): Int - } - - interface Callback { - /** Whether we are currently on the keyguard or not. */ - fun isOnKeyguard(): Boolean = false - - /** Hide the keyguard and animate using [runner]. */ - fun hideKeyguardWithAnimation(runner: IRemoteAnimationRunner) { - throw UnsupportedOperationException() - } - - /* Get the background color of [task]. */ - fun getBackgroundColor(task: TaskInfo): Int - } - - interface Listener { - /** Called when an activity transition animation started. */ - fun onTransitionAnimationStart() {} - - /** - * Called when an activity transition animation is finished. This will be called if and only - * if [onTransitionAnimationStart] was called earlier. - */ - fun onTransitionAnimationEnd() {} - - /** - * The animation was cancelled. Note that [onTransitionAnimationEnd] will still be called - * after this if the animation was already started, i.e. if [onTransitionAnimationStart] was - * called before the cancellation. - */ - fun onTransitionAnimationCancelled() {} - - /** Called when an activity transition animation made progress. */ - fun onTransitionAnimationProgress(linearProgress: Float) {} - } - - /** - * A controller that takes care of applying the animation to an expanding view. - * - * Note that all callbacks (onXXX methods) are all called on the main thread. - */ - interface Controller : TransitionAnimator.Controller { - companion object { - /** - * Return a [Controller] that will animate and expand [view] into the opening window. - * - * Important: The view must be attached to a [ViewGroup] when calling this function and - * during the animation. For safety, this method will return null when it is not. The - * view must also implement [LaunchableView], otherwise this method will throw. - * - * Note: The background of [view] should be a (rounded) rectangle so that it can be - * properly animated. - */ - @JvmOverloads - @JvmStatic - fun fromView( - view: View, - cujType: Int? = null, - cookie: TransitionCookie? = null, - component: ComponentName? = null, - returnCujType: Int? = null - ): Controller? { - // Make sure the View we launch from implements LaunchableView to avoid visibility - // issues. - if (view !is LaunchableView) { - throw IllegalArgumentException( - "An ActivityTransitionAnimator.Controller was created from a View that " + - "does not implement LaunchableView. This can lead to subtle bugs " + - "where the visibility of the View we are launching from is not what " + - "we expected." - ) - } - - if (view.parent !is ViewGroup) { - Log.e( - TAG, - "Skipping animation as view $view is not attached to a ViewGroup", - Exception() - ) - return null - } - - return GhostedViewTransitionAnimatorController( - view, - cujType, - cookie, - component, - returnCujType - ) - } - } - - /** - * Whether this controller is controlling a dialog launch. This will be used to adapt the - * timings, making sure we don't show the app until the dialog dim had the time to fade out. - */ - // TODO(b/218989950): Remove this. - val isDialogLaunch: Boolean - get() = false - - /** - * Whether the expandable controller by this [Controller] is below the window that is going - * to be animated. - * - * This should be `false` when animating an app from or to the shade or status bar, given - * that they are drawn above all apps. This is usually `true` when using this animator in a - * normal app or a launcher, that are drawn below the animating activity/window. - */ - val isBelowAnimatingWindow: Boolean - get() = false - - /** - * The cookie associated with the transition controlled by this [Controller]. - * - * This should be defined for all return [Controller] (when [isLaunching] is false) and for - * their associated launch [Controller]s. - * - * For the recommended format, see [TransitionCookie]. - */ - val transitionCookie: TransitionCookie? - get() = null - - /** - * The [ComponentName] of the activity whose window is tied to this [Controller]. - * - * This is used as a fallback when a cookie is defined but there is no match (e.g. when a - * matching activity was launched by a mean different from the launchable in this - * [Controller]), and should be defined for all long-lived registered [Controller]s. - */ - val component: ComponentName? - get() = null - - /** - * The intent was started. If [willAnimate] is false, nothing else will happen and the - * animation will not be started. - */ - fun onIntentStarted(willAnimate: Boolean) {} - - /** - * The animation was cancelled. Note that [onTransitionAnimationEnd] will still be called - * after this if the animation was already started, i.e. if [onTransitionAnimationStart] was - * called before the cancellation. - * - * If this transition animation affected the occlusion state of the keyguard, WM will - * provide us with [newKeyguardOccludedState] so that we can set the occluded state - * appropriately. - */ - fun onTransitionAnimationCancelled(newKeyguardOccludedState: Boolean? = null) {} - } - - /** - * Registers [controller] as a long-lived transition handler for launch and return animations. - * - * The [controller] will only be used for transitions matching the [TransitionCookie] defined - * within it, or the [ComponentName] if the cookie matching fails. Both fields are mandatory for - * this registration. - */ - fun register(controller: Controller) { - check(returnAnimationFrameworkLibrary()) { - "Long-lived registrations cannot be used when the returnAnimationFrameworkLibrary " + - "flag is disabled" - } - - if (transitionRegister == null) { - throw IllegalStateException( - "A RemoteTransitionRegister must be provided when creating this animator in " + - "order to use long-lived animations" - ) - } - - val cookie = - controller.transitionCookie - ?: throw IllegalStateException( - "A cookie must be defined in order to use long-lived animations" - ) - val component = - controller.component - ?: throw IllegalStateException( - "A component must be defined in order to use long-lived animations" - ) - - // Make sure that any previous registrations linked to the same cookie are gone. - unregister(cookie) - - val launchFilter = - TransitionFilter().apply { - mRequirements = - arrayOf( - TransitionFilter.Requirement().apply { - mActivityType = WindowConfiguration.ACTIVITY_TYPE_STANDARD - mModes = intArrayOf(TRANSIT_OPEN, TRANSIT_TO_FRONT) - mTopActivity = component - } - ) - } - val launchRemoteTransition = - RemoteTransition( - RemoteAnimationRunnerCompat.wrap(createRunner(controller)), - "${cookie}_launchTransition" - ) - transitionRegister.register(launchFilter, launchRemoteTransition) - - val returnController = - object : Controller by controller { - override val isLaunching: Boolean = false - } - val returnFilter = - TransitionFilter().apply { - mRequirements = - arrayOf( - TransitionFilter.Requirement().apply { - mActivityType = WindowConfiguration.ACTIVITY_TYPE_STANDARD - mModes = intArrayOf(TRANSIT_CLOSE, TRANSIT_TO_BACK) - mTopActivity = component - } - ) - } - val returnRemoteTransition = - RemoteTransition( - RemoteAnimationRunnerCompat.wrap(createRunner(returnController)), - "${cookie}_returnTransition" - ) - transitionRegister.register(returnFilter, returnRemoteTransition) - - longLivedTransitions[cookie] = Pair(launchRemoteTransition, returnRemoteTransition) - } - - /** Unregisters all controllers previously registered that contain [cookie]. */ - fun unregister(cookie: TransitionCookie) { - val transitions = longLivedTransitions[cookie] ?: return - transitionRegister?.unregister(transitions.first) - transitionRegister?.unregister(transitions.second) - longLivedTransitions.remove(cookie) - } - - /** - * Invokes [onAnimationComplete] when animation is either cancelled or completed. Delegates all - * events to the passed [delegate]. - */ - @VisibleForTesting - inner class DelegatingAnimationCompletionListener( - private val delegate: Listener?, - private val onAnimationComplete: () -> Unit - ) : Listener { - var cancelled = false - - override fun onTransitionAnimationStart() { - delegate?.onTransitionAnimationStart() - } - - override fun onTransitionAnimationProgress(linearProgress: Float) { - delegate?.onTransitionAnimationProgress(linearProgress) - } - - override fun onTransitionAnimationEnd() { - delegate?.onTransitionAnimationEnd() - if (!cancelled) { - onAnimationComplete.invoke() - } - } - - override fun onTransitionAnimationCancelled() { - cancelled = true - delegate?.onTransitionAnimationCancelled() - onAnimationComplete.invoke() - } - } - - @VisibleForTesting - inner class Runner( - controller: Controller, - callback: Callback, - /** The animator to use to animate the window transition. */ - transitionAnimator: TransitionAnimator, - /** Listener for animation lifecycle events. */ - listener: Listener? = null - ) : IRemoteAnimationRunner.Stub() { - // This is being passed across IPC boundaries and cycles (through PendingIntentRecords, - // etc.) are possible. So we need to make sure we drop any references that might - // transitively cause leaks when we're done with animation. - @VisibleForTesting var delegate: AnimationDelegate? - - init { - delegate = - AnimationDelegate( - mainExecutor, - controller, - callback, - DelegatingAnimationCompletionListener(listener, this::dispose), - transitionAnimator, - disableWmTimeout, - ) - } - - @BinderThread - override fun onAnimationStart( - transit: Int, - apps: Array?, - wallpapers: Array?, - nonApps: Array?, - finishedCallback: IRemoteAnimationFinishedCallback? - ) { - val delegate = delegate - mainExecutor.execute { - if (delegate == null) { - Log.i(TAG, "onAnimationStart called after completion") - // Animation started too late and timed out already. We need to still - // signal back that we're done with it. - finishedCallback?.onAnimationFinished() - } else { - delegate.onAnimationStart(transit, apps, wallpapers, nonApps, finishedCallback) - } - } - } - - @BinderThread - override fun onAnimationCancelled() { - val delegate = delegate - mainExecutor.execute { - delegate ?: Log.wtf(TAG, "onAnimationCancelled called after completion") - delegate?.onAnimationCancelled() - } - } - - @AnyThread - fun dispose() { - // Drop references to animation controller once we're done with the animation - // to avoid leaking. - mainExecutor.execute { delegate = null } - } - } - - class AnimationDelegate - @JvmOverloads - constructor( - private val mainExecutor: Executor, - private val controller: Controller, - private val callback: Callback, - /** Listener for animation lifecycle events. */ - private val listener: Listener? = null, - /** The animator to use to animate the window transition. */ - private val transitionAnimator: TransitionAnimator = - defaultTransitionAnimator(mainExecutor), - - /** - * Whether we should disable the WindowManager timeout. This should be set to true in tests - * only. - */ - // TODO(b/301385865): Remove this flag. - disableWmTimeout: Boolean = false, - ) : RemoteAnimationDelegate { - private val transitionContainer = controller.transitionContainer - private val context = transitionContainer.context - private val transactionApplierView = - controller.openingWindowSyncView ?: controller.transitionContainer - private val transactionApplier = SyncRtSurfaceTransactionApplier(transactionApplierView) - private val timeoutHandler = - if (!disableWmTimeout) { - Handler(Looper.getMainLooper()) - } else { - null - } - - private val matrix = Matrix() - private val invertMatrix = Matrix() - private var windowCrop = Rect() - private var windowCropF = RectF() - private var timedOut = false - private var cancelled = false - private var animation: TransitionAnimator.Animation? = null - - /** - * A timeout to cancel the transition animation if the remote animation is not started or - * cancelled within [TRANSITION_TIMEOUT] milliseconds after the intent was started. - * - * Note that this is important to keep this a Runnable (and not a Kotlin lambda), otherwise - * it will be automatically converted when posted and we wouldn't be able to remove it after - * posting it. - */ - private var onTimeout = Runnable { onAnimationTimedOut() } - - /** - * A long timeout to Log.wtf (signaling a bug in WM) when the remote animation wasn't - * started or cancelled within [LONG_TRANSITION_TIMEOUT] milliseconds after the intent was - * started. - */ - private var onLongTimeout = Runnable { - Log.wtf( - TAG, - "The remote animation was neither cancelled or started within " + - "$LONG_TRANSITION_TIMEOUT" - ) - } - - init { - // We do this check here to cover all entry points, including Launcher which doesn't - // call startIntentWithAnimation() - if (!controller.isLaunching) TransitionAnimator.checkReturnAnimationFrameworkFlag() - } - - @UiThread - internal fun postTimeouts() { - if (timeoutHandler != null) { - timeoutHandler.postDelayed(onTimeout, TRANSITION_TIMEOUT) - timeoutHandler.postDelayed(onLongTimeout, LONG_TRANSITION_TIMEOUT) - } - } - - private fun removeTimeouts() { - if (timeoutHandler != null) { - timeoutHandler.removeCallbacks(onTimeout) - timeoutHandler.removeCallbacks(onLongTimeout) - } - } - - @UiThread - override fun onAnimationStart( - @WindowManager.TransitionOldType transit: Int, - apps: Array?, - wallpapers: Array?, - nonApps: Array?, - callback: IRemoteAnimationFinishedCallback? - ) { - removeTimeouts() - - // The animation was started too late and we already notified the controller that it - // timed out. - if (timedOut) { - callback?.invoke() - return - } - - // This should not happen, but let's make sure we don't start the animation if it was - // cancelled before and we already notified the controller. - if (cancelled) { - return - } - - val window = findTargetWindowIfPossible(apps) - if (window == null) { - Log.i(TAG, "Aborting the animation as no window is opening") - callback?.invoke() - - if (DEBUG_TRANSITION_ANIMATION) { - Log.d( - TAG, - "Calling controller.onTransitionAnimationCancelled() [no window opening]" - ) - } - controller.onTransitionAnimationCancelled() - listener?.onTransitionAnimationCancelled() - return - } - - val navigationBar = - nonApps?.firstOrNull { - it.windowType == WindowManager.LayoutParams.TYPE_NAVIGATION_BAR - } - - startAnimation(window, navigationBar, callback) - } - - private fun findTargetWindowIfPossible( - apps: Array? - ): RemoteAnimationTarget? { - if (apps == null) { - return null - } - - val targetMode = - if (controller.isLaunching) { - RemoteAnimationTarget.MODE_OPENING - } else { - RemoteAnimationTarget.MODE_CLOSING - } - var candidate: RemoteAnimationTarget? = null - - for (it in apps) { - if (it.mode == targetMode) { - if (activityTransitionUseLargestWindow()) { - if (returnAnimationFrameworkLibrary()) { - // If the controller contains a cookie, _only_ match if either the - // candidate contains the matching cookie, or a component is also - // defined and is a match. - if ( - controller.transitionCookie != null && - it.taskInfo - ?.launchCookies - ?.contains(controller.transitionCookie) != true && - (controller.component == null || - it.taskInfo?.topActivity != controller.component) - ) { - continue - } - } - - if ( - candidate == null || - !it.hasAnimatingParent && candidate.hasAnimatingParent - ) { - candidate = it - continue - } - if ( - !it.hasAnimatingParent && - it.screenSpaceBounds.hasGreaterAreaThan(candidate.screenSpaceBounds) - ) { - candidate = it - } - } else { - if (!it.hasAnimatingParent) { - return it - } - if (candidate == null) { - candidate = it - } - } - } - } - - return candidate - } - - private fun startAnimation( - window: RemoteAnimationTarget, - navigationBar: RemoteAnimationTarget?, - iCallback: IRemoteAnimationFinishedCallback? - ) { - if (TransitionAnimator.DEBUG) { - Log.d(TAG, "Remote animation started") - } - - val windowBounds = window.screenSpaceBounds - val endState = - if (controller.isLaunching) { - TransitionAnimator.State( - top = windowBounds.top, - bottom = windowBounds.bottom, - left = windowBounds.left, - right = windowBounds.right - ) - } else { - controller.createAnimatorState() - } - val windowBackgroundColor = - if (translucentOccludingActivityFix() && window.isTranslucent) { - Color.TRANSPARENT - } else { - window.taskInfo?.let { callback.getBackgroundColor(it) } - ?: window.backgroundColor - } - - // TODO(b/184121838): We should somehow get the top and bottom radius of the window - // instead of recomputing isExpandingFullyAbove here. - val isExpandingFullyAbove = - transitionAnimator.isExpandingFullyAbove(controller.transitionContainer, endState) - if (controller.isLaunching) { - val endRadius = getWindowRadius(isExpandingFullyAbove) - endState.topCornerRadius = endRadius - endState.bottomCornerRadius = endRadius - } - - // We animate the opening window and delegate the view expansion to [this.controller]. - val delegate = this.controller - val controller = - object : Controller by delegate { - override fun createAnimatorState(): TransitionAnimator.State { - if (isLaunching) return delegate.createAnimatorState() - val windowRadius = getWindowRadius(isExpandingFullyAbove) - return TransitionAnimator.State( - top = windowBounds.top, - bottom = windowBounds.bottom, - left = windowBounds.left, - right = windowBounds.right, - topCornerRadius = windowRadius, - bottomCornerRadius = windowRadius - ) - } - - override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) { - listener?.onTransitionAnimationStart() - - if (DEBUG_TRANSITION_ANIMATION) { - Log.d( - TAG, - "Calling controller.onTransitionAnimationStart(" + - "isExpandingFullyAbove=$isExpandingFullyAbove) " + - "[controller=$delegate]" - ) - } - delegate.onTransitionAnimationStart(isExpandingFullyAbove) - } - - override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) { - listener?.onTransitionAnimationEnd() - iCallback?.invoke() - - if (DEBUG_TRANSITION_ANIMATION) { - Log.d( - TAG, - "Calling controller.onTransitionAnimationEnd(" + - "isExpandingFullyAbove=$isExpandingFullyAbove) " + - "[controller=$delegate]" - ) - } - delegate.onTransitionAnimationEnd(isExpandingFullyAbove) - } - - override fun onTransitionAnimationProgress( - state: TransitionAnimator.State, - progress: Float, - linearProgress: Float - ) { - applyStateToWindow(window, state, linearProgress) - navigationBar?.let { applyStateToNavigationBar(it, state, linearProgress) } - - listener?.onTransitionAnimationProgress(linearProgress) - delegate.onTransitionAnimationProgress(state, progress, linearProgress) - } - } - - animation = - transitionAnimator.startAnimation( - controller, - endState, - windowBackgroundColor, - fadeWindowBackgroundLayer = !controller.isBelowAnimatingWindow, - drawHole = !controller.isBelowAnimatingWindow, - ) - } - - private fun getWindowRadius(isExpandingFullyAbove: Boolean): Float { - return if (isExpandingFullyAbove) { - // Most of the time, expanding fully above the root view means - // expanding in full screen. - ScreenDecorationsUtils.getWindowCornerRadius(context) - } else { - // This usually means we are in split screen mode, so 2 out of 4 - // corners will have a radius of 0. - 0f - } - } - - private fun applyStateToWindow( - window: RemoteAnimationTarget, - state: TransitionAnimator.State, - linearProgress: Float, - ) { - if (transactionApplierView.viewRootImpl == null || !window.leash.isValid) { - // Don't apply any transaction if the view root we synchronize with was detached or - // if the SurfaceControl associated with [window] is not valid, as - // [SyncRtSurfaceTransactionApplier.scheduleApply] would otherwise throw. - return - } - - val screenBounds = window.screenSpaceBounds - val centerX = (screenBounds.left + screenBounds.right) / 2f - val centerY = (screenBounds.top + screenBounds.bottom) / 2f - val width = screenBounds.right - screenBounds.left - val height = screenBounds.bottom - screenBounds.top - - // Scale the window. We use the max of (widthRatio, heightRatio) so that there is no - // blank space on any side. - val widthRatio = state.width.toFloat() / width - val heightRatio = state.height.toFloat() / height - val scale = maxOf(widthRatio, heightRatio) - matrix.reset() - matrix.setScale(scale, scale, centerX, centerY) - - // Align it to the top and center it in the x-axis. - val heightChange = height * scale - height - val translationX = state.centerX - centerX - val translationY = state.top - screenBounds.top + heightChange / 2f - matrix.postTranslate(translationX, translationY) - - // Crop it. The matrix will also be applied to the crop, so we apply the inverse - // operation. Given that we only scale (by factor > 0) then translate, we can assume - // that the matrix is invertible. - val cropX = state.left.toFloat() - screenBounds.left - val cropY = state.top.toFloat() - screenBounds.top - windowCropF.set(cropX, cropY, cropX + state.width, cropY + state.height) - matrix.invert(invertMatrix) - invertMatrix.mapRect(windowCropF) - windowCrop.set( - windowCropF.left.roundToInt(), - windowCropF.top.roundToInt(), - windowCropF.right.roundToInt(), - windowCropF.bottom.roundToInt() - ) - - val windowAnimationDelay = - if (controller.isLaunching) { - TIMINGS.contentAfterFadeInDelay - } else { - TIMINGS.contentBeforeFadeOutDelay - } - val windowAnimationDuration = - if (controller.isLaunching) { - TIMINGS.contentAfterFadeInDuration - } else { - TIMINGS.contentBeforeFadeOutDuration - } - val windowProgress = - TransitionAnimator.getProgress( - TIMINGS, - linearProgress, - windowAnimationDelay, - windowAnimationDuration - ) - - // The alpha of the opening window. If it opens above the expandable, then it should - // fade in progressively. Otherwise, it should be fully opaque and will be progressively - // revealed as the window background color layer above the window fades out. - val alpha = - if (controller.isBelowAnimatingWindow) { - if (controller.isLaunching) { - INTERPOLATORS.contentAfterFadeInInterpolator.getInterpolation( - windowProgress - ) - } else { - 1 - - INTERPOLATORS.contentBeforeFadeOutInterpolator.getInterpolation( - windowProgress - ) - } - } else { - 1f - } - - // The scale will also be applied to the corner radius, so we divide by the scale to - // keep the original radius. We use the max of (topCornerRadius, bottomCornerRadius) to - // make sure that the window does not draw itself behind the expanding view. This is - // especially important for lock screen animations, where the window is not clipped by - // the shade. - val cornerRadius = maxOf(state.topCornerRadius, state.bottomCornerRadius) / scale - val params = - SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(window.leash) - .withAlpha(alpha) - .withMatrix(matrix) - .withWindowCrop(windowCrop) - .withCornerRadius(cornerRadius) - .withVisibility(true) - .build() - - transactionApplier.scheduleApply(params) - } - - private fun applyStateToNavigationBar( - navigationBar: RemoteAnimationTarget, - state: TransitionAnimator.State, - linearProgress: Float - ) { - if (transactionApplierView.viewRootImpl == null || !navigationBar.leash.isValid) { - // Don't apply any transaction if the view root we synchronize with was detached or - // if the SurfaceControl associated with [navigationBar] is not valid, as - // [SyncRtSurfaceTransactionApplier.scheduleApply] would otherwise throw. - return - } - - val fadeInProgress = - TransitionAnimator.getProgress( - TIMINGS, - linearProgress, - ANIMATION_DELAY_NAV_FADE_IN, - ANIMATION_DURATION_NAV_FADE_OUT - ) - - val params = SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(navigationBar.leash) - if (fadeInProgress > 0) { - matrix.reset() - matrix.setTranslate( - 0f, - (state.top - navigationBar.sourceContainerBounds.top).toFloat() - ) - windowCrop.set(state.left, 0, state.right, state.height) - params - .withAlpha(NAV_FADE_IN_INTERPOLATOR.getInterpolation(fadeInProgress)) - .withMatrix(matrix) - .withWindowCrop(windowCrop) - .withVisibility(true) - } else { - val fadeOutProgress = - TransitionAnimator.getProgress( - TIMINGS, - linearProgress, - 0, - ANIMATION_DURATION_NAV_FADE_OUT - ) - params.withAlpha(1f - NAV_FADE_OUT_INTERPOLATOR.getInterpolation(fadeOutProgress)) - } - - transactionApplier.scheduleApply(params.build()) - } - - private fun onAnimationTimedOut() { - // The remote animation was cancelled by WM, so we already cancelled the transition - // animation. - if (cancelled) { - return - } - - Log.w(TAG, "Remote animation timed out") - timedOut = true - - if (DEBUG_TRANSITION_ANIMATION) { - Log.d( - TAG, - "Calling controller.onTransitionAnimationCancelled() [animation timed out]" - ) - } - controller.onTransitionAnimationCancelled() - listener?.onTransitionAnimationCancelled() - } - - @UiThread - override fun onAnimationCancelled() { - removeTimeouts() - - // The short timeout happened, so we already cancelled the transition animation. - if (timedOut) { - return - } - - Log.i(TAG, "Remote animation was cancelled") - cancelled = true - - animation?.cancel() - - if (DEBUG_TRANSITION_ANIMATION) { - Log.d( - TAG, - "Calling controller.onTransitionAnimationCancelled() [remote animation " + - "cancelled]", - ) - } - controller.onTransitionAnimationCancelled() - listener?.onTransitionAnimationCancelled() - } - - private fun IRemoteAnimationFinishedCallback.invoke() { - try { - onAnimationFinished() - } catch (e: RemoteException) { - e.printStackTrace() - } - } - - private fun Rect.hasGreaterAreaThan(other: Rect): Boolean { - return (this.width() * this.height()) > (other.width() * other.height()) - } - } - - /** - * Wraps one of the two methods we have to register remote transitions with WM Shell: - * - for in-process registrations (e.g. System UI) we use [ShellTransitions] - * - for cross-process registrations (e.g. Launcher) we use [IShellTransitions] - * - * Important: each instance of this class must wrap exactly one of the two. - */ - class TransitionRegister - private constructor( - private val shellTransitions: ShellTransitions? = null, - private val iShellTransitions: IShellTransitions? = null, - ) { - init { - assert((shellTransitions != null).xor(iShellTransitions != null)) - } - - companion object { - /** Provides a [TransitionRegister] instance wrapping [ShellTransitions]. */ - fun fromShellTransitions(shellTransitions: ShellTransitions): TransitionRegister { - return TransitionRegister(shellTransitions = shellTransitions) - } - - /** Provides a [TransitionRegister] instance wrapping [IShellTransitions]. */ - fun fromIShellTransitions(iShellTransitions: IShellTransitions): TransitionRegister { - return TransitionRegister(iShellTransitions = iShellTransitions) - } - } - - /** Register [remoteTransition] with WM Shell using the given [filter]. */ - internal fun register( - filter: TransitionFilter, - remoteTransition: RemoteTransition, - ) { - shellTransitions?.registerRemote(filter, remoteTransition) - iShellTransitions?.registerRemote(filter, remoteTransition) - } - - /** Unregister [remoteTransition] from WM Shell. */ - internal fun unregister(remoteTransition: RemoteTransition) { - shellTransitions?.unregisterRemote(remoteTransition) - iShellTransitions?.unregisterRemote(remoteTransition) - } - } - - /** - * A cookie used to uniquely identify a task launched using an - * [ActivityTransitionAnimator.Controller]. - * - * The [String] encapsulated by this class should be formatted in such a way to be unique across - * the system, but reliably constant for the same associated launchable. - * - * Recommended naming scheme: - * - DO use the fully qualified name of the class that owns the instance of the launchable, - * along with a concise and precise description of the purpose of the launchable in question. - * - DO NOT introduce uniqueness through the use of timestamps or other runtime variables that - * will change if the instance is destroyed and re-created. - * - * Example: "com.not.the.real.class.name.ShadeController_openSettingsButton" - * - * Note that sometimes (e.g. in recycler views) there could be multiple instances of the same - * launchable, and no static knowledge to adequately differentiate between them using a single - * description. In this case, the recommendation is to append a unique identifier related to the - * contents of the launchable. - * - * Example: “com.not.the.real.class.name.ToastWebResult_launchAga_id143256” - */ - data class TransitionCookie(private val cookie: String) : Binder() -} diff --git a/systemUIAnim/src/com/android/systemui/animation/AnimationFeatureFlags.kt b/systemUIAnim/src/com/android/systemui/animation/AnimationFeatureFlags.kt deleted file mode 100644 index 1c9dabbb0e..0000000000 --- a/systemUIAnim/src/com/android/systemui/animation/AnimationFeatureFlags.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.android.systemui.animation - -interface AnimationFeatureFlags { - val isPredictiveBackQsDialogAnim: Boolean - get() = false -} diff --git a/systemUIAnim/src/com/android/systemui/animation/DelegateTransitionAnimatorController.kt b/systemUIAnim/src/com/android/systemui/animation/DelegateTransitionAnimatorController.kt deleted file mode 100644 index e246562761..0000000000 --- a/systemUIAnim/src/com/android/systemui/animation/DelegateTransitionAnimatorController.kt +++ /dev/null @@ -1,26 +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.animation - -/** - * A base class to easily create an implementation of [ActivityTransitionAnimator.Controller] which - * delegates most of its call to [delegate]. This is mostly useful for Java code which can't easily - * create such a delegated class. - */ -open class DelegateTransitionAnimatorController( - protected val delegate: ActivityTransitionAnimator.Controller -) : ActivityTransitionAnimator.Controller by delegate diff --git a/systemUIAnim/src/com/android/systemui/animation/DialogTransitionAnimator.kt b/systemUIAnim/src/com/android/systemui/animation/DialogTransitionAnimator.kt deleted file mode 100644 index 907c39d842..0000000000 --- a/systemUIAnim/src/com/android/systemui/animation/DialogTransitionAnimator.kt +++ /dev/null @@ -1,1100 +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.animation - -import android.animation.Animator -import android.animation.AnimatorListenerAdapter -import android.animation.ValueAnimator -import android.app.Dialog -import android.graphics.Color -import android.graphics.Rect -import android.os.Looper -import android.util.Log -import android.util.MathUtils -import android.view.View -import android.view.ViewGroup -import android.view.ViewGroup.LayoutParams.MATCH_PARENT -import android.view.ViewRootImpl -import android.view.WindowInsets -import android.view.WindowManager -import android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS -import com.android.app.animation.Interpolators -import com.android.internal.jank.Cuj.CujType -import com.android.internal.jank.InteractionJankMonitor -import com.android.systemui.util.maybeForceFullscreen -import com.android.systemui.util.registerAnimationOnBackInvoked -import java.util.concurrent.Executor -import kotlin.math.roundToInt - -private const val TAG = "DialogTransitionAnimator" - -/** - * A class that allows dialogs to be started in a seamless way from a view that is transforming - * nicely into the starting dialog. - * - * This animator also allows to easily animate a dialog into an activity. - * - * @see show - * @see showFromView - * @see showFromDialog - * @see createActivityTransitionController - */ -class DialogTransitionAnimator -@JvmOverloads -constructor( - private val mainExecutor: Executor, - private val callback: Callback, - private val interactionJankMonitor: InteractionJankMonitor, - private val featureFlags: AnimationFeatureFlags, - private val transitionAnimator: TransitionAnimator = - TransitionAnimator( - mainExecutor, - TIMINGS, - INTERPOLATORS, - ), - private val isForTesting: Boolean = false, -) { - private companion object { - private val TIMINGS = ActivityTransitionAnimator.TIMINGS - - // We use the same interpolator for X and Y axis to make sure the dialog does not move out - // of the screen bounds during the animation. - private val INTERPOLATORS = - ActivityTransitionAnimator.INTERPOLATORS.copy( - positionXInterpolator = - ActivityTransitionAnimator.INTERPOLATORS.positionInterpolator - ) - } - - /** - * A controller that takes care of applying the dialog launch and exit animations to the source - * that triggered the animation. - */ - interface Controller { - /** The [ViewRootImpl] of this controller. */ - val viewRoot: ViewRootImpl? - - /** - * The identity object of the source animated by this controller. This animator will ensure - * that 2 animations with the same source identity are not going to run at the same time, to - * avoid flickers when a dialog is shown from the same source more or less at the same time - * (for instance if the user clicks an expandable button twice). - */ - val sourceIdentity: Any - - /** The CUJ associated to this controller. */ - val cuj: DialogCuj? - - /** - * Move the drawing of the source in the overlay of [viewGroup]. - * - * Once this method is called, and until [stopDrawingInOverlay] is called, the source - * controlled by this Controller should be drawn in the overlay of [viewGroup] so that it is - * drawn above all other elements in the same [viewRoot]. - */ - fun startDrawingInOverlayOf(viewGroup: ViewGroup) - - /** - * Move the drawing of the source back in its original location. - * - * @see startDrawingInOverlayOf - */ - fun stopDrawingInOverlay() - - /** - * Create the [TransitionAnimator.Controller] that will be called to animate the source - * controlled by this [Controller] during the dialog launch animation. - * - * At the end of this animation, the source should *not* be visible anymore (until the - * dialog is closed and is animated back into the source). - */ - fun createTransitionController(): TransitionAnimator.Controller - - /** - * Create the [TransitionAnimator.Controller] that will be called to animate the source - * controlled by this [Controller] during the dialog exit animation. - * - * At the end of this animation, the source should be visible again. - */ - fun createExitController(): TransitionAnimator.Controller - - /** - * Whether we should animate the dialog back into the source when it is dismissed. If this - * methods returns `false`, then the dialog will simply fade out and - * [onExitAnimationCancelled] will be called. - * - * Note that even when this returns `true`, the exit animation might still be cancelled (in - * which case [onExitAnimationCancelled] will also be called). - */ - fun shouldAnimateExit(): Boolean - - /** - * Called if we decided to *not* animate the dialog into the source for some reason. This - * means that [createExitController] will *not* be called and this implementation should - * make sure that the source is back in its original state, before it was animated into the - * dialog. In particular, the source should be visible again. - */ - fun onExitAnimationCancelled() - - /** - * Return the [InteractionJankMonitor.Configuration.Builder] to be used for animations - * controlled by this controller. - */ - // TODO(b/252723237): Make this non-nullable - fun jankConfigurationBuilder(): InteractionJankMonitor.Configuration.Builder? - - companion object { - /** - * Create a [Controller] that can animate [source] to and from a dialog. - * - * Important: The view must be attached to a [ViewGroup] when calling this function and - * during the animation. For safety, this method will return null when it is not. The - * view must also implement [LaunchableView], otherwise this method will throw. - * - * Note: The background of [view] should be a (rounded) rectangle so that it can be - * properly animated. - */ - fun fromView(source: View, cuj: DialogCuj? = null): Controller? { - // Make sure the View we launch from implements LaunchableView to avoid visibility - // issues. - if (source !is LaunchableView) { - throw IllegalArgumentException( - "A DialogTransitionAnimator.Controller was created from a View that does " + - "not implement LaunchableView. This can lead to subtle bugs where " + - "the visibility of the View we are launching from is not what we " + - "expected." - ) - } - - if (source.parent !is ViewGroup) { - Log.e( - TAG, - "Skipping animation as view $source is not attached to a ViewGroup", - Exception(), - ) - return null - } - - return ViewDialogTransitionAnimatorController(source, cuj) - } - } - } - - /** - * The set of dialogs that were animated using this animator and that are still opened (not - * dismissed, but can be hidden). - */ - // TODO(b/201264644): Remove this set. - private val openedDialogs = hashSetOf() - - /** - * Show [dialog] by expanding it from [view]. If [view] is a view inside another dialog that was - * shown using this method, then we will animate from that dialog instead. - * - * If [animateBackgroundBoundsChange] is true, then the background of the dialog will be - * animated when the dialog bounds change. - * - * Note: The background of [view] should be a (rounded) rectangle so that it can be properly - * animated. - * - * Caveats: When calling this function and [dialog] is not a fullscreen dialog, then it will be - * made fullscreen and 2 views will be inserted between the dialog DecorView and its children. - */ - @JvmOverloads - fun showFromView( - dialog: Dialog, - view: View, - cuj: DialogCuj? = null, - animateBackgroundBoundsChange: Boolean = false - ) { - val controller = Controller.fromView(view, cuj) - if (controller == null) { - dialog.show() - } else { - show(dialog, controller, animateBackgroundBoundsChange) - } - } - - /** - * Show [dialog] by expanding it from a source controlled by [controller]. - * - * If [animateBackgroundBoundsChange] is true, then the background of the dialog will be - * animated when the dialog bounds change. - * - * Note: The background of [view] should be a (rounded) rectangle so that it can be properly - * animated. - * - * Caveats: When calling this function and [dialog] is not a fullscreen dialog, then it will be - * made fullscreen and 2 views will be inserted between the dialog DecorView and its children. - */ - @JvmOverloads - fun show( - dialog: Dialog, - controller: Controller, - animateBackgroundBoundsChange: Boolean = false - ) { - if (Looper.myLooper() != Looper.getMainLooper()) { - throw IllegalStateException( - "showFromView must be called from the main thread and dialog must be created in " + - "the main thread" - ) - } - - // If the view we are launching from belongs to another dialog, then this means the caller - // intent is to launch a dialog from another dialog. - val animatedParent = - openedDialogs.firstOrNull { - it.dialog.window?.decorView?.viewRootImpl == controller.viewRoot - } - val controller = - animatedParent?.dialogContentWithBackground?.let { - Controller.fromView(it, controller.cuj) - } - ?: controller - - // Make sure we don't run the launch animation from the same source twice at the same time. - if (openedDialogs.any { it.controller.sourceIdentity == controller.sourceIdentity }) { - Log.e( - TAG, - "Not running dialog launch animation from source as it is already expanded into a" + - " dialog" - ) - dialog.show() - return - } - - val animatedDialog = - AnimatedDialog( - transitionAnimator = transitionAnimator, - callback = callback, - interactionJankMonitor = interactionJankMonitor, - controller = controller, - onDialogDismissed = { openedDialogs.remove(it) }, - dialog = dialog, - animateBackgroundBoundsChange = animateBackgroundBoundsChange, - parentAnimatedDialog = animatedParent, - forceDisableSynchronization = isForTesting, - featureFlags = featureFlags, - ) - - openedDialogs.add(animatedDialog) - animatedDialog.start() - } - - /** - * Launch [dialog] from [another dialog][animateFrom] that was shown using [show]. This will - * allow for dismissing the whole stack. - * - * @see dismissStack - */ - fun showFromDialog( - dialog: Dialog, - animateFrom: Dialog, - cuj: DialogCuj? = null, - animateBackgroundBoundsChange: Boolean = false - ) { - val view = - openedDialogs.firstOrNull { it.dialog == animateFrom }?.dialogContentWithBackground - if (view == null) { - Log.w( - TAG, - "Showing dialog $dialog normally as the dialog it is shown from was not shown " + - "using DialogTransitionAnimator" - ) - dialog.show() - return - } - - showFromView( - dialog, - view, - animateBackgroundBoundsChange = animateBackgroundBoundsChange, - cuj = cuj - ) - } - - /** - * Create an [ActivityTransitionAnimator.Controller] that can be used to launch an activity from - * the dialog that contains [View]. Note that the dialog must have been shown using this - * animator, otherwise this method will return null. - * - * The returned controller will take care of dismissing the dialog at the right time after the - * activity started, when the dialog to app animation is done (or when it is cancelled). If this - * method returns null, then the dialog won't be dismissed. - * - * @param view any view inside the dialog to animate. - */ - @JvmOverloads - fun createActivityTransitionController( - view: View, - cujType: Int? = null, - ): ActivityTransitionAnimator.Controller? { - val animatedDialog = - openedDialogs.firstOrNull { - it.dialog.window?.decorView?.viewRootImpl == view.viewRootImpl - } - ?: return null - return createActivityTransitionController(animatedDialog, cujType) - } - - /** - * Create an [ActivityTransitionAnimator.Controller] that can be used to launch an activity from - * [dialog]. Note that the dialog must have been shown using this animator, otherwise this - * method will return null. - * - * The returned controller will take care of dismissing the dialog at the right time after the - * activity started, when the dialog to app animation is done (or when it is cancelled). If this - * method returns null, then the dialog won't be dismissed. - * - * @param dialog the dialog to animate. - */ - @JvmOverloads - fun createActivityTransitionController( - dialog: Dialog, - cujType: Int? = null, - ): ActivityTransitionAnimator.Controller? { - val animatedDialog = openedDialogs.firstOrNull { it.dialog == dialog } ?: return null - return createActivityTransitionController(animatedDialog, cujType) - } - - private fun createActivityTransitionController( - animatedDialog: AnimatedDialog, - cujType: Int? = null - ): ActivityTransitionAnimator.Controller? { - // At this point, we know that the intent of the caller is to dismiss the dialog to show - // an app, so we disable the exit animation into the source because we will never want to - // run it anyways. - animatedDialog.exitAnimationDisabled = true - - val dialog = animatedDialog.dialog - - // Don't animate if the dialog is not showing or if we are locked and going to show the - // primary bouncer. - if ( - !dialog.isShowing || - (!callback.isUnlocked() && !callback.isShowingAlternateAuthOnUnlock()) - ) { - return null - } - - val dialogContentWithBackground = animatedDialog.dialogContentWithBackground ?: return null - val controller = - ActivityTransitionAnimator.Controller.fromView(dialogContentWithBackground, cujType) - ?: return null - - // Wrap the controller into one that will instantly dismiss the dialog when the animation is - // done or dismiss it normally (fading it out) if the animation is cancelled. - return object : ActivityTransitionAnimator.Controller by controller { - override val isDialogLaunch = true - - override fun onIntentStarted(willAnimate: Boolean) { - controller.onIntentStarted(willAnimate) - - if (!willAnimate) { - dialog.dismiss() - } - } - - override fun onTransitionAnimationCancelled(newKeyguardOccludedState: Boolean?) { - controller.onTransitionAnimationCancelled() - enableDialogDismiss() - dialog.dismiss() - } - - override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) { - controller.onTransitionAnimationStart(isExpandingFullyAbove) - - // Make sure the dialog is not dismissed during the animation. - disableDialogDismiss() - - // If this dialog was shown from a cascade of other dialogs, make sure those ones - // are dismissed too. - animatedDialog.prepareForStackDismiss() - - // Remove the dim. - dialog.window?.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND) - } - - override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) { - controller.onTransitionAnimationEnd(isExpandingFullyAbove) - - // Hide the dialog then dismiss it to instantly dismiss it without playing the - // animation. - dialog.hide() - enableDialogDismiss() - dialog.dismiss() - } - - private fun disableDialogDismiss() { - dialog.setDismissOverride { /* Do nothing */} - } - - private fun enableDialogDismiss() { - // We don't set the override to null given that [AnimatedDialog.OnDialogDismissed] - // will still properly dismiss the dialog but will also make sure to clean up - // everything (like making sure that the touched view that triggered the dialog is - // made VISIBLE again). - dialog.setDismissOverride(animatedDialog::onDialogDismissed) - } - } - } - - /** - * Ensure that all dialogs currently shown won't animate into their source when dismissed. - * - * This is a temporary API meant to be called right before we both dismiss a dialog and start an - * activity, which currently does not look good if we animate the dialog into their source at - * the same time as the activity starts. - * - * TODO(b/193634619): Remove this function and animate dialog into opening activity instead. - */ - fun disableAllCurrentDialogsExitAnimations() { - openedDialogs.forEach { it.exitAnimationDisabled = true } - } - - /** - * Dismiss [dialog]. If it was launched from another dialog using this animator, also dismiss - * the stack of dialogs and simply fade out [dialog]. - */ - fun dismissStack(dialog: Dialog) { - openedDialogs.firstOrNull { it.dialog == dialog }?.prepareForStackDismiss() - dialog.dismiss() - } - - interface Callback { - /** Whether the device is currently in dreaming (screensaver) mode. */ - fun isDreaming(): Boolean - - /** - * Whether the device is currently unlocked, i.e. if it is *not* on the keyguard or if the - * keyguard can be dismissed. - */ - fun isUnlocked(): Boolean - - /** - * Whether we are going to show alternate authentication (like UDFPS) instead of the - * traditional bouncer when unlocking the device. - */ - fun isShowingAlternateAuthOnUnlock(): Boolean - } -} - -/** - * The CUJ interaction associated with opening the dialog. - * - * The optional tag indicates the specific dialog being opened. - */ -data class DialogCuj(@CujType val cujType: Int, val tag: String? = null) - -private class AnimatedDialog( - private val transitionAnimator: TransitionAnimator, - private val callback: DialogTransitionAnimator.Callback, - private val interactionJankMonitor: InteractionJankMonitor, - - /** - * The controller of the source that triggered the dialog and that will animate into/from the - * dialog. - */ - val controller: DialogTransitionAnimator.Controller, - - /** - * A callback that will be called with this [AnimatedDialog] after the dialog was dismissed and - * the exit animation is done. - */ - private val onDialogDismissed: (AnimatedDialog) -> Unit, - - /** The dialog to show and animate. */ - val dialog: Dialog, - - /** Whether we should animate the dialog background when its bounds change. */ - animateBackgroundBoundsChange: Boolean, - - /** Launch animation corresponding to the parent [AnimatedDialog]. */ - private val parentAnimatedDialog: AnimatedDialog? = null, - - /** - * Whether synchronization should be disabled, which can be useful if we are running in a test. - */ - private val forceDisableSynchronization: Boolean, - private val featureFlags: AnimationFeatureFlags, -) { - /** - * The DecorView of this dialog window. - * - * Note that we access this DecorView lazily to avoid accessing it before the dialog is created, - * which can sometimes cause crashes (e.g. with the Cast dialog). - */ - private val decorView by lazy { dialog.window!!.decorView as ViewGroup } - - /** - * The dialog content with its background. When animating a fullscreen dialog, this is just the - * first ViewGroup of the dialog that has a background. When animating a normal (not fullscreen) - * dialog, this is an additional view that serves as a fake window that will have the same size - * as the dialog window initially had and to which we will set the dialog window background. - */ - var dialogContentWithBackground: ViewGroup? = null - - /** The background color of [dialog], taking into consideration its window background color. */ - private var originalDialogBackgroundColor = Color.BLACK - - /** - * Whether we are currently launching/showing the dialog by animating it from its source - * controlled by [controller]. - */ - private var isLaunching = true - - /** Whether we are currently dismissing/hiding the dialog by animating into its source. */ - private var isDismissing = false - - private var dismissRequested = false - var exitAnimationDisabled = false - - private var isSourceDrawnInDialog = false - private var isOriginalDialogViewLaidOut = false - - /** A layout listener to animate the dialog height change. */ - private val backgroundLayoutListener = - if (animateBackgroundBoundsChange) { - AnimatedBoundsLayoutListener() - } else { - null - } - - /* - * A layout listener in case the dialog (window) size changes (for instance because of a - * configuration change) to ensure that the dialog stays full width. - */ - private var decorViewLayoutListener: View.OnLayoutChangeListener? = null - - private var hasInstrumentedJank = false - - fun start() { - val cuj = controller.cuj - if (cuj != null) { - val config = controller.jankConfigurationBuilder() - if (config != null) { - if (cuj.tag != null) { - config.setTag(cuj.tag) - } - - interactionJankMonitor.begin(config) - hasInstrumentedJank = true - } - } - - // Create the dialog so that its onCreate() method is called, which usually sets the dialog - // content. - dialog.create() - - val window = dialog.window!! - val isWindowFullScreen = - window.attributes.width == MATCH_PARENT && window.attributes.height == MATCH_PARENT - val dialogContentWithBackground = - if (isWindowFullScreen) { - // If the dialog window is already fullscreen, then we look for the first ViewGroup - // that has a background (and is not the DecorView, which always has a background) - // and animate towards that ViewGroup given that this is probably what represents - // the actual dialog view. - var viewGroupWithBackground: ViewGroup? = null - for (i in 0 until decorView.childCount) { - viewGroupWithBackground = - findFirstViewGroupWithBackground(decorView.getChildAt(i)) - if (viewGroupWithBackground != null) { - break - } - } - - // Animate that view with the background. Throw if we didn't find one, because - // otherwise it's not clear what we should animate. - if (viewGroupWithBackground == null) { - error("Unable to find ViewGroup with background") - } - - if (viewGroupWithBackground !is LaunchableView) { - error("The animated ViewGroup with background must implement LaunchableView") - } - - viewGroupWithBackground - } else { - val (dialogContentWithBackground, decorViewLayoutListener) = - dialog.maybeForceFullscreen()!! - this.decorViewLayoutListener = decorViewLayoutListener - dialogContentWithBackground - } - - this.dialogContentWithBackground = dialogContentWithBackground - dialogContentWithBackground.setTag(R.id.tag_dialog_background, true) - - val background = dialogContentWithBackground.background - originalDialogBackgroundColor = - GhostedViewTransitionAnimatorController.findGradientDrawable(background) - ?.color - ?.defaultColor - ?: Color.BLACK - - // Make the background view invisible until we start the animation. We use the transition - // visibility like GhostView does so that we don't mess up with the accessibility tree (see - // b/204944038#comment17). Given that this background implements LaunchableView, we call - // setShouldBlockVisibilityChanges() early so that the current visibility (VISIBLE) is - // restored at the end of the animation. - dialogContentWithBackground.setShouldBlockVisibilityChanges(true) - dialogContentWithBackground.setTransitionVisibility(View.INVISIBLE) - - // Make sure the dialog is visible instantly and does not do any window animation. - val attributes = window.attributes - attributes.windowAnimations = R.style.Animation_LaunchAnimation - - // Ensure that the animation is not clipped by the display cut-out when animating this - // dialog into an app. - attributes.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS - - // Ensure that the animation is not clipped by the navigation/task bars when animating this - // dialog into an app. - val wasFittingNavigationBars = - attributes.fitInsetsTypes and WindowInsets.Type.navigationBars() != 0 - attributes.fitInsetsTypes = - attributes.fitInsetsTypes and WindowInsets.Type.navigationBars().inv() - - window.attributes = window.attributes - - // We apply the insets ourselves to make sure that the paddings are set on the correct - // View. - window.setDecorFitsSystemWindows(false) - val viewWithInsets = (dialogContentWithBackground.parent as ViewGroup) - viewWithInsets.setOnApplyWindowInsetsListener { view, windowInsets -> - val type = - if (wasFittingNavigationBars) { - WindowInsets.Type.displayCutout() or WindowInsets.Type.navigationBars() - } else { - WindowInsets.Type.displayCutout() - } - - val insets = windowInsets.getInsets(type) - view.setPadding(insets.left, insets.top, insets.right, insets.bottom) - WindowInsets.CONSUMED - } - - // Start the animation once the background view is properly laid out. - dialogContentWithBackground.addOnLayoutChangeListener( - object : View.OnLayoutChangeListener { - override fun onLayoutChange( - v: View, - left: Int, - top: Int, - right: Int, - bottom: Int, - oldLeft: Int, - oldTop: Int, - oldRight: Int, - oldBottom: Int - ) { - dialogContentWithBackground.removeOnLayoutChangeListener(this) - - isOriginalDialogViewLaidOut = true - maybeStartLaunchAnimation() - } - } - ) - - // Disable the dim. We will enable it once we start the animation. - window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND) - - // Override the dialog dismiss() so that we can animate the exit before actually dismissing - // the dialog. - dialog.setDismissOverride(this::onDialogDismissed) - - if (featureFlags.isPredictiveBackQsDialogAnim) { - dialog.registerAnimationOnBackInvoked(targetView = dialogContentWithBackground) - } - - // Show the dialog. - dialog.show() - moveSourceDrawingToDialog() - } - - private fun moveSourceDrawingToDialog() { - if (decorView.viewRootImpl == null) { - // Make sure that we have access to the dialog view root to move the drawing to the - // dialog overlay. - decorView.post(::moveSourceDrawingToDialog) - return - } - - // Move the drawing of the source in the overlay of this dialog, then animate. We trigger a - // one-off synchronization to make sure that this is done in sync between the two different - // windows. - controller.startDrawingInOverlayOf(decorView) - synchronizeNextDraw( - then = { - isSourceDrawnInDialog = true - maybeStartLaunchAnimation() - } - ) - } - - /** - * Synchronize the next draw of the source and dialog view roots so that they are performed at - * the same time, in the same transaction. This is necessary to make sure that the source is - * drawn in the overlay at the same time as it is removed from its original position (or - * inversely, removed from the overlay when the source is moved back to its original position). - */ - private fun synchronizeNextDraw(then: () -> Unit) { - val controllerRootView = controller.viewRoot?.view - if (forceDisableSynchronization || controllerRootView == null) { - // Don't synchronize when inside an automated test or if the controller root view is - // detached. - then() - return - } - - ViewRootSync.synchronizeNextDraw(controllerRootView, decorView, then) - decorView.invalidate() - controllerRootView.invalidate() - } - - private fun findFirstViewGroupWithBackground(view: View): ViewGroup? { - if (view !is ViewGroup) { - return null - } - - if (view.background != null) { - return view - } - - for (i in 0 until view.childCount) { - val match = findFirstViewGroupWithBackground(view.getChildAt(i)) - if (match != null) { - return match - } - } - - return null - } - - private fun maybeStartLaunchAnimation() { - if (!isSourceDrawnInDialog || !isOriginalDialogViewLaidOut) { - return - } - - // Show the background dim. - dialog.window?.addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND) - - startAnimation( - isLaunching = true, - onLaunchAnimationEnd = { - isLaunching = false - - // dismiss was called during the animation, dismiss again now to actually dismiss. - if (dismissRequested) { - dialog.dismiss() - } - - // If necessary, we animate the dialog background when its bounds change. We do it - // at the end of the launch animation, because the lauch animation already correctly - // handles bounds changes. - if (backgroundLayoutListener != null) { - dialogContentWithBackground!!.addOnLayoutChangeListener( - backgroundLayoutListener - ) - } - - if (hasInstrumentedJank) { - interactionJankMonitor.end(controller.cuj!!.cujType) - } - } - ) - } - - fun onDialogDismissed() { - if (Looper.myLooper() != Looper.getMainLooper()) { - dialog.context.mainExecutor.execute { onDialogDismissed() } - return - } - - // TODO(b/193634619): Support interrupting the launch animation in the middle. - if (isLaunching) { - dismissRequested = true - return - } - - if (isDismissing) { - return - } - - isDismissing = true - hideDialogIntoView { animationRan: Boolean -> - if (animationRan) { - // Instantly dismiss the dialog if we ran the animation into view. If it was - // skipped, dismiss() will run the window animation (which fades out the dialog). - dialog.hide() - } - - dialog.setDismissOverride(null) - dialog.dismiss() - } - } - - /** - * Hide the dialog into the source and call [onAnimationFinished] when the animation is done - * (passing animationRan=true) or if it's skipped (passing animationRan=false) to actually - * dismiss the dialog. - */ - private fun hideDialogIntoView(onAnimationFinished: (Boolean) -> Unit) { - // Remove the layout change listener we have added to the DecorView earlier. - if (decorViewLayoutListener != null) { - decorView.removeOnLayoutChangeListener(decorViewLayoutListener) - } - - if (!shouldAnimateDialogIntoSource()) { - Log.i(TAG, "Skipping animation of dialog into the source") - controller.onExitAnimationCancelled() - onAnimationFinished(false /* instantDismiss */) - onDialogDismissed(this@AnimatedDialog) - return - } - - startAnimation( - isLaunching = false, - onLaunchAnimationStart = { - // Remove the dim background as soon as we start the animation. - dialog.window?.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND) - }, - onLaunchAnimationEnd = { - val dialogContentWithBackground = this.dialogContentWithBackground!! - dialogContentWithBackground.visibility = View.INVISIBLE - - if (backgroundLayoutListener != null) { - dialogContentWithBackground.removeOnLayoutChangeListener( - backgroundLayoutListener - ) - } - - controller.stopDrawingInOverlay() - synchronizeNextDraw { - onAnimationFinished(true /* instantDismiss */) - onDialogDismissed(this@AnimatedDialog) - } - } - ) - } - - private fun startAnimation( - isLaunching: Boolean, - onLaunchAnimationStart: () -> Unit = {}, - onLaunchAnimationEnd: () -> Unit = {} - ) { - // Create 2 controllers to animate both the dialog and the source. - val startController = - if (isLaunching) { - controller.createTransitionController() - } else { - GhostedViewTransitionAnimatorController(dialogContentWithBackground!!) - } - val endController = - if (isLaunching) { - GhostedViewTransitionAnimatorController(dialogContentWithBackground!!) - } else { - controller.createExitController() - } - startController.transitionContainer = decorView - endController.transitionContainer = decorView - - val endState = endController.createAnimatorState() - val controller = - object : TransitionAnimator.Controller { - override var transitionContainer: ViewGroup - get() = startController.transitionContainer - set(value) { - startController.transitionContainer = value - endController.transitionContainer = value - } - - // We tell TransitionController that this is always a launch, and handle the launch - // vs return logic internally. - // TODO(b/323863002): maybe move the launch vs return logic out of this class and - // delegate it to TransitionController? - override val isLaunching: Boolean = true - - override fun createAnimatorState(): TransitionAnimator.State { - return startController.createAnimatorState() - } - - override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) { - // During launch, onLaunchAnimationStart will be used to remove the temporary - // touch surface ghost so it is important to call this before calling - // onLaunchAnimationStart on the controller (which will create its own ghost). - onLaunchAnimationStart() - - startController.onTransitionAnimationStart(isExpandingFullyAbove) - endController.onTransitionAnimationStart(isExpandingFullyAbove) - } - - override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) { - // onLaunchAnimationEnd is called by an Animator at the end of the animation, - // on a Choreographer animation tick. The following calls will move the animated - // content from the dialog overlay back to its original position, and this - // change must be reflected in the next frame given that we then sync the next - // frame of both the content and dialog ViewRoots. However, in case that content - // is rendered by Compose, whose compositions are also scheduled on a - // Choreographer frame, any state change made *right now* won't be reflected in - // the next frame given that a Choreographer frame can't schedule another and - // have it happen in the same frame. So we post the forwarded calls to - // [Controller.onLaunchAnimationEnd], leaving this Choreographer frame, ensuring - // that the move of the content back to its original window will be reflected in - // the next frame right after [onLaunchAnimationEnd] is called. - // - // TODO(b/330672236): Move this to TransitionAnimator. - dialog.context.mainExecutor.execute { - startController.onTransitionAnimationEnd(isExpandingFullyAbove) - endController.onTransitionAnimationEnd(isExpandingFullyAbove) - - onLaunchAnimationEnd() - } - } - - override fun onTransitionAnimationProgress( - state: TransitionAnimator.State, - progress: Float, - linearProgress: Float - ) { - startController.onTransitionAnimationProgress(state, progress, linearProgress) - - // The end view is visible only iff the starting view is not visible. - state.visible = !state.visible - endController.onTransitionAnimationProgress(state, progress, linearProgress) - - // If the dialog content is complex, its dimension might change during the - // launch animation. The animation end position might also change during the - // exit animation, for instance when locking the phone when the dialog is open. - // Therefore we update the end state to the new position/size. Usually the - // dialog dimension or position will change in the early frames, so changing the - // end state shouldn't really be noticeable. - if (endController is GhostedViewTransitionAnimatorController) { - endController.fillGhostedViewState(endState) - } - } - } - - transitionAnimator.startAnimation(controller, endState, originalDialogBackgroundColor) - } - - private fun shouldAnimateDialogIntoSource(): Boolean { - // Don't animate if the dialog was previously hidden using hide() or if we disabled the exit - // animation. - if (exitAnimationDisabled || !dialog.isShowing) { - return false - } - - // If we are dreaming, the dialog was probably closed because of that so we don't animate - // into the source. - if (callback.isDreaming()) { - return false - } - - return controller.shouldAnimateExit() - } - - /** A layout listener to animate the change of bounds of the dialog background. */ - class AnimatedBoundsLayoutListener : View.OnLayoutChangeListener { - companion object { - private const val ANIMATION_DURATION = 500L - } - - private var lastBounds: Rect? = null - private var currentAnimator: ValueAnimator? = null - - override fun onLayoutChange( - view: View, - left: Int, - top: Int, - right: Int, - bottom: Int, - oldLeft: Int, - oldTop: Int, - oldRight: Int, - oldBottom: Int - ) { - // Don't animate if bounds didn't actually change. - if (left == oldLeft && top == oldTop && right == oldRight && bottom == oldBottom) { - // Make sure that we that the last bounds set by the animator were not overridden. - lastBounds?.let { bounds -> - view.left = bounds.left - view.top = bounds.top - view.right = bounds.right - view.bottom = bounds.bottom - } - return - } - - if (lastBounds == null) { - lastBounds = Rect(oldLeft, oldTop, oldRight, oldBottom) - } - - val bounds = lastBounds!! - val startLeft = bounds.left - val startTop = bounds.top - val startRight = bounds.right - val startBottom = bounds.bottom - - currentAnimator?.cancel() - currentAnimator = null - - val animator = - ValueAnimator.ofFloat(0f, 1f).apply { - duration = ANIMATION_DURATION - interpolator = Interpolators.STANDARD - - addListener( - object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - currentAnimator = null - } - } - ) - - addUpdateListener { animatedValue -> - val progress = animatedValue.animatedFraction - - // Compute new bounds. - bounds.left = MathUtils.lerp(startLeft, left, progress).roundToInt() - bounds.top = MathUtils.lerp(startTop, top, progress).roundToInt() - bounds.right = MathUtils.lerp(startRight, right, progress).roundToInt() - bounds.bottom = MathUtils.lerp(startBottom, bottom, progress).roundToInt() - - // Set the new bounds. - view.left = bounds.left - view.top = bounds.top - view.right = bounds.right - view.bottom = bounds.bottom - } - } - - currentAnimator = animator - animator.start() - } - } - - fun prepareForStackDismiss() { - if (parentAnimatedDialog == null) { - return - } - parentAnimatedDialog.exitAnimationDisabled = true - parentAnimatedDialog.dialog.hide() - parentAnimatedDialog.prepareForStackDismiss() - parentAnimatedDialog.dialog.dismiss() - } -} diff --git a/systemUIAnim/src/com/android/systemui/animation/Expandable.kt b/systemUIAnim/src/com/android/systemui/animation/Expandable.kt deleted file mode 100644 index 3ba9a29748..0000000000 --- a/systemUIAnim/src/com/android/systemui/animation/Expandable.kt +++ /dev/null @@ -1,102 +0,0 @@ -/* - * 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.animation - -import android.content.ComponentName -import android.view.View - -/** A piece of UI that can be expanded into a Dialog or an Activity. */ -interface Expandable { - /** - * Create an [ActivityTransitionAnimator.Controller] that can be used to expand this - * [Expandable] into an Activity, or return `null` if this [Expandable] should not be animated - * (e.g. if it is currently not attached or visible). - * - * @param launchCujType The CUJ type from the [com.android.internal.jank.InteractionJankMonitor] - * associated to the launch that will use this controller. - * @param cookie The unique cookie associated with the launch that will use this controller. - * This is required iff a return animation should be included. - * @param component The name of the activity that will be launched by this controller. This is - * required for long-lived registrations only. - * @param returnCujType The CUJ type from the [com.android.internal.jank.InteractionJankMonitor] - * associated to the return animation that will use this controller. - */ - fun activityTransitionController( - launchCujType: Int? = null, - cookie: ActivityTransitionAnimator.TransitionCookie? = null, - component: ComponentName? = null, - returnCujType: Int? = null - ): ActivityTransitionAnimator.Controller? - - /** - * See [activityTransitionController] above. - * - * Interfaces don't support [JvmOverloads], so this is a useful overload for Java usages that - * don't use the return-related parameters. - */ - fun activityTransitionController( - launchCujType: Int? = null - ): ActivityTransitionAnimator.Controller? { - return activityTransitionController( - launchCujType, - cookie = null, - component = null, - returnCujType = null - ) - } - - /** - * Create a [DialogTransitionAnimator.Controller] that can be used to expand this [Expandable] - * into a Dialog, or return `null` if this [Expandable] should not be animated (e.g. if it is - * currently not attached or visible). - */ - fun dialogTransitionController(cuj: DialogCuj? = null): DialogTransitionAnimator.Controller? - - companion object { - /** - * Create an [Expandable] that will animate [view] when expanded. - * - * Note: The background of [view] should be a (rounded) rectangle so that it can be properly - * animated. - */ - @JvmStatic - fun fromView(view: View): Expandable { - return object : Expandable { - override fun activityTransitionController( - launchCujType: Int?, - cookie: ActivityTransitionAnimator.TransitionCookie?, - component: ComponentName?, - returnCujType: Int? - ): ActivityTransitionAnimator.Controller? { - return ActivityTransitionAnimator.Controller.fromView( - view, - launchCujType, - cookie, - component, - returnCujType - ) - } - - override fun dialogTransitionController( - cuj: DialogCuj? - ): DialogTransitionAnimator.Controller? { - return DialogTransitionAnimator.Controller.fromView(view, cuj) - } - } - } - } -} diff --git a/systemUIAnim/src/com/android/systemui/animation/FontInterpolator.kt b/systemUIAnim/src/com/android/systemui/animation/FontInterpolator.kt deleted file mode 100644 index addabcc028..0000000000 --- a/systemUIAnim/src/com/android/systemui/animation/FontInterpolator.kt +++ /dev/null @@ -1,238 +0,0 @@ -/* - * Copyright (C) 2020 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.animation - -import android.graphics.fonts.Font -import android.graphics.fonts.FontVariationAxis -import android.util.Log -import android.util.LruCache -import android.util.MathUtils -import androidx.annotation.VisibleForTesting -import java.lang.Float.max -import java.lang.Float.min - -private const val TAG_WGHT = "wght" -private const val TAG_ITAL = "ital" - -private const val FONT_WEIGHT_DEFAULT_VALUE = 400f -private const val FONT_ITALIC_MAX = 1f -private const val FONT_ITALIC_MIN = 0f -private const val FONT_ITALIC_ANIMATION_STEP = 0.1f -private const val FONT_ITALIC_DEFAULT_VALUE = 0f - -// Benchmarked via Perfetto, difference between 10 and 50 entries is about 0.3ms in -// frame draw time on a Pixel 6. -@VisibleForTesting const val DEFAULT_FONT_CACHE_MAX_ENTRIES = 10 - -/** Provide interpolation of two fonts by adjusting font variation settings. */ -class FontInterpolator( - numberOfAnimationSteps: Int? = null, -) { - /** - * Cache key for the interpolated font. - * - * This class is mutable for recycling. - */ - private data class InterpKey(var l: Font?, var r: Font?, var progress: Float) { - fun set(l: Font, r: Font, progress: Float) { - this.l = l - this.r = r - this.progress = progress - } - } - - /** - * Cache key for the font that has variable font. - * - * This class is mutable for recycling. - */ - private data class VarFontKey( - var sourceId: Int, - var index: Int, - val sortedAxes: MutableList - ) { - constructor( - font: Font, - axes: List - ) : this( - font.sourceIdentifier, - font.ttcIndex, - axes.toMutableList().apply { sortBy { it.tag } } - ) - - fun set(font: Font, axes: List) { - sourceId = font.sourceIdentifier - index = font.ttcIndex - sortedAxes.clear() - sortedAxes.addAll(axes) - sortedAxes.sortBy { it.tag } - } - } - - // Font interpolator has two level caches: one for input and one for font with different - // variation settings. No synchronization is needed since FontInterpolator is not designed to be - // thread-safe and can be used only on UI thread. - val cacheMaxEntries = numberOfAnimationSteps?.let { it * 2 } ?: DEFAULT_FONT_CACHE_MAX_ENTRIES - private val interpCache = LruCache(cacheMaxEntries) - private val verFontCache = LruCache(cacheMaxEntries) - - // Mutable keys for recycling. - private val tmpInterpKey = InterpKey(null, null, 0f) - private val tmpVarFontKey = VarFontKey(0, 0, mutableListOf()) - - /** Linear interpolate the font variation settings. */ - fun lerp(start: Font, end: Font, progress: Float): Font { - if (progress == 0f) { - return start - } else if (progress == 1f) { - return end - } - - val startAxes = start.axes ?: EMPTY_AXES - val endAxes = end.axes ?: EMPTY_AXES - - if (startAxes.isEmpty() && endAxes.isEmpty()) { - return start - } - - // Check we already know the result. This is commonly happens since we draws the different - // text chunks with the same font. - tmpInterpKey.set(start, end, progress) - val cachedFont = interpCache[tmpInterpKey] - if (cachedFont != null) { - if (DEBUG) { - Log.d(LOG_TAG, "[$progress] Interp. cache hit for $tmpInterpKey") - } - return cachedFont - } - - // General axes interpolation takes O(N log N), this is came from sorting the axes. Usually - // this doesn't take much time since the variation axes is usually up to 5. If we need to - // support more number of axes, we may want to preprocess the font and store the sorted axes - // and also pre-fill the missing axes value with default value from 'fvar' table. - val newAxes = - lerp(startAxes, endAxes) { tag, startValue, endValue -> - when (tag) { - TAG_WGHT -> - MathUtils.lerp( - startValue ?: FONT_WEIGHT_DEFAULT_VALUE, - endValue ?: FONT_WEIGHT_DEFAULT_VALUE, - progress - ) - TAG_ITAL -> - adjustItalic( - MathUtils.lerp( - startValue ?: FONT_ITALIC_DEFAULT_VALUE, - endValue ?: FONT_ITALIC_DEFAULT_VALUE, - progress - ) - ) - else -> { - require(startValue != null && endValue != null) { - "Unable to interpolate due to unknown default axes value : $tag" - } - MathUtils.lerp(startValue, endValue, progress) - } - } - } - - // Check if we already make font for this axes. This is typically happens if the animation - // happens backward. - tmpVarFontKey.set(start, newAxes) - val axesCachedFont = verFontCache[tmpVarFontKey] - if (axesCachedFont != null) { - interpCache.put(InterpKey(start, end, progress), axesCachedFont) - if (DEBUG) { - Log.d(LOG_TAG, "[$progress] Axis cache hit for $tmpVarFontKey") - } - return axesCachedFont - } - - // This is the first time to make the font for the axes. Build and store it to the cache. - // Font.Builder#build won't throw IOException since creating fonts from existing fonts will - // not do any IO work. - val newFont = Font.Builder(start).setFontVariationSettings(newAxes.toTypedArray()).build() - interpCache.put(InterpKey(start, end, progress), newFont) - verFontCache.put(VarFontKey(start, newAxes), newFont) - - // Cache misses are likely to create memory leaks, so this is logged at error level. - Log.e(LOG_TAG, "[$progress] Cache MISS for $tmpInterpKey / $tmpVarFontKey") - return newFont - } - - private fun lerp( - start: Array, - end: Array, - filter: (tag: String, left: Float?, right: Float?) -> Float - ): List { - // Safe to modify result of Font#getAxes since it returns cloned object. - start.sortBy { axis -> axis.tag } - end.sortBy { axis -> axis.tag } - - val result = mutableListOf() - var i = 0 - var j = 0 - while (i < start.size || j < end.size) { - val tagA = if (i < start.size) start[i].tag else null - val tagB = if (j < end.size) end[j].tag else null - - val comp = - when { - tagA == null -> 1 - tagB == null -> -1 - else -> tagA.compareTo(tagB) - } - - val axis = - when { - comp == 0 -> { - val v = filter(tagA!!, start[i++].styleValue, end[j++].styleValue) - FontVariationAxis(tagA, v) - } - comp < 0 -> { - val v = filter(tagA!!, start[i++].styleValue, null) - FontVariationAxis(tagA, v) - } - else -> { // comp > 0 - val v = filter(tagB!!, null, end[j++].styleValue) - FontVariationAxis(tagB, v) - } - } - - result.add(axis) - } - return result - } - - // For the performance reasons, we animate italic with FONT_ITALIC_ANIMATION_STEP. This helps - // Cache hit ratio in the Skia glyph cache. - private fun adjustItalic(value: Float) = - coerceInWithStep(value, FONT_ITALIC_MIN, FONT_ITALIC_MAX, FONT_ITALIC_ANIMATION_STEP) - - private fun coerceInWithStep(v: Float, min: Float, max: Float, step: Float) = - (v.coerceIn(min, max) / step).toInt() * step - - companion object { - private const val LOG_TAG = "FontInterpolator" - private val DEBUG = Log.isLoggable(LOG_TAG, Log.DEBUG) - private val EMPTY_AXES = arrayOf() - - // Returns true if given two font instance can be interpolated. - fun canInterpolate(start: Font, end: Font) = - start.ttcIndex == end.ttcIndex && start.sourceIdentifier == end.sourceIdentifier - } -} diff --git a/systemUIAnim/src/com/android/systemui/animation/FontVariationUtils.kt b/systemUIAnim/src/com/android/systemui/animation/FontVariationUtils.kt deleted file mode 100644 index 78ae4af258..0000000000 --- a/systemUIAnim/src/com/android/systemui/animation/FontVariationUtils.kt +++ /dev/null @@ -1,59 +0,0 @@ -package com.android.systemui.animation - -private const val TAG_WGHT = "wght" -private const val TAG_WDTH = "wdth" -private const val TAG_OPSZ = "opsz" -private const val TAG_ROND = "ROND" - -class FontVariationUtils { - private var mWeight = -1 - private var mWidth = -1 - private var mOpticalSize = -1 - private var mRoundness = -1 - private var isUpdated = false - - /* - * generate fontVariationSettings string, used for key in typefaceCache in TextAnimator - * the order of axes should align to the order of parameters - * if every axis remains unchanged, return "" - */ - fun updateFontVariation( - weight: Int = -1, - width: Int = -1, - opticalSize: Int = -1, - roundness: Int = -1 - ): String { - isUpdated = false - if (weight >= 0 && mWeight != weight) { - isUpdated = true - mWeight = weight - } - if (width >= 0 && mWidth != width) { - isUpdated = true - mWidth = width - } - if (opticalSize >= 0 && mOpticalSize != opticalSize) { - isUpdated = true - mOpticalSize = opticalSize - } - - if (roundness >= 0 && mRoundness != roundness) { - isUpdated = true - mRoundness = roundness - } - var resultString = "" - if (mWeight >= 0) { - resultString += "'$TAG_WGHT' $mWeight" - } - if (mWidth >= 0) { - resultString += (if (resultString.isBlank()) "" else ", ") + "'$TAG_WDTH' $mWidth" - } - if (mOpticalSize >= 0) { - resultString += (if (resultString.isBlank()) "" else ", ") + "'$TAG_OPSZ' $mOpticalSize" - } - if (mRoundness >= 0) { - resultString += (if (resultString.isBlank()) "" else ", ") + "'$TAG_ROND' $mRoundness" - } - return if (isUpdated) resultString else "" - } -} diff --git a/systemUIAnim/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt b/systemUIAnim/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt deleted file mode 100644 index e626c04675..0000000000 --- a/systemUIAnim/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt +++ /dev/null @@ -1,532 +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.animation - -import android.content.ComponentName -import android.graphics.Canvas -import android.graphics.ColorFilter -import android.graphics.Insets -import android.graphics.Matrix -import android.graphics.PixelFormat -import android.graphics.Rect -import android.graphics.drawable.Drawable -import android.graphics.drawable.GradientDrawable -import android.graphics.drawable.InsetDrawable -import android.graphics.drawable.LayerDrawable -import android.graphics.drawable.StateListDrawable -import android.util.Log -import android.view.GhostView -import android.view.View -import android.view.ViewGroup -import android.view.ViewGroupOverlay -import android.widget.FrameLayout -import com.android.internal.jank.Cuj.CujType -import com.android.internal.jank.InteractionJankMonitor -import java.util.LinkedList -import kotlin.math.min -import kotlin.math.roundToInt - -private const val TAG = "GhostedViewTransitionAnimatorController" - -/** - * A base implementation of [ActivityTransitionAnimator.Controller] which creates a - * [ghost][GhostView] of [ghostedView] as well as an expandable background view, which are drawn and - * animated instead of the ghosted view. - * - * Important: [ghostedView] must be attached to a [ViewGroup] when calling this function and during - * the animation. It must also implement [LaunchableView], otherwise an exception will be thrown - * during this controller instantiation. - * - * Note: Avoid instantiating this directly and call [ActivityTransitionAnimator.Controller.fromView] - * whenever possible instead. - */ -open class GhostedViewTransitionAnimatorController -@JvmOverloads -constructor( - /** The view that will be ghosted and from which the background will be extracted. */ - private val ghostedView: View, - - /** The [CujType] associated to this launch animation. */ - private val launchCujType: Int? = null, - override val transitionCookie: ActivityTransitionAnimator.TransitionCookie? = null, - override val component: ComponentName? = null, - - /** The [CujType] associated to this return animation. */ - private val returnCujType: Int? = null, - private var interactionJankMonitor: InteractionJankMonitor = - InteractionJankMonitor.getInstance(), -) : ActivityTransitionAnimator.Controller { - override val isLaunching: Boolean = true - - /** The container to which we will add the ghost view and expanding background. */ - override var transitionContainer = ghostedView.rootView as ViewGroup - private val transitionContainerOverlay: ViewGroupOverlay - get() = transitionContainer.overlay - - private val transitionContainerLocation = IntArray(2) - - /** The ghost view that is drawn and animated instead of the ghosted view. */ - private var ghostView: GhostView? = null - private val initialGhostViewMatrixValues = FloatArray(9) { 0f } - private val ghostViewMatrix = Matrix() - - /** - * The expanding background view that will be added to [transitionContainer] (below [ghostView]) - * and animate. - */ - private var backgroundView: FrameLayout? = null - - /** - * The drawable wrapping the [ghostedView] background and used as background for - * [backgroundView]. - */ - private var backgroundDrawable: WrappedDrawable? = null - private val backgroundInsets by lazy { background?.opticalInsets ?: Insets.NONE } - private var startBackgroundAlpha: Int = 0xFF - - private val ghostedViewLocation = IntArray(2) - private val ghostedViewState = TransitionAnimator.State() - - /** - * The background of the [ghostedView]. This background will be used to draw the background of - * the background view that is expanding up to the final animation position. - * - * Note that during the animation, the alpha value value of this background will be set to 0, - * then set back to its initial value at the end of the animation. - */ - private val background: Drawable? - - /** CUJ identifier accounting for whether this controller is for a launch or a return. */ - private val cujType: Int? - get() = - if (isLaunching) { - launchCujType - } else { - returnCujType - } - - init { - // Make sure the View we launch from implements LaunchableView to avoid visibility issues. - if (ghostedView !is LaunchableView) { - throw IllegalArgumentException( - "A GhostedViewLaunchAnimatorController was created from a View that does not " + - "implement LaunchableView. This can lead to subtle bugs where the visibility " + - "of the View we are launching from is not what we expected." - ) - } - - /** Find the first view with a background in [view] and its children. */ - fun findBackground(view: View): Drawable? { - if (view.background != null) { - return view.background - } - - // Perform a BFS to find the largest View with background. - val views = LinkedList().apply { add(view) } - - while (views.isNotEmpty()) { - val v = views.removeAt(0) - if (v.background != null) { - return v.background - } - - if (v is ViewGroup) { - for (i in 0 until v.childCount) { - views.add(v.getChildAt(i)) - } - } - } - - return null - } - - background = findBackground(ghostedView) - } - - /** - * Set the corner radius of [background]. The background is the one that was returned by - * [getBackground]. - */ - protected open fun setBackgroundCornerRadius( - background: Drawable, - topCornerRadius: Float, - bottomCornerRadius: Float - ) { - // By default, we rely on WrappedDrawable to set/restore the background radii before/after - // each draw. - backgroundDrawable?.setBackgroundRadius(topCornerRadius, bottomCornerRadius) - } - - /** Return the current top corner radius of the background. */ - protected open fun getCurrentTopCornerRadius(): Float { - val drawable = background ?: return 0f - val gradient = findGradientDrawable(drawable) ?: return 0f - - // TODO(b/184121838): Support more than symmetric top & bottom radius. - val radius = gradient.cornerRadii?.get(CORNER_RADIUS_TOP_INDEX) ?: gradient.cornerRadius - return radius * ghostedView.scaleX - } - - /** Return the current bottom corner radius of the background. */ - protected open fun getCurrentBottomCornerRadius(): Float { - val drawable = background ?: return 0f - val gradient = findGradientDrawable(drawable) ?: return 0f - - // TODO(b/184121838): Support more than symmetric top & bottom radius. - val radius = gradient.cornerRadii?.get(CORNER_RADIUS_BOTTOM_INDEX) ?: gradient.cornerRadius - return radius * ghostedView.scaleX - } - - override fun createAnimatorState(): TransitionAnimator.State { - val state = - TransitionAnimator.State( - topCornerRadius = getCurrentTopCornerRadius(), - bottomCornerRadius = getCurrentBottomCornerRadius() - ) - fillGhostedViewState(state) - return state - } - - fun fillGhostedViewState(state: TransitionAnimator.State) { - // For the animation we are interested in the area that has a non transparent background, - // so we have to take the optical insets into account. - ghostedView.getLocationOnScreen(ghostedViewLocation) - val insets = backgroundInsets - val boundCorrections: Rect = - if (ghostedView is LaunchableView) { - ghostedView.getPaddingForLaunchAnimation() - } else { - Rect() - } - state.top = ghostedViewLocation[1] + insets.top + boundCorrections.top - state.bottom = - ghostedViewLocation[1] + (ghostedView.height * ghostedView.scaleY).roundToInt() - - insets.bottom + boundCorrections.bottom - state.left = ghostedViewLocation[0] + insets.left + boundCorrections.left - state.right = - ghostedViewLocation[0] + (ghostedView.width * ghostedView.scaleX).roundToInt() - - insets.right + boundCorrections.right - } - - override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) { - if (ghostedView.parent !is ViewGroup) { - // This should usually not happen, but let's make sure we don't crash if the view was - // detached right before we started the animation. - Log.w(TAG, "Skipping animation as ghostedView is not attached to a ViewGroup") - return - } - - backgroundView = - FrameLayout(transitionContainer.context).also { transitionContainerOverlay.add(it) } - - // We wrap the ghosted view background and use it to draw the expandable background. Its - // alpha will be set to 0 as soon as we start drawing the expanding background. - startBackgroundAlpha = background?.alpha ?: 0xFF - backgroundDrawable = WrappedDrawable(background) - backgroundView?.background = backgroundDrawable - - // Delay the calls to `ghostedView.setVisibility()` during the animation. This must be - // called before `GhostView.addGhost()` is called because the latter will change the - // *transition* visibility, which won't be blocked and will affect the normal View - // visibility that is saved by `setShouldBlockVisibilityChanges()` for a later restoration. - (ghostedView as? LaunchableView)?.setShouldBlockVisibilityChanges(true) - - // Create a ghost of the view that will be moving and fading out. This allows to fade out - // the content before fading out the background. - ghostView = GhostView.addGhost(ghostedView, transitionContainer) - - // [GhostView.addGhost], the result of which is our [ghostView], creates a [GhostView], and - // adds it first to a [FrameLayout] container. It then adds _that_ container to an - // [OverlayViewGroup]. We need to turn off clipping for that container view. Currently, - // however, the only way to get a reference to that overlay is by going through our - // [ghostView]. The [OverlayViewGroup] will always be its grandparent view. - // TODO(b/306652954) reference the overlay view group directly if we can - (ghostView?.parent?.parent as? ViewGroup)?.let { - it.clipChildren = false - it.clipToPadding = false - } - - val matrix = ghostView?.animationMatrix ?: Matrix.IDENTITY_MATRIX - matrix.getValues(initialGhostViewMatrixValues) - - cujType?.let { interactionJankMonitor.begin(ghostedView, it) } - } - - override fun onTransitionAnimationProgress( - state: TransitionAnimator.State, - progress: Float, - linearProgress: Float - ) { - val ghostView = this.ghostView ?: return - val backgroundView = this.backgroundView!! - - if (!state.visible || !ghostedView.isAttachedToWindow) { - if (ghostView.visibility == View.VISIBLE) { - // Making the ghost view invisible will make the ghosted view visible, so order is - // important here. - ghostView.visibility = View.INVISIBLE - - // Make the ghosted view invisible again. We use the transition visibility like - // GhostView does so that we don't mess up with the accessibility tree (see - // b/204944038#comment17). - ghostedView.setTransitionVisibility(View.INVISIBLE) - backgroundView.visibility = View.INVISIBLE - } - return - } - - // The ghost and backgrounds views were made invisible earlier. That can for instance happen - // when animating a dialog into a view. - if (ghostView.visibility == View.INVISIBLE) { - ghostView.visibility = View.VISIBLE - backgroundView.visibility = View.VISIBLE - } - - fillGhostedViewState(ghostedViewState) - val leftChange = state.left - ghostedViewState.left - val rightChange = state.right - ghostedViewState.right - val topChange = state.top - ghostedViewState.top - val bottomChange = state.bottom - ghostedViewState.bottom - - val widthRatio = state.width.toFloat() / ghostedViewState.width - val heightRatio = state.height.toFloat() / ghostedViewState.height - val scale = min(widthRatio, heightRatio) - - if (ghostedView.parent is ViewGroup) { - // Recalculate the matrix in case the ghosted view moved. We ensure that the ghosted - // view is still attached to a ViewGroup, otherwise calculateMatrix will throw. - GhostView.calculateMatrix(ghostedView, transitionContainer, ghostViewMatrix) - } - - transitionContainer.getLocationOnScreen(transitionContainerLocation) - ghostViewMatrix.postScale( - scale, - scale, - ghostedViewState.centerX - transitionContainerLocation[0], - ghostedViewState.centerY - transitionContainerLocation[1] - ) - ghostViewMatrix.postTranslate( - (leftChange + rightChange) / 2f, - (topChange + bottomChange) / 2f - ) - ghostView.animationMatrix = ghostViewMatrix - - // We need to take into account the background insets for the background position. - val insets = backgroundInsets - val topWithInsets = state.top - insets.top - val leftWithInsets = state.left - insets.left - val rightWithInsets = state.right + insets.right - val bottomWithInsets = state.bottom + insets.bottom - - backgroundView.top = topWithInsets - transitionContainerLocation[1] - backgroundView.bottom = bottomWithInsets - transitionContainerLocation[1] - backgroundView.left = leftWithInsets - transitionContainerLocation[0] - backgroundView.right = rightWithInsets - transitionContainerLocation[0] - - val backgroundDrawable = backgroundDrawable!! - backgroundDrawable.wrapped?.let { - setBackgroundCornerRadius(it, state.topCornerRadius, state.bottomCornerRadius) - } - } - - override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) { - if (ghostView == null) { - // We didn't actually run the animation. - return - } - - cujType?.let { interactionJankMonitor.end(it) } - - backgroundDrawable?.wrapped?.alpha = startBackgroundAlpha - - GhostView.removeGhost(ghostedView) - backgroundView?.let { transitionContainerOverlay.remove(it) } - - if (ghostedView is LaunchableView) { - // Restore the ghosted view visibility. - ghostedView.setShouldBlockVisibilityChanges(false) - ghostedView.onActivityLaunchAnimationEnd() - } else { - // Make the ghosted view visible. We ensure that the view is considered VISIBLE by - // accessibility by first making it INVISIBLE then VISIBLE (see b/204944038#comment17 - // for more info). - ghostedView.visibility = View.INVISIBLE - ghostedView.visibility = View.VISIBLE - ghostedView.invalidate() - } - } - - companion object { - private const val CORNER_RADIUS_TOP_INDEX = 0 - private const val CORNER_RADIUS_BOTTOM_INDEX = 4 - - /** - * Return the first [GradientDrawable] found in [drawable], or null if none is found. If - * [drawable] is a [LayerDrawable], this will return the first layer that has a - * [GradientDrawable]. - */ - fun findGradientDrawable(drawable: Drawable): GradientDrawable? { - if (drawable is GradientDrawable) { - return drawable - } - - if (drawable is InsetDrawable) { - return drawable.drawable?.let { findGradientDrawable(it) } - } - - if (drawable is LayerDrawable) { - for (i in 0 until drawable.numberOfLayers) { - val maybeGradient = findGradientDrawable(drawable.getDrawable(i)) - if (maybeGradient != null) { - return maybeGradient - } - } - } - - if (drawable is StateListDrawable) { - return findGradientDrawable(drawable.current) - } - - return null - } - } - - private class WrappedDrawable(val wrapped: Drawable?) : Drawable() { - private var currentAlpha = 0xFF - private var previousBounds = Rect() - - private var cornerRadii = FloatArray(8) { -1f } - private var previousCornerRadii = FloatArray(8) - - override fun draw(canvas: Canvas) { - val wrapped = this.wrapped ?: return - - wrapped.copyBounds(previousBounds) - - wrapped.alpha = currentAlpha - wrapped.bounds = bounds - applyBackgroundRadii() - - wrapped.draw(canvas) - - // The background view (and therefore this drawable) is drawn before the ghost view, so - // the ghosted view background alpha should always be 0 when it is drawn above the - // background. - wrapped.alpha = 0 - wrapped.bounds = previousBounds - restoreBackgroundRadii() - } - - override fun setAlpha(alpha: Int) { - if (alpha != currentAlpha) { - currentAlpha = alpha - invalidateSelf() - } - } - - override fun getAlpha() = currentAlpha - - override fun getOpacity(): Int { - val wrapped = this.wrapped ?: return PixelFormat.TRANSPARENT - - val previousAlpha = wrapped.alpha - wrapped.alpha = currentAlpha - val opacity = wrapped.opacity - wrapped.alpha = previousAlpha - return opacity - } - - override fun setColorFilter(filter: ColorFilter?) { - wrapped?.colorFilter = filter - } - - fun setBackgroundRadius(topCornerRadius: Float, bottomCornerRadius: Float) { - updateRadii(cornerRadii, topCornerRadius, bottomCornerRadius) - invalidateSelf() - } - - private fun updateRadii( - radii: FloatArray, - topCornerRadius: Float, - bottomCornerRadius: Float - ) { - radii[0] = topCornerRadius - radii[1] = topCornerRadius - radii[2] = topCornerRadius - radii[3] = topCornerRadius - - radii[4] = bottomCornerRadius - radii[5] = bottomCornerRadius - radii[6] = bottomCornerRadius - radii[7] = bottomCornerRadius - } - - private fun applyBackgroundRadii() { - if (cornerRadii[0] < 0 || wrapped == null) { - return - } - - savePreviousBackgroundRadii(wrapped) - applyBackgroundRadii(wrapped, cornerRadii) - } - - private fun savePreviousBackgroundRadii(background: Drawable) { - // TODO(b/184121838): This method assumes that all GradientDrawable in background will - // have the same radius. Should we save/restore the radii for each layer instead? - val gradient = findGradientDrawable(background) ?: return - - // TODO(b/184121838): GradientDrawable#getCornerRadii clones its radii array. Should we - // try to avoid that? - val radii = gradient.cornerRadii - if (radii != null) { - radii.copyInto(previousCornerRadii) - } else { - // Copy the cornerRadius into previousCornerRadii. - val radius = gradient.cornerRadius - updateRadii(previousCornerRadii, radius, radius) - } - } - - private fun applyBackgroundRadii(drawable: Drawable, radii: FloatArray) { - if (drawable is GradientDrawable) { - drawable.cornerRadii = radii - return - } - - if (drawable is InsetDrawable) { - drawable.drawable?.let { applyBackgroundRadii(it, radii) } - return - } - - if (drawable !is LayerDrawable) { - return - } - - for (i in 0 until drawable.numberOfLayers) { - applyBackgroundRadii(drawable.getDrawable(i), radii) - } - } - - private fun restoreBackgroundRadii() { - if (cornerRadii[0] < 0 || wrapped == null) { - return - } - - applyBackgroundRadii(wrapped, previousCornerRadii) - } - } -} diff --git a/systemUIAnim/src/com/android/systemui/animation/LaunchableView.kt b/systemUIAnim/src/com/android/systemui/animation/LaunchableView.kt deleted file mode 100644 index 330ab0f7fc..0000000000 --- a/systemUIAnim/src/com/android/systemui/animation/LaunchableView.kt +++ /dev/null @@ -1,96 +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.animation - -import android.graphics.Rect -import android.view.View - -/** A view that can expand/launch into an app or a dialog. */ -interface LaunchableView { - /** - * Set whether this view should block/postpone all calls to [View.setVisibility]. This ensures - * that this view: - * - remains invisible during the launch animation given that it is ghosted and already drawn - * somewhere else. - * - remains invisible as long as a dialog expanded from it is shown. - * - restores its expected visibility once the dialog expanded from it is dismissed. - * - * When `setShouldBlockVisibilityChanges(false)` is called, then visibility of the View should - * be restored to its expected value, i.e. it should have the visibility of the last call to - * `View.setVisibility()` that was made after `setShouldBlockVisibilityChanges(true)`, if any, - * or the original view visibility otherwise. - * - * Note that calls to [View.setTransitionVisibility] shouldn't be blocked. - * - * @param block whether we should block/postpone all calls to `setVisibility`. - */ - fun setShouldBlockVisibilityChanges(block: Boolean) - - /** Perform an action when the activity launch animation ends */ - fun onActivityLaunchAnimationEnd() {} - - /** Provide an optional correction applied to the visible area during a launch animation */ - fun getPaddingForLaunchAnimation(): Rect = Rect() -} - -/** A delegate that can be used by views to make the implementation of [LaunchableView] easier. */ -class LaunchableViewDelegate( - private val view: View, - - /** - * The lambda that should set the actual visibility of [view], usually by calling - * super.setVisibility(visibility). - */ - private val superSetVisibility: (Int) -> Unit, -) : LaunchableView { - private var blockVisibilityChanges = false - private var lastVisibility = view.visibility - - /** Call this when [LaunchableView.setShouldBlockVisibilityChanges] is called. */ - override fun setShouldBlockVisibilityChanges(block: Boolean) { - if (block == blockVisibilityChanges) { - return - } - - blockVisibilityChanges = block - if (block) { - // Save the current visibility for later. - lastVisibility = view.visibility - } else { - // Restore the visibility. To avoid accessibility issues, we change the visibility twice - // which makes sure that we trigger a visibility flag change (see b/204944038#comment17 - // for more info). - if (lastVisibility == View.VISIBLE) { - superSetVisibility(View.INVISIBLE) - superSetVisibility(View.VISIBLE) - } else { - superSetVisibility(View.VISIBLE) - superSetVisibility(lastVisibility) - } - } - } - - /** Call this when [View.setVisibility] is called. */ - fun setVisibility(visibility: Int) { - if (blockVisibilityChanges) { - lastVisibility = visibility - return - } - - superSetVisibility(visibility) - } -} diff --git a/systemUIAnim/src/com/android/systemui/animation/RemoteAnimationDelegate.kt b/systemUIAnim/src/com/android/systemui/animation/RemoteAnimationDelegate.kt deleted file mode 100644 index d465962d6e..0000000000 --- a/systemUIAnim/src/com/android/systemui/animation/RemoteAnimationDelegate.kt +++ /dev/null @@ -1,30 +0,0 @@ -package com.android.systemui.animation - -import android.annotation.UiThread -import android.view.IRemoteAnimationFinishedCallback -import android.view.RemoteAnimationTarget -import android.view.WindowManager - -/** - * A component capable of running remote animations. - * - * Expands the IRemoteAnimationRunner API by allowing for different types of more specialized - * callbacks. - */ -interface RemoteAnimationDelegate { - /** - * Called on the UI thread when the animation targets are received. Sets up and kicks off the - * animation. - */ - @UiThread - fun onAnimationStart( - @WindowManager.TransitionOldType transit: Int, - apps: Array?, - wallpapers: Array?, - nonApps: Array?, - callback: T? - ) - - /** Called on the UI thread when a signal is received to cancel the animation. */ - @UiThread fun onAnimationCancelled() -} diff --git a/systemUIAnim/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java b/systemUIAnim/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java deleted file mode 100644 index 94f884673f..0000000000 --- a/systemUIAnim/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java +++ /dev/null @@ -1,239 +0,0 @@ -/* - * Copyright (C) 2024 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.animation; - -import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; -import static android.view.WindowManager.TRANSIT_CLOSE; -import static android.view.WindowManager.TRANSIT_OLD_NONE; -import static android.view.WindowManager.TRANSIT_OPEN; -import static android.view.WindowManager.TRANSIT_TO_BACK; -import static android.view.WindowManager.TRANSIT_TO_FRONT; -import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; - -import android.os.IBinder; -import android.os.RemoteException; -import android.util.ArrayMap; -import android.util.Log; -import android.view.IRemoteAnimationFinishedCallback; -import android.view.IRemoteAnimationRunner; -import android.view.RemoteAnimationTarget; -import android.view.SurfaceControl; -import android.view.WindowManager; -import android.view.WindowManager.TransitionOldType; -import android.window.IRemoteTransition; -import android.window.IRemoteTransitionFinishedCallback; -import android.window.RemoteTransitionStub; -import android.window.TransitionInfo; - -import com.android.wm.shell.shared.CounterRotator; - -public abstract class RemoteAnimationRunnerCompat extends IRemoteAnimationRunner.Stub { - private static final String TAG = "RemoteAnimRunnerCompat"; - - public abstract void onAnimationStart(@WindowManager.TransitionOldType int transit, - RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers, - RemoteAnimationTarget[] nonApps, Runnable finishedCallback); - - @Override - public final void onAnimationStart(@TransitionOldType int transit, - RemoteAnimationTarget[] apps, - RemoteAnimationTarget[] wallpapers, - RemoteAnimationTarget[] nonApps, - final IRemoteAnimationFinishedCallback finishedCallback) { - - onAnimationStart(transit, apps, wallpapers, - nonApps, () -> { - try { - finishedCallback.onAnimationFinished(); - } catch (RemoteException e) { - Log.e(TAG, "Failed to call app controlled animation finished callback", e); - } - }); - } - - public IRemoteTransition toRemoteTransition() { - return wrap(this); - } - - /** Wraps a remote animation runner in a remote-transition. */ - public static RemoteTransitionStub wrap(IRemoteAnimationRunner runner) { - return new RemoteTransitionStub() { - final ArrayMap mFinishRunnables = new ArrayMap<>(); - - @Override - public void startAnimation(IBinder token, TransitionInfo info, - SurfaceControl.Transaction t, - IRemoteTransitionFinishedCallback finishCallback) throws RemoteException { - final ArrayMap leashMap = new ArrayMap<>(); - final RemoteAnimationTarget[] apps = - RemoteAnimationTargetCompat.wrapApps(info, t, leashMap); - final RemoteAnimationTarget[] wallpapers = - RemoteAnimationTargetCompat.wrapNonApps( - info, true /* wallpapers */, t, leashMap); - final RemoteAnimationTarget[] nonApps = - RemoteAnimationTargetCompat.wrapNonApps( - info, false /* wallpapers */, t, leashMap); - - // TODO(b/177438007): Move this set-up logic into launcher's animation impl. - boolean isReturnToHome = false; - TransitionInfo.Change launcherTask = null; - TransitionInfo.Change wallpaper = null; - int launcherLayer = 0; - int rotateDelta = 0; - float displayW = 0; - float displayH = 0; - for (int i = info.getChanges().size() - 1; i >= 0; --i) { - final TransitionInfo.Change change = info.getChanges().get(i); - // skip changes that we didn't wrap - if (!leashMap.containsKey(change.getLeash())) continue; - if (change.getTaskInfo() != null - && change.getTaskInfo().getActivityType() == ACTIVITY_TYPE_HOME) { - isReturnToHome = change.getMode() == TRANSIT_OPEN - || change.getMode() == TRANSIT_TO_FRONT; - launcherTask = change; - launcherLayer = info.getChanges().size() - i; - } else if ((change.getFlags() & FLAG_IS_WALLPAPER) != 0) { - wallpaper = change; - } - if (change.getParent() == null && change.getEndRotation() >= 0 - && change.getEndRotation() != change.getStartRotation()) { - rotateDelta = change.getEndRotation() - change.getStartRotation(); - displayW = change.getEndAbsBounds().width(); - displayH = change.getEndAbsBounds().height(); - } - } - - // Prepare for rotation if there is one - final CounterRotator counterLauncher = new CounterRotator(); - final CounterRotator counterWallpaper = new CounterRotator(); - if (launcherTask != null && rotateDelta != 0 && launcherTask.getParent() != null) { - final TransitionInfo.Change parent = info.getChange(launcherTask.getParent()); - if (parent != null) { - counterLauncher.setup(t, parent.getLeash(), rotateDelta, displayW, - displayH); - } else { - Log.e(TAG, "Malformed: " + launcherTask + " has parent=" - + launcherTask.getParent() + " but it's not in info."); - } - if (counterLauncher.getSurface() != null) { - t.setLayer(counterLauncher.getSurface(), launcherLayer); - } - } - - if (isReturnToHome) { - if (counterLauncher.getSurface() != null) { - t.setLayer(counterLauncher.getSurface(), info.getChanges().size() * 3); - } - // Need to "boost" the closing things since that's what launcher expects. - for (int i = info.getChanges().size() - 1; i >= 0; --i) { - final TransitionInfo.Change change = info.getChanges().get(i); - final SurfaceControl leash = leashMap.get(change.getLeash()); - // skip changes that we didn't wrap - if (leash == null) continue; - final int mode = info.getChanges().get(i).getMode(); - // Only deal with independent layers - if (!TransitionInfo.isIndependent(change, info)) continue; - if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) { - t.setLayer(leash, info.getChanges().size() * 3 - i); - counterLauncher.addChild(t, leash); - } - } - // Make wallpaper visible immediately since launcher apparently won't do this. - for (int i = wallpapers.length - 1; i >= 0; --i) { - t.show(wallpapers[i].leash); - t.setAlpha(wallpapers[i].leash, 1.f); - } - } else { - if (launcherTask != null) { - counterLauncher.addChild(t, leashMap.get(launcherTask.getLeash())); - } - if (wallpaper != null && rotateDelta != 0 && wallpaper.getParent() != null) { - final TransitionInfo.Change parent = info.getChange(wallpaper.getParent()); - if (parent != null) { - counterWallpaper.setup(t, parent.getLeash(), rotateDelta, displayW, - displayH); - } else { - Log.e(TAG, "Malformed: " + wallpaper + " has parent=" - + wallpaper.getParent() + " but it's not in info."); - } - if (counterWallpaper.getSurface() != null) { - t.setLayer(counterWallpaper.getSurface(), -1); - counterWallpaper.addChild(t, leashMap.get(wallpaper.getLeash())); - } - } - } - t.apply(); - - final Runnable animationFinishedCallback = () -> { - final SurfaceControl.Transaction finishTransaction = - new SurfaceControl.Transaction(); - counterLauncher.cleanUp(finishTransaction); - counterWallpaper.cleanUp(finishTransaction); - // Release surface references now. This is apparently to free GPU memory - // before GC would. - info.releaseAllSurfaces(); - // Don't release here since launcher might still be using them. Instead - // let launcher release them (eg. via RemoteAnimationTargets) - leashMap.clear(); - try { - finishCallback.onTransitionFinished(null /* wct */, finishTransaction); - finishTransaction.close(); - } catch (RemoteException e) { - Log.e(TAG, "Failed to call app controlled animation finished callback", e); - } - }; - synchronized (mFinishRunnables) { - mFinishRunnables.put(token, animationFinishedCallback); - } - // TODO(bc-unlcok): Pass correct transit type. - runner.onAnimationStart(TRANSIT_OLD_NONE, - apps, wallpapers, nonApps, new IRemoteAnimationFinishedCallback() { - @Override - public void onAnimationFinished() { - synchronized (mFinishRunnables) { - if (mFinishRunnables.remove(token) == null) return; - } - animationFinishedCallback.run(); - } - - @Override - public IBinder asBinder() { - return null; - } - }); - } - - @Override - public void mergeAnimation(IBinder token, TransitionInfo info, - SurfaceControl.Transaction t, IBinder mergeTarget, - IRemoteTransitionFinishedCallback finishCallback) throws RemoteException { - // TODO: hook up merge to recents onTaskAppeared if applicable. Until then, adapt - // to legacy cancel. - final Runnable finishRunnable; - synchronized (mFinishRunnables) { - finishRunnable = mFinishRunnables.remove(mergeTarget); - } - // Since we're not actually animating, release native memory now - t.close(); - info.releaseAllSurfaces(); - if (finishRunnable == null) return; - runner.onAnimationCancelled(); - finishRunnable.run(); - } - }; - } -} diff --git a/systemUIAnim/src/com/android/systemui/animation/RemoteAnimationTargetCompat.java b/systemUIAnim/src/com/android/systemui/animation/RemoteAnimationTargetCompat.java deleted file mode 100644 index e251af4472..0000000000 --- a/systemUIAnim/src/com/android/systemui/animation/RemoteAnimationTargetCompat.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (C) 2024 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.animation; - -import android.util.ArrayMap; -import android.view.RemoteAnimationTarget; -import android.view.SurfaceControl; -import android.window.TransitionInfo; -import android.window.TransitionInfo.Change; - -import com.android.wm.shell.shared.TransitionUtil; - -import java.util.ArrayList; -import java.util.function.Predicate; - -/** - * Some utility methods for creating {@link RemoteAnimationTarget} instances. - */ -public class RemoteAnimationTargetCompat { - - /** - * Represents a TransitionInfo object as an array of old-style app targets - * - * @param leashMap Temporary map of change leash -> launcher leash. Is an output, so should be - * populated by this function. If null, it is ignored. - */ - public static RemoteAnimationTarget[] wrapApps(TransitionInfo info, - SurfaceControl.Transaction t, ArrayMap leashMap) { - // LeafTaskFilter is order-dependent, so the same object needs to be used for all Change - // objects. That's why it's constructed here and captured by the lambda instead of building - // a new one ad hoc every time. - TransitionUtil.LeafTaskFilter taskFilter = new TransitionUtil.LeafTaskFilter(); - return wrap(info, t, leashMap, (change) -> { - // Intra-task activity -> activity transitions should be categorized as apps. - if (change.getActivityComponent() != null) return true; - return taskFilter.test(change); - }); - } - - /** - * Represents a TransitionInfo object as an array of old-style non-app targets - * - * @param wallpapers If true, this will return wallpaper targets; otherwise it returns - * non-wallpaper targets. - * @param leashMap Temporary map of change leash -> launcher leash. Is an output, so should be - * populated by this function. If null, it is ignored. - */ - public static RemoteAnimationTarget[] wrapNonApps(TransitionInfo info, boolean wallpapers, - SurfaceControl.Transaction t, ArrayMap leashMap) { - return wrap(info, t, leashMap, (change) -> { - // Intra-task activity -> activity transitions should be categorized as apps. - if (change.getActivityComponent() != null) return false; - return wallpapers - ? TransitionUtil.isWallpaper(change) : TransitionUtil.isNonApp(change); - }); - } - - private static RemoteAnimationTarget[] wrap(TransitionInfo info, - SurfaceControl.Transaction t, ArrayMap leashMap, - Predicate filter) { - final ArrayList out = new ArrayList<>(); - for (int i = 0; i < info.getChanges().size(); i++) { - TransitionInfo.Change change = info.getChanges().get(i); - if (TransitionUtil.isOrderOnly(change)) continue; - if (filter.test(change)) { - out.add(TransitionUtil.newTarget( - change, info.getChanges().size() - i, info, t, leashMap)); - } - } - return out.toArray(new RemoteAnimationTarget[out.size()]); - } -} diff --git a/systemUIAnim/src/com/android/systemui/animation/ShadeInterpolation.kt b/systemUIAnim/src/com/android/systemui/animation/ShadeInterpolation.kt deleted file mode 100644 index b89a8b0e02..0000000000 --- a/systemUIAnim/src/com/android/systemui/animation/ShadeInterpolation.kt +++ /dev/null @@ -1,39 +0,0 @@ -package com.android.systemui.animation - -import android.util.MathUtils - -object ShadeInterpolation { - - /** - * Interpolate alpha for notification background scrim during shade expansion. - * - * @param fraction Shade expansion fraction - */ - @JvmStatic - fun getNotificationScrimAlpha(fraction: Float): Float { - val mappedFraction = MathUtils.constrainedMap(0f, 1f, 0f, 0.5f, fraction) - return interpolateEaseInOut(mappedFraction) - } - - /** - * Interpolate alpha for shade content during shade expansion. - * - * @param fraction Shade expansion fraction - */ - @JvmStatic - fun getContentAlpha(fraction: Float): Float { - val mappedFraction = MathUtils.constrainedMap(0f, 1f, 0.3f, 1f, fraction) - return interpolateEaseInOut(mappedFraction) - } - - private fun interpolateEaseInOut(fraction: Float): Float { - val mappedFraction = fraction * 1.2f - 0.2f - return if (mappedFraction <= 0) { - 0f - } else { - val oneMinusFrac = 1f - mappedFraction - (1f - 0.5f * (1f - Math.cos((3.14159f * oneMinusFrac * oneMinusFrac).toDouble()))) - .toFloat() - } - } -} diff --git a/systemUIAnim/src/com/android/systemui/animation/TextAnimator.kt b/systemUIAnim/src/com/android/systemui/animation/TextAnimator.kt deleted file mode 100644 index 978943ae7f..0000000000 --- a/systemUIAnim/src/com/android/systemui/animation/TextAnimator.kt +++ /dev/null @@ -1,409 +0,0 @@ -/* - * Copyright (C) 2020 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.animation - -import android.animation.Animator -import android.animation.AnimatorListenerAdapter -import android.animation.TimeInterpolator -import android.animation.ValueAnimator -import android.graphics.Canvas -import android.graphics.Typeface -import android.graphics.fonts.Font -import android.graphics.fonts.FontVariationAxis -import android.text.Layout -import android.util.LruCache -import kotlin.math.roundToInt -import android.util.Log - -private const val DEFAULT_ANIMATION_DURATION: Long = 300 -private const val TYPEFACE_CACHE_MAX_ENTRIES = 5 - -typealias GlyphCallback = (TextAnimator.PositionedGlyph, Float) -> Unit - -interface TypefaceVariantCache { - fun getTypefaceForVariant(fvar: String?): Typeface? - - companion object { - fun createVariantTypeface(baseTypeface: Typeface, fVar: String?): Typeface { - if (fVar.isNullOrEmpty()) { - return baseTypeface - } - - val axes = FontVariationAxis.fromFontVariationSettings(fVar) - ?.toMutableList() - ?: mutableListOf() - axes.removeIf { !baseTypeface.isSupportedAxes(it.getOpenTypeTagValue()) } - if (axes.isEmpty()) { - return baseTypeface - } - return Typeface.createFromTypefaceWithVariation(baseTypeface, axes) - } - } -} - -class TypefaceVariantCacheImpl( - var baseTypeface: Typeface, -) : TypefaceVariantCache { - private val cache = LruCache(TYPEFACE_CACHE_MAX_ENTRIES) - override fun getTypefaceForVariant(fvar: String?): Typeface? { - if (fvar == null) { - return baseTypeface - } - cache.get(fvar)?.let { - return it - } - - return TypefaceVariantCache.createVariantTypeface(baseTypeface, fvar).also { - cache.put(fvar, it) - } - } -} - -/** - * This class provides text animation between two styles. - * - * Currently this class can provide text style animation for text weight and text size. For example - * the simple view that draws text with animating text size is like as follows: - *
 
- * ```
- *     class SimpleTextAnimation : View {
- *         @JvmOverloads constructor(...)
- *
- *         private val layout: Layout = ... // Text layout, e.g. StaticLayout.
- *
- *         // TextAnimator tells us when needs to be invalidate.
- *         private val animator = TextAnimator(layout) { invalidate() }
- *
- *         override fun onDraw(canvas: Canvas) = animator.draw(canvas)
- *
- *         // Change the text size with animation.
- *         fun setTextSize(sizePx: Float, animate: Boolean) {
- *             animator.setTextStyle("" /* unchanged fvar... */, sizePx, animate)
- *         }
- *     }
- * ```
- *  
- */ -class TextAnimator( - layout: Layout, - numberOfAnimationSteps: Int? = null, // Only do this number of discrete animation steps. - private val invalidateCallback: () -> Unit, -) { - var typefaceCache: TypefaceVariantCache = TypefaceVariantCacheImpl(layout.paint.typeface) - get() = field - set(value) { - field = value - textInterpolator.typefaceCache = value - } - - // Following two members are for mutable for testing purposes. - public var textInterpolator: TextInterpolator = - TextInterpolator(layout, typefaceCache, numberOfAnimationSteps) - public var animator: ValueAnimator = - ValueAnimator.ofFloat(1f).apply { - duration = DEFAULT_ANIMATION_DURATION - addUpdateListener { - textInterpolator.progress = - calculateProgress(it.animatedValue as Float, numberOfAnimationSteps) - invalidateCallback() - } - addListener( - object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) = textInterpolator.rebase() - override fun onAnimationCancel(animation: Animator) = textInterpolator.rebase() - } - ) - } - - private fun calculateProgress(animProgress: Float, numberOfAnimationSteps: Int?): Float { - if (numberOfAnimationSteps != null) { - // This clamps the progress to the nearest value of "numberOfAnimationSteps" - // discrete values between 0 and 1f. - return (animProgress * numberOfAnimationSteps).roundToInt() / - numberOfAnimationSteps.toFloat() - } - - return animProgress - } - - sealed class PositionedGlyph { - /** Mutable X coordinate of the glyph position relative from drawing offset. */ - var x: Float = 0f - - /** Mutable Y coordinate of the glyph position relative from the baseline. */ - var y: Float = 0f - - /** The current line of text being drawn, in a multi-line TextView. */ - var lineNo: Int = 0 - - /** Mutable text size of the glyph in pixels. */ - var textSize: Float = 0f - - /** Mutable color of the glyph. */ - var color: Int = 0 - - /** Immutable character offset in the text that the current font run start. */ - abstract var runStart: Int - protected set - - /** Immutable run length of the font run. */ - abstract var runLength: Int - protected set - - /** Immutable glyph index of the font run. */ - abstract var glyphIndex: Int - protected set - - /** Immutable font instance for this font run. */ - abstract var font: Font - protected set - - /** Immutable glyph ID for this glyph. */ - abstract var glyphId: Int - protected set - } - - private val fontVariationUtils = FontVariationUtils() - - fun updateLayout(layout: Layout, textSize: Float = -1f) { - textInterpolator.layout = layout - - if (textSize >= 0) { - textInterpolator.targetPaint.textSize = textSize - textInterpolator.basePaint.textSize = textSize - textInterpolator.onTargetPaintModified() - textInterpolator.onBasePaintModified() - } - } - - fun isRunning(): Boolean { - return animator.isRunning - } - - /** - * GlyphFilter applied just before drawing to canvas for tweaking positions and text size. - * - * This callback is called for each glyphs just before drawing the glyphs. This function will be - * called with the intrinsic position, size, color, glyph ID and font instance. You can mutate - * the position, size and color for tweaking animations. Do not keep the reference of passed - * glyph object. The interpolator reuses that object for avoiding object allocations. - * - * Details: The text is drawn with font run units. The font run is a text segment that draws - * with the same font. The {@code runStart} and {@code runLimit} is a range of the font run in - * the text that current glyph is in. Once the font run is determined, the system will convert - * characters into glyph IDs. The {@code glyphId} is the glyph identifier in the font and {@code - * glyphIndex} is the offset of the converted glyph array. Please note that the {@code - * glyphIndex} is not a character index, because the character will not be converted to glyph - * one-by-one. If there are ligatures including emoji sequence, etc, the glyph ID may be - * composed from multiple characters. - * - * Here is an example of font runs: "fin. 終わり" - * - * Characters : f i n . _ 終 わ り - * Code Points: \u0066 \u0069 \u006E \u002E \u0020 \u7D42 \u308F \u308A - * Font Runs : <-- Roboto-Regular.ttf --><-- NotoSans-CJK.otf --> - * runStart = 0, runLength = 5 runStart = 5, runLength = 3 - * Glyph IDs : 194 48 7 8 4367 1039 1002 - * Glyph Index: 0 1 2 3 0 1 2 - * - * In this example, the "fi" is converted into ligature form, thus the single glyph ID is - * assigned for two characters, f and i. - * - * Example: - * ``` - * private val glyphFilter: GlyphCallback = { glyph, progress -> - * val index = glyph.runStart - * val i = glyph.glyphIndex - * val moveAmount = 1.3f - * val sign = (-1 + 2 * ((i + index) % 2)) - * val turnProgress = if (progress < .5f) progress / 0.5f else (1.0f - progress) / 0.5f - * - * // You can modify (x, y) coordinates, textSize and color during animation. - * glyph.textSize += glyph.textSize * sign * moveAmount * turnProgress - * glyph.y += glyph.y * sign * moveAmount * turnProgress - * glyph.x += glyph.x * sign * moveAmount * turnProgress - * } - * ``` - */ - var glyphFilter: GlyphCallback? - get() = textInterpolator.glyphFilter - set(value) { - textInterpolator.glyphFilter = value - } - - fun draw(c: Canvas) = textInterpolator.draw(c) - - /** - * Set text style with animation. - * - * By passing -1 to weight, the view preserve the current weight. - * By passing -1 to textSize, the view preserve the current text size. - * Bu passing -1 to duration, the default text animation, 1000ms, is used. - * By passing false to animate, the text will be updated without animation. - * - * @param fvar an optional text fontVariationSettings. - * @param textSize an optional font size. - * @param colors an optional colors array that must be the same size as numLines passed to - * the TextInterpolator - * @param strokeWidth an optional paint stroke width - * @param animate an optional boolean indicating true for showing style transition as animation, - * false for immediate style transition. True by default. - * @param duration an optional animation duration in milliseconds. This is ignored if animate is - * false. - * @param interpolator an optional time interpolator. If null is passed, last set interpolator - * will be used. This is ignored if animate is false. - */ - fun setTextStyle( - fvar: String? = "", - textSize: Float = -1f, - color: Int? = null, - strokeWidth: Float = -1f, - animate: Boolean = true, - duration: Long = -1L, - interpolator: TimeInterpolator? = null, - delay: Long = 0, - onAnimationEnd: Runnable? = null, - ) = setTextStyleInternal(fvar, textSize, color, strokeWidth, animate, duration, - interpolator, delay, onAnimationEnd, updateLayoutOnFailure = true) - - private fun setTextStyleInternal( - fvar: String?, - textSize: Float, - color: Int?, - strokeWidth: Float, - animate: Boolean, - duration: Long, - interpolator: TimeInterpolator?, - delay: Long, - onAnimationEnd: Runnable?, - updateLayoutOnFailure: Boolean, - ) { - try { - if (animate) { - animator.cancel() - textInterpolator.rebase() - } - - if (textSize >= 0) { - textInterpolator.targetPaint.textSize = textSize - } - if (!fvar.isNullOrBlank()) { - textInterpolator.targetPaint.typeface = typefaceCache.getTypefaceForVariant(fvar) - } - if (color != null) { - textInterpolator.targetPaint.color = color - } - if (strokeWidth >= 0F) { - textInterpolator.targetPaint.strokeWidth = strokeWidth - } - textInterpolator.onTargetPaintModified() - - if (animate) { - animator.startDelay = delay - animator.duration = - if (duration == -1L) { - DEFAULT_ANIMATION_DURATION - } else { - duration - } - interpolator?.let { animator.interpolator = it } - if (onAnimationEnd != null) { - val listener = object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - onAnimationEnd.run() - animator.removeListener(this) - } - override fun onAnimationCancel(animation: Animator) { - animator.removeListener(this) - } - } - animator.addListener(listener) - } - animator.start() - } else { - // No animation is requested, thus set base and target state to the same state. - textInterpolator.progress = 1f - textInterpolator.rebase() - invalidateCallback() - } - } catch (ex: IllegalArgumentException) { - if (updateLayoutOnFailure) { - Log.e(TAG, "setTextStyleInternal: Exception caught but retrying. This is usually" + - " due to the layout having changed unexpectedly without being notified.", ex) - updateLayout(textInterpolator.layout) - setTextStyleInternal(fvar, textSize, color, strokeWidth, animate, duration, - interpolator, delay, onAnimationEnd, updateLayoutOnFailure = false) - } else { - throw ex - } - } - } - - /** - * Set text style with animation. Similar as - * fun setTextStyle( - * fvar: String? = "", - * textSize: Float = -1f, - * color: Int? = null, - * strokeWidth: Float = -1f, - * animate: Boolean = true, - * duration: Long = -1L, - * interpolator: TimeInterpolator? = null, - * delay: Long = 0, - * onAnimationEnd: Runnable? = null - * ) - * - * @param weight an optional style value for `wght` in fontVariationSettings. - * @param width an optional style value for `wdth` in fontVariationSettings. - * @param opticalSize an optional style value for `opsz` in fontVariationSettings. - * @param roundness an optional style value for `ROND` in fontVariationSettings. - */ - fun setTextStyle( - weight: Int = -1, - width: Int = -1, - opticalSize: Int = -1, - roundness: Int = -1, - textSize: Float = -1f, - color: Int? = null, - strokeWidth: Float = -1f, - animate: Boolean = true, - duration: Long = -1L, - interpolator: TimeInterpolator? = null, - delay: Long = 0, - onAnimationEnd: Runnable? = null - ) = setTextStyleInternal( - fvar = fontVariationUtils.updateFontVariation( - weight = weight, - width = width, - opticalSize = opticalSize, - roundness = roundness, - ), - textSize = textSize, - color = color, - strokeWidth = strokeWidth, - animate = animate, - duration = duration, - interpolator = interpolator, - delay = delay, - onAnimationEnd = onAnimationEnd, - updateLayoutOnFailure = true, - ) - - companion object { - private val TAG = TextAnimator::class.simpleName!! - } -} diff --git a/systemUIAnim/src/com/android/systemui/animation/TextInterpolator.kt b/systemUIAnim/src/com/android/systemui/animation/TextInterpolator.kt deleted file mode 100644 index 02caeeddd7..0000000000 --- a/systemUIAnim/src/com/android/systemui/animation/TextInterpolator.kt +++ /dev/null @@ -1,531 +0,0 @@ -/* - * Copyright (C) 2020 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.animation - -import android.graphics.Canvas -import android.graphics.Paint -import android.graphics.fonts.Font -import android.graphics.fonts.FontVariationAxis -import android.graphics.text.PositionedGlyphs -import android.text.Layout -import android.text.TextPaint -import android.text.TextShaper -import android.util.MathUtils -import com.android.internal.graphics.ColorUtils -import java.lang.Math.max - -/** Provide text style linear interpolation for plain text. */ -class TextInterpolator( - layout: Layout, - var typefaceCache: TypefaceVariantCache, - numberOfAnimationSteps: Int? = null, -) { - /** - * Returns base paint used for interpolation. - * - * Once you modified the style parameters, you have to call reshapeText to recalculate base text - * layout. - * - * Do not bypass the cache and update the typeface or font variation directly. - * - * @return a paint object - */ - val basePaint = TextPaint(layout.paint) - - /** - * Returns target paint used for interpolation. - * - * Once you modified the style parameters, you have to call reshapeText to recalculate target - * text layout. - * - * Do not bypass the cache and update the typeface or font variation directly. - * - * @return a paint object - */ - val targetPaint = TextPaint(layout.paint) - - /** - * A class represents a single font run. - * - * A font run is a range that will be drawn with the same font. - */ - private data class FontRun( - val start: Int, // inclusive - val end: Int, // exclusive - var baseFont: Font, - var targetFont: Font - ) { - val length: Int - get() = end - start - } - - /** A class represents text layout of a single run. */ - private class Run( - val glyphIds: IntArray, - val baseX: FloatArray, // same length as glyphIds - val baseY: FloatArray, // same length as glyphIds - val targetX: FloatArray, // same length as glyphIds - val targetY: FloatArray, // same length as glyphIds - val fontRuns: List - ) - - /** A class represents text layout of a single line. */ - private class Line(val runs: List) - - private var lines = listOf() - private val fontInterpolator = FontInterpolator(numberOfAnimationSteps) - - // Recycling object for glyph drawing and tweaking. - private val tmpPaint = TextPaint() - private val tmpPaintForGlyph by lazy { TextPaint() } - private val tmpGlyph by lazy { MutablePositionedGlyph() } - // Will be extended for the longest font run if needed. - private var tmpPositionArray = FloatArray(20) - - /** - * The progress position of the interpolation. - * - * The 0f means the start state, 1f means the end state. - */ - var progress: Float = 0f - - /** - * The layout used for drawing text. - * - * Only non-styled text is supported. Even if the given layout is created from Spanned, the span - * information is not used. - * - * The paint objects used for interpolation are not changed by this method call. - * - * Note: disabling ligature is strongly recommended if you give extra letter spacing since they - * may be disjointed based on letter spacing value and cannot be interpolated. Animator will - * throw runtime exception if they cannot be interpolated. - */ - var layout: Layout = layout - get() = field - set(value) { - field = value - shapeText(value) - } - - var shapedText: String = "" - private set - - init { - // shapeText needs to be called after all members are initialized. - shapeText(layout) - } - - /** - * Recalculate internal text layout for interpolation. - * - * Whenever the target paint is modified, call this method to recalculate internal text layout - * used for interpolation. - */ - fun onTargetPaintModified() { - updatePositionsAndFonts(shapeText(layout, targetPaint), updateBase = false) - } - - /** - * Recalculate internal text layout for interpolation. - * - * Whenever the base paint is modified, call this method to recalculate internal text layout - * used for interpolation. - */ - fun onBasePaintModified() { - updatePositionsAndFonts(shapeText(layout, basePaint), updateBase = true) - } - - /** - * Rebase the base state to the middle of the interpolation. - * - * The text interpolator does not calculate all the text position by text shaper due to - * performance reasons. Instead, the text interpolator shape the start and end state and - * calculate text position of the middle state by linear interpolation. Due to this trick, the - * text positions of the middle state is likely different from the text shaper result. So, if - * you want to start animation from the middle state, you will see the glyph jumps due to this - * trick, i.e. the progress 0.5 of interpolation between weight 400 and 700 is different from - * text shape result of weight 550. - * - * After calling this method, do not call onBasePaintModified() since it reshape the text and - * update the base state. As in above notice, the text shaping result at current progress is - * different shaped result. By calling onBasePaintModified(), you may see the glyph jump. - * - * By calling this method, the progress will be reset to 0. - * - * This API is useful to continue animation from the middle of the state. For example, if you - * animate weight from 200 to 400, then if you want to move back to 200 at the half of the - * animation, it will look like - *
 
-     * ```
-     *     val interp = TextInterpolator(layout)
-     *
-     *     // Interpolate between weight 200 to 400.
-     *     interp.basePaint.fontVariationSettings = "'wght' 200"
-     *     interp.onBasePaintModified()
-     *     interp.targetPaint.fontVariationSettings = "'wght' 400"
-     *     interp.onTargetPaintModified()
-     *
-     *     // animate
-     *     val animator = ValueAnimator.ofFloat(1f).apply {
-     *         addUpdaterListener {
-     *             interp.progress = it.animateValue as Float
-     *         }
-     *     }.start()
-     *
-     *     // Here, assuming you receive some event and want to start new animation from current
-     *     // state.
-     *     OnSomeEvent {
-     *         animator.cancel()
-     *
-     *         // start another animation from the current state.
-     *         interp.rebase() // Use current state as base state.
-     *         interp.targetPaint.fontVariationSettings = "'wght' 200" // set new target
-     *         interp.onTargetPaintModified() // reshape target
-     *
-     *         // Here the textInterpolator interpolate from 'wght' from 300 to 200 if the current
-     *         // progress is 0.5
-     *         animator.start()
-     *     }
-     * ```
-     *  
- */ - fun rebase() { - if (progress == 0f) { - return - } else if (progress == 1f) { - basePaint.set(targetPaint) - } else { - lerp(basePaint, targetPaint, progress, tmpPaint) - basePaint.set(tmpPaint) - } - - lines.forEach { line -> - line.runs.forEach { run -> - for (i in run.baseX.indices) { - run.baseX[i] = MathUtils.lerp(run.baseX[i], run.targetX[i], progress) - run.baseY[i] = MathUtils.lerp(run.baseY[i], run.targetY[i], progress) - } - run.fontRuns.forEach { fontRun -> - fontRun.baseFont = - fontInterpolator.lerp(fontRun.baseFont, fontRun.targetFont, progress) - val fvar = FontVariationAxis.toFontVariationSettings(fontRun.baseFont.axes) - basePaint.typeface = typefaceCache.getTypefaceForVariant(fvar) - } - } - } - - progress = 0f - } - - /** - * Draws interpolated text at the given progress. - * - * @param canvas a canvas. - */ - fun draw(canvas: Canvas) { - lerp(basePaint, targetPaint, progress, tmpPaint) - lines.forEachIndexed { lineNo, line -> - line.runs.forEach { run -> - canvas.save() - try { - // Move to drawing origin. - val origin = layout.getDrawOrigin(lineNo) - canvas.translate(origin, layout.getLineBaseline(lineNo).toFloat()) - - run.fontRuns.forEach { fontRun -> - drawFontRun(canvas, run, fontRun, lineNo, tmpPaint) - } - } finally { - canvas.restore() - } - } - } - } - - // Shape text with current paint parameters. - private fun shapeText(layout: Layout) { - val baseLayout = shapeText(layout, basePaint) - val targetLayout = shapeText(layout, targetPaint) - - require(baseLayout.size == targetLayout.size) { - "The new layout result has different line count." - } - - var maxRunLength = 0 - lines = - baseLayout.zip(targetLayout) { baseLine, targetLine -> - val runs = - baseLine.zip(targetLine) { base, target -> - require(base.glyphCount() == target.glyphCount()) { - "Inconsistent glyph count at line ${lines.size}" - } - - val glyphCount = base.glyphCount() - - // Good to recycle the array if the existing array can hold the new layout - // result. - val glyphIds = - IntArray(glyphCount) { - base.getGlyphId(it).also { baseGlyphId -> - require(baseGlyphId == target.getGlyphId(it)) { - "Inconsistent glyph ID at $it in line ${lines.size}" - } - } - } - - val baseX = FloatArray(glyphCount) { base.getGlyphX(it) } - val baseY = FloatArray(glyphCount) { base.getGlyphY(it) } - val targetX = FloatArray(glyphCount) { target.getGlyphX(it) } - val targetY = FloatArray(glyphCount) { target.getGlyphY(it) } - - // Calculate font runs - val fontRun = mutableListOf() - if (glyphCount != 0) { - var start = 0 - var baseFont = base.getFont(start) - var targetFont = target.getFont(start) - require(FontInterpolator.canInterpolate(baseFont, targetFont)) { - "Cannot interpolate font at $start ($baseFont vs $targetFont)" - } - - for (i in 1 until glyphCount) { - val nextBaseFont = base.getFont(i) - val nextTargetFont = target.getFont(i) - - if (baseFont !== nextBaseFont) { - require(targetFont !== nextTargetFont) { - "Base font has changed at $i but target font is unchanged." - } - // Font transition point. push run and reset context. - fontRun.add(FontRun(start, i, baseFont, targetFont)) - maxRunLength = max(maxRunLength, i - start) - baseFont = nextBaseFont - targetFont = nextTargetFont - start = i - require(FontInterpolator.canInterpolate(baseFont, targetFont)) { - "Cannot interpolate font at $start" + - " ($baseFont vs $targetFont)" - } - } else { // baseFont === nextBaseFont - require(targetFont === nextTargetFont) { - "Base font is unchanged at $i but target font has changed." - } - } - } - fontRun.add(FontRun(start, glyphCount, baseFont, targetFont)) - maxRunLength = max(maxRunLength, glyphCount - start) - } - Run(glyphIds, baseX, baseY, targetX, targetY, fontRun) - } - Line(runs) - } - - // Update float array used for drawing. - if (tmpPositionArray.size < maxRunLength * 2) { - tmpPositionArray = FloatArray(maxRunLength * 2) - } - } - - private class MutablePositionedGlyph : TextAnimator.PositionedGlyph() { - override var runStart: Int = 0 - public set - override var runLength: Int = 0 - public set - override var glyphIndex: Int = 0 - public set - override lateinit var font: Font - public set - override var glyphId: Int = 0 - public set - } - - var glyphFilter: GlyphCallback? = null - - // Draws single font run. - private fun drawFontRun(c: Canvas, line: Run, run: FontRun, lineNo: Int, paint: Paint) { - var arrayIndex = 0 - val font = fontInterpolator.lerp(run.baseFont, run.targetFont, progress) - - val glyphFilter = glyphFilter - if (glyphFilter == null) { - for (i in run.start until run.end) { - tmpPositionArray[arrayIndex++] = - MathUtils.lerp(line.baseX[i], line.targetX[i], progress) - tmpPositionArray[arrayIndex++] = - MathUtils.lerp(line.baseY[i], line.targetY[i], progress) - } - c.drawGlyphs(line.glyphIds, run.start, tmpPositionArray, 0, run.length, font, paint) - return - } - - tmpGlyph.font = font - tmpGlyph.runStart = run.start - tmpGlyph.runLength = run.end - run.start - tmpGlyph.lineNo = lineNo - - tmpPaintForGlyph.set(paint) - var prevStart = run.start - - for (i in run.start until run.end) { - tmpGlyph.glyphIndex = i - tmpGlyph.glyphId = line.glyphIds[i] - tmpGlyph.x = MathUtils.lerp(line.baseX[i], line.targetX[i], progress) - tmpGlyph.y = MathUtils.lerp(line.baseY[i], line.targetY[i], progress) - tmpGlyph.textSize = paint.textSize - tmpGlyph.color = paint.color - - glyphFilter(tmpGlyph, progress) - - if (tmpGlyph.textSize != paint.textSize || tmpGlyph.color != paint.color) { - tmpPaintForGlyph.textSize = tmpGlyph.textSize - tmpPaintForGlyph.color = tmpGlyph.color - - c.drawGlyphs( - line.glyphIds, - prevStart, - tmpPositionArray, - 0, - i - prevStart, - font, - tmpPaintForGlyph - ) - prevStart = i - arrayIndex = 0 - } - - tmpPositionArray[arrayIndex++] = tmpGlyph.x - tmpPositionArray[arrayIndex++] = tmpGlyph.y - } - - c.drawGlyphs( - line.glyphIds, - prevStart, - tmpPositionArray, - 0, - run.end - prevStart, - font, - tmpPaintForGlyph - ) - } - - private fun updatePositionsAndFonts( - layoutResult: List>, - updateBase: Boolean - ) { - // Update target positions with newly calculated text layout. - check(layoutResult.size == lines.size) { "The new layout result has different line count." } - - lines.zip(layoutResult) { line, runs -> - line.runs.zip(runs) { lineRun, newGlyphs -> - require(newGlyphs.glyphCount() == lineRun.glyphIds.size) { - "The new layout has different glyph count." - } - - lineRun.fontRuns.forEach { run -> - val newFont = newGlyphs.getFont(run.start) - for (i in run.start until run.end) { - require(newGlyphs.getGlyphId(run.start) == lineRun.glyphIds[run.start]) { - "The new layout has different glyph ID at ${run.start}" - } - require(newFont === newGlyphs.getFont(i)) { - "The new layout has different font run." + - " $newFont vs ${newGlyphs.getFont(i)} at $i" - } - } - - // The passing base font and target font is already interpolatable, so just - // check new font can be interpolatable with base font. - require(FontInterpolator.canInterpolate(newFont, run.baseFont)) { - "New font cannot be interpolated with existing font. $newFont," + - " ${run.baseFont}" - } - - if (updateBase) { - run.baseFont = newFont - } else { - run.targetFont = newFont - } - } - - if (updateBase) { - for (i in lineRun.baseX.indices) { - lineRun.baseX[i] = newGlyphs.getGlyphX(i) - lineRun.baseY[i] = newGlyphs.getGlyphY(i) - } - } else { - for (i in lineRun.baseX.indices) { - lineRun.targetX[i] = newGlyphs.getGlyphX(i) - lineRun.targetY[i] = newGlyphs.getGlyphY(i) - } - } - } - } - } - - // Linear interpolate the paint. - private fun lerp(from: Paint, to: Paint, progress: Float, out: Paint) { - out.set(from) - - // Currently only font size & colors are interpolated. - // TODO(172943390): Add other interpolation or support custom interpolator. - out.textSize = MathUtils.lerp(from.textSize, to.textSize, progress) - out.color = ColorUtils.blendARGB(from.color, to.color, progress) - out.strokeWidth = MathUtils.lerp(from.strokeWidth, to.strokeWidth, progress) - } - - // Shape the text and stores the result to out argument. - private fun shapeText(layout: Layout, paint: TextPaint): List> { - var text = StringBuilder() - val out = mutableListOf>() - for (lineNo in 0 until layout.lineCount) { // Shape all lines. - val lineStart = layout.getLineStart(lineNo) - val lineEnd = layout.getLineEnd(lineNo) - var count = lineEnd - lineStart - // Do not render the last character in the line if it's a newline and unprintable - val last = lineStart + count - 1 - if (last > lineStart && last < layout.text.length && layout.text[last] == '\n') { - count-- - } - - val runs = mutableListOf() - TextShaper.shapeText( - layout.text, - lineStart, - count, - layout.textDirectionHeuristic, - paint - ) { _, _, glyphs, _ -> - runs.add(glyphs) - } - out.add(runs) - - if (lineNo > 0) { - text.append("\n") - } - text.append(layout.text.substring(lineStart, lineEnd)) - } - shapedText = text.toString() - return out - } -} - -private fun Layout.getDrawOrigin(lineNo: Int) = - if (getParagraphDirection(lineNo) == Layout.DIR_LEFT_TO_RIGHT) { - getLineLeft(lineNo) - } else { - getLineRight(lineNo) - } diff --git a/systemUIAnim/src/com/android/systemui/animation/TransitionAnimator.kt b/systemUIAnim/src/com/android/systemui/animation/TransitionAnimator.kt deleted file mode 100644 index 8e824e60d4..0000000000 --- a/systemUIAnim/src/com/android/systemui/animation/TransitionAnimator.kt +++ /dev/null @@ -1,576 +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.animation - -import android.animation.Animator -import android.animation.AnimatorListenerAdapter -import android.animation.ValueAnimator -import android.content.Context -import android.graphics.PorterDuff -import android.graphics.PorterDuffXfermode -import android.graphics.drawable.GradientDrawable -import android.util.Log -import android.util.MathUtils -import android.view.View -import android.view.ViewGroup -import android.view.animation.Interpolator -import androidx.annotation.VisibleForTesting -import com.android.app.animation.Interpolators.LINEAR -import com.android.systemui.shared.Flags.returnAnimationFrameworkLibrary -import java.util.concurrent.Executor -import kotlin.math.roundToInt - -private const val TAG = "TransitionAnimator" - -/** A base class to animate a window (activity or dialog) launch to or return from a view . */ -class TransitionAnimator( - private val mainExecutor: Executor, - private val timings: Timings, - private val interpolators: Interpolators, -) { - companion object { - internal const val DEBUG = false - private val SRC_MODE = PorterDuffXfermode(PorterDuff.Mode.SRC) - - /** - * Given the [linearProgress] of a transition animation, return the linear progress of the - * sub-animation starting [delay] ms after the transition animation and that lasts - * [duration]. - */ - @JvmStatic - fun getProgress( - timings: Timings, - linearProgress: Float, - delay: Long, - duration: Long - ): Float { - return MathUtils.constrain( - (linearProgress * timings.totalDuration - delay) / duration, - 0.0f, - 1.0f - ) - } - - internal fun checkReturnAnimationFrameworkFlag() { - check(returnAnimationFrameworkLibrary()) { - "isLaunching cannot be false when the returnAnimationFrameworkLibrary flag is " + - "disabled" - } - } - } - - private val transitionContainerLocation = IntArray(2) - private val cornerRadii = FloatArray(8) - - /** - * A controller that takes care of applying the animation to an expanding view. - * - * Note that all callbacks (onXXX methods) are all called on the main thread. - */ - interface Controller { - /** - * The container in which the view that started the animation will be animating together - * with the opening or closing window. - * - * This will be used to: - * - Get the associated [Context]. - * - Compute whether we are expanding to or contracting from fully above the transition - * container. - * - Get the overlay into which we put the window background layer, while the animating - * window is not visible (see [openingWindowSyncView]). - * - * This container can be changed to force this [Controller] to animate the expanding view - * inside a different location, for instance to ensure correct layering during the - * animation. - */ - var transitionContainer: ViewGroup - - /** Whether the animation being controlled is a launch or a return. */ - val isLaunching: Boolean - - /** - * If [isLaunching], the [View] with which the opening app window should be synchronized - * once it starts to be visible. Otherwise, the [View] with which the closing app window - * should be synchronized until it stops being visible. - * - * We will also move the window background layer to this view's overlay once the opening - * window is visible (if [isLaunching]), or from this view's overlay once the closing window - * stop being visible (if ![isLaunching]). - * - * If null, this will default to [transitionContainer]. - */ - val openingWindowSyncView: View? - get() = null - - /** - * Return the [State] of the view that will be animated. We will animate from this state to - * the final window state. - * - * Note: This state will be mutated and passed to [onTransitionAnimationProgress] during the - * animation. - */ - fun createAnimatorState(): State - - /** - * The animation started. This is typically used to initialize any additional resource - * needed for the animation. [isExpandingFullyAbove] will be true if the window is expanding - * fully above the [transitionContainer]. - */ - fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) {} - - /** The animation made progress and the expandable view [state] should be updated. */ - fun onTransitionAnimationProgress(state: State, progress: Float, linearProgress: Float) {} - - /** - * The animation ended. This will be called *if and only if* [onTransitionAnimationStart] - * was called previously. This is typically used to clean up the resources initialized when - * the animation was started. - */ - fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {} - } - - /** The state of an expandable view during a [TransitionAnimator] animation. */ - open class State( - /** The position of the view in screen space coordinates. */ - var top: Int = 0, - var bottom: Int = 0, - var left: Int = 0, - var right: Int = 0, - var topCornerRadius: Float = 0f, - var bottomCornerRadius: Float = 0f - ) { - private val startTop = top - - val width: Int - get() = right - left - - val height: Int - get() = bottom - top - - open val topChange: Int - get() = top - startTop - - val centerX: Float - get() = left + width / 2f - - val centerY: Float - get() = top + height / 2f - - /** Whether the expanding view should be visible or hidden. */ - var visible: Boolean = true - } - - interface Animation { - /** Cancel the animation. */ - fun cancel() - } - - /** The timings (durations and delays) used by this animator. */ - data class Timings( - /** The total duration of the animation. */ - val totalDuration: Long, - - /** The time to wait before fading out the expanding content. */ - val contentBeforeFadeOutDelay: Long, - - /** The duration of the expanding content fade out. */ - val contentBeforeFadeOutDuration: Long, - - /** - * The time to wait before fading in the expanded content (usually an activity or dialog - * window). - */ - val contentAfterFadeInDelay: Long, - - /** The duration of the expanded content fade in. */ - val contentAfterFadeInDuration: Long - ) - - /** The interpolators used by this animator. */ - data class Interpolators( - /** The interpolator used for the Y position, width, height and corner radius. */ - val positionInterpolator: Interpolator, - - /** - * The interpolator used for the X position. This can be different than - * [positionInterpolator] to create an arc-path during the animation. - */ - val positionXInterpolator: Interpolator = positionInterpolator, - - /** The interpolator used when fading out the expanding content. */ - val contentBeforeFadeOutInterpolator: Interpolator, - - /** The interpolator used when fading in the expanded content. */ - val contentAfterFadeInInterpolator: Interpolator - ) - - /** - * Start a transition animation controlled by [controller] towards [endState]. An intermediary - * layer with [windowBackgroundColor] will fade in then (optionally) fade out above the - * expanding view, and should be the same background color as the opening (or closing) window. - * - * If [fadeWindowBackgroundLayer] is true, then this intermediary layer will fade out during the - * second half of the animation (if [Controller.isLaunching] or fade in during the first half of - * the animation (if ![Controller.isLaunching]), and will have SRC blending mode (ultimately - * punching a hole in the [transition container][Controller.transitionContainer]) iff [drawHole] - * is true. - */ - fun startAnimation( - controller: Controller, - endState: State, - windowBackgroundColor: Int, - fadeWindowBackgroundLayer: Boolean = true, - drawHole: Boolean = false, - ): Animation { - if (!controller.isLaunching) checkReturnAnimationFrameworkFlag() - - // We add an extra layer with the same color as the dialog/app splash screen background - // color, which is usually the same color of the app background. We first fade in this layer - // to hide the expanding view, then we fade it out with SRC mode to draw a hole in the - // transition container and reveal the opening window. - val windowBackgroundLayer = - GradientDrawable().apply { - setColor(windowBackgroundColor) - alpha = 0 - } - - val animator = - createAnimator( - controller, - endState, - windowBackgroundLayer, - fadeWindowBackgroundLayer, - drawHole - ) - animator.start() - - return object : Animation { - override fun cancel() { - animator.cancel() - } - } - } - - @VisibleForTesting - fun createAnimator( - controller: Controller, - endState: State, - windowBackgroundLayer: GradientDrawable, - fadeWindowBackgroundLayer: Boolean = true, - drawHole: Boolean = false - ): ValueAnimator { - val state = controller.createAnimatorState() - - // Start state. - val startTop = state.top - val startBottom = state.bottom - val startLeft = state.left - val startRight = state.right - val startCenterX = (startLeft + startRight) / 2f - val startWidth = startRight - startLeft - val startTopCornerRadius = state.topCornerRadius - val startBottomCornerRadius = state.bottomCornerRadius - - // End state. - var endTop = endState.top - var endBottom = endState.bottom - var endLeft = endState.left - var endRight = endState.right - var endCenterX = (endLeft + endRight) / 2f - var endWidth = endRight - endLeft - val endTopCornerRadius = endState.topCornerRadius - val endBottomCornerRadius = endState.bottomCornerRadius - - fun maybeUpdateEndState() { - if ( - endTop != endState.top || - endBottom != endState.bottom || - endLeft != endState.left || - endRight != endState.right - ) { - endTop = endState.top - endBottom = endState.bottom - endLeft = endState.left - endRight = endState.right - endCenterX = (endLeft + endRight) / 2f - endWidth = endRight - endLeft - } - } - - val transitionContainer = controller.transitionContainer - val isExpandingFullyAbove = isExpandingFullyAbove(transitionContainer, endState) - - // Update state. - val animator = ValueAnimator.ofFloat(0f, 1f) - animator.duration = timings.totalDuration - animator.interpolator = LINEAR - - // Whether we should move the [windowBackgroundLayer] into the overlay of - // [Controller.openingWindowSyncView] once the opening app window starts to be visible, or - // from it once the closing app window stops being visible. - // This is necessary as a one-off sync so we can avoid syncing at every frame, especially - // in complex interactions like launching an activity from a dialog. See - // b/214961273#comment2 for more details. - val openingWindowSyncView = controller.openingWindowSyncView - val openingWindowSyncViewOverlay = openingWindowSyncView?.overlay - val moveBackgroundLayerWhenAppVisibilityChanges = - openingWindowSyncView != null && - openingWindowSyncView.viewRootImpl != controller.transitionContainer.viewRootImpl - - val transitionContainerOverlay = transitionContainer.overlay - var movedBackgroundLayer = false - - animator.addListener( - object : AnimatorListenerAdapter() { - override fun onAnimationStart(animation: Animator, isReverse: Boolean) { - if (DEBUG) { - Log.d(TAG, "Animation started") - } - controller.onTransitionAnimationStart(isExpandingFullyAbove) - - // Add the drawable to the transition container overlay. Overlays always draw - // drawables after views, so we know that it will be drawn above any view added - // by the controller. - if (controller.isLaunching || openingWindowSyncViewOverlay == null) { - transitionContainerOverlay.add(windowBackgroundLayer) - } else { - openingWindowSyncViewOverlay.add(windowBackgroundLayer) - } - } - - override fun onAnimationEnd(animation: Animator) { - if (DEBUG) { - Log.d(TAG, "Animation ended") - } - - // TODO(b/330672236): Post this to the main thread instead so that it does not - // flicker with Flexiglass enabled. - controller.onTransitionAnimationEnd(isExpandingFullyAbove) - transitionContainerOverlay.remove(windowBackgroundLayer) - - if (moveBackgroundLayerWhenAppVisibilityChanges && controller.isLaunching) { - openingWindowSyncViewOverlay?.remove(windowBackgroundLayer) - } - } - } - ) - - animator.addUpdateListener { animation -> - maybeUpdateEndState() - - // TODO(b/184121838): Use reverse interpolators to get the same path/arc as the non - // reversed animation. - val linearProgress = animation.animatedFraction - val progress = interpolators.positionInterpolator.getInterpolation(linearProgress) - val xProgress = interpolators.positionXInterpolator.getInterpolation(linearProgress) - - val xCenter = MathUtils.lerp(startCenterX, endCenterX, xProgress) - val halfWidth = MathUtils.lerp(startWidth, endWidth, progress) / 2f - - state.top = MathUtils.lerp(startTop, endTop, progress).roundToInt() - state.bottom = MathUtils.lerp(startBottom, endBottom, progress).roundToInt() - state.left = (xCenter - halfWidth).roundToInt() - state.right = (xCenter + halfWidth).roundToInt() - - state.topCornerRadius = - MathUtils.lerp(startTopCornerRadius, endTopCornerRadius, progress) - state.bottomCornerRadius = - MathUtils.lerp(startBottomCornerRadius, endBottomCornerRadius, progress) - - state.visible = - if (controller.isLaunching) { - // The expanding view can/should be hidden once it is completely covered by the - // opening window. - getProgress( - timings, - linearProgress, - timings.contentBeforeFadeOutDelay, - timings.contentBeforeFadeOutDuration - ) < 1 - } else { - getProgress( - timings, - linearProgress, - timings.contentAfterFadeInDelay, - timings.contentAfterFadeInDuration - ) > 0 - } - - if ( - controller.isLaunching && - moveBackgroundLayerWhenAppVisibilityChanges && - !state.visible && - !movedBackgroundLayer - ) { - // The expanding view is not visible, so the opening app is visible. If this is - // the first frame when it happens, trigger a one-off sync and move the - // background layer in its new container. - movedBackgroundLayer = true - - transitionContainerOverlay.remove(windowBackgroundLayer) - openingWindowSyncViewOverlay!!.add(windowBackgroundLayer) - - ViewRootSync.synchronizeNextDraw( - transitionContainer, - openingWindowSyncView, - then = {} - ) - } else if ( - !controller.isLaunching && - moveBackgroundLayerWhenAppVisibilityChanges && - state.visible && - !movedBackgroundLayer - ) { - // The contracting view is now visible, so the closing app is not. If this is - // the first frame when it happens, trigger a one-off sync and move the - // background layer in its new container. - movedBackgroundLayer = true - - openingWindowSyncViewOverlay!!.remove(windowBackgroundLayer) - transitionContainerOverlay.add(windowBackgroundLayer) - - ViewRootSync.synchronizeNextDraw( - openingWindowSyncView, - transitionContainer, - then = {} - ) - } - - val container = - if (movedBackgroundLayer) { - openingWindowSyncView!! - } else { - controller.transitionContainer - } - - applyStateToWindowBackgroundLayer( - windowBackgroundLayer, - state, - linearProgress, - container, - fadeWindowBackgroundLayer, - drawHole, - controller.isLaunching - ) - controller.onTransitionAnimationProgress(state, progress, linearProgress) - } - - return animator - } - - /** Return whether we are expanding fully above the [transitionContainer]. */ - internal fun isExpandingFullyAbove(transitionContainer: View, endState: State): Boolean { - transitionContainer.getLocationOnScreen(transitionContainerLocation) - return endState.top <= transitionContainerLocation[1] && - endState.bottom >= transitionContainerLocation[1] + transitionContainer.height && - endState.left <= transitionContainerLocation[0] && - endState.right >= transitionContainerLocation[0] + transitionContainer.width - } - - private fun applyStateToWindowBackgroundLayer( - drawable: GradientDrawable, - state: State, - linearProgress: Float, - transitionContainer: View, - fadeWindowBackgroundLayer: Boolean, - drawHole: Boolean, - isLaunching: Boolean - ) { - // Update position. - transitionContainer.getLocationOnScreen(transitionContainerLocation) - drawable.setBounds( - state.left - transitionContainerLocation[0], - state.top - transitionContainerLocation[1], - state.right - transitionContainerLocation[0], - state.bottom - transitionContainerLocation[1] - ) - - // Update radius. - cornerRadii[0] = state.topCornerRadius - cornerRadii[1] = state.topCornerRadius - cornerRadii[2] = state.topCornerRadius - cornerRadii[3] = state.topCornerRadius - cornerRadii[4] = state.bottomCornerRadius - cornerRadii[5] = state.bottomCornerRadius - cornerRadii[6] = state.bottomCornerRadius - cornerRadii[7] = state.bottomCornerRadius - drawable.cornerRadii = cornerRadii - - // We first fade in the background layer to hide the expanding view, then fade it out - // with SRC mode to draw a hole punch in the status bar and reveal the opening window. - val fadeInProgress = - getProgress( - timings, - linearProgress, - timings.contentBeforeFadeOutDelay, - timings.contentBeforeFadeOutDuration - ) - - if (isLaunching) { - if (fadeInProgress < 1) { - val alpha = - interpolators.contentBeforeFadeOutInterpolator.getInterpolation(fadeInProgress) - drawable.alpha = (alpha * 0xFF).roundToInt() - } else if (fadeWindowBackgroundLayer) { - val fadeOutProgress = - getProgress( - timings, - linearProgress, - timings.contentAfterFadeInDelay, - timings.contentAfterFadeInDuration - ) - val alpha = - 1 - - interpolators.contentAfterFadeInInterpolator.getInterpolation( - fadeOutProgress - ) - drawable.alpha = (alpha * 0xFF).roundToInt() - - if (drawHole) { - drawable.setXfermode(SRC_MODE) - } - } else { - drawable.alpha = 0xFF - } - } else { - if (fadeInProgress < 1 && fadeWindowBackgroundLayer) { - val alpha = - interpolators.contentBeforeFadeOutInterpolator.getInterpolation(fadeInProgress) - drawable.alpha = (alpha * 0xFF).roundToInt() - - if (drawHole) { - drawable.setXfermode(SRC_MODE) - } - } else { - val fadeOutProgress = - getProgress( - timings, - linearProgress, - timings.contentAfterFadeInDelay, - timings.contentAfterFadeInDuration - ) - val alpha = - 1 - - interpolators.contentAfterFadeInInterpolator.getInterpolation( - fadeOutProgress - ) - drawable.alpha = (alpha * 0xFF).roundToInt() - drawable.setXfermode(null) - } - } - } -} diff --git a/systemUIAnim/src/com/android/systemui/animation/ViewDialogTransitionAnimatorController.kt b/systemUIAnim/src/com/android/systemui/animation/ViewDialogTransitionAnimatorController.kt deleted file mode 100644 index 307bc95941..0000000000 --- a/systemUIAnim/src/com/android/systemui/animation/ViewDialogTransitionAnimatorController.kt +++ /dev/null @@ -1,128 +0,0 @@ -/* - * 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.animation - -import android.util.Log -import android.view.GhostView -import android.view.View -import android.view.ViewGroup -import android.view.ViewRootImpl -import com.android.internal.jank.InteractionJankMonitor - -private const val TAG = "ViewDialogTransitionAnimatorController" - -/** A [DialogTransitionAnimator.Controller] that can animate a [View] from/to a dialog. */ -class ViewDialogTransitionAnimatorController -internal constructor( - private val source: View, - override val cuj: DialogCuj?, -) : DialogTransitionAnimator.Controller { - override val viewRoot: ViewRootImpl? - get() = source.viewRootImpl - - override val sourceIdentity: Any = source - - override fun startDrawingInOverlayOf(viewGroup: ViewGroup) { - // Delay the calls to `source.setVisibility()` during the animation. This must be called - // before `GhostView.addGhost()` is called because the latter will change the *transition* - // visibility, which won't be blocked and will affect the normal View visibility that is - // saved by `setShouldBlockVisibilityChanges()` for a later restoration. - (source as? LaunchableView)?.setShouldBlockVisibilityChanges(true) - - // Create a temporary ghost of the source (which will make it invisible) and add it - // to the host dialog. - if (source.parent !is ViewGroup) { - // This should usually not happen, but let's make sure we don't call GhostView.addGhost - // and crash if the view was detached right before we started the animation. - Log.w(TAG, "source was detached right before drawing was moved to overlay") - } else { - GhostView.addGhost(source, viewGroup) - } - } - - override fun stopDrawingInOverlay() { - // Note: here we should remove the ghost from the overlay, but in practice this is - // already done by the transition controller created below. - - if (source is LaunchableView) { - // Make sure we allow the source to change its visibility again and restore its previous - // value. - source.setShouldBlockVisibilityChanges(false) - } else { - // We made the source invisible earlier, so let's make it visible again. - source.visibility = View.VISIBLE - } - } - - override fun createTransitionController(): TransitionAnimator.Controller { - val delegate = GhostedViewTransitionAnimatorController(source) - return object : TransitionAnimator.Controller by delegate { - override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) { - // Remove the temporary ghost added by [startDrawingInOverlayOf]. Another - // ghost (that ghosts only the source content, and not its background) will - // be added right after this by the delegate and will be animated. - GhostView.removeGhost(source) - delegate.onTransitionAnimationStart(isExpandingFullyAbove) - } - - override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) { - delegate.onTransitionAnimationEnd(isExpandingFullyAbove) - - // At this point the view visibility is restored by the delegate, so we delay the - // visibility changes again and make it invisible while the dialog is shown. - if (source is LaunchableView) { - source.setShouldBlockVisibilityChanges(true) - source.setTransitionVisibility(View.INVISIBLE) - } else { - source.visibility = View.INVISIBLE - } - } - } - } - - override fun createExitController(): TransitionAnimator.Controller { - return GhostedViewTransitionAnimatorController(source) - } - - override fun shouldAnimateExit(): Boolean { - // The source should be invisible by now, if it's not then something else changed - // its visibility and we probably don't want to run the animation. - if (source.visibility != View.INVISIBLE) { - return false - } - - return source.isAttachedToWindow && ((source.parent as? View)?.isShown ?: true) - } - - override fun onExitAnimationCancelled() { - if (source is LaunchableView) { - // Make sure we allow the source to change its visibility again. - source.setShouldBlockVisibilityChanges(false) - } else { - // If the view is invisible it's probably because of us, so we make it visible - // again. - if (source.visibility == View.INVISIBLE) { - source.visibility = View.VISIBLE - } - } - } - - override fun jankConfigurationBuilder(): InteractionJankMonitor.Configuration.Builder? { - val type = cuj?.cujType ?: return null - return InteractionJankMonitor.Configuration.Builder.withView(type, source) - } -} diff --git a/systemUIAnim/src/com/android/systemui/animation/ViewHierarchyAnimator.kt b/systemUIAnim/src/com/android/systemui/animation/ViewHierarchyAnimator.kt deleted file mode 100644 index 00d9056529..0000000000 --- a/systemUIAnim/src/com/android/systemui/animation/ViewHierarchyAnimator.kt +++ /dev/null @@ -1,1160 +0,0 @@ -/* - * 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.animation - -import android.animation.Animator -import android.animation.AnimatorListenerAdapter -import android.animation.ObjectAnimator -import android.animation.PropertyValuesHolder -import android.animation.ValueAnimator -import android.util.IntProperty -import android.view.View -import android.view.ViewGroup -import android.view.animation.Interpolator -import com.android.app.animation.Interpolators -import kotlin.math.max -import kotlin.math.min - -/** - * A class that allows changes in bounds within a view hierarchy to animate seamlessly between the - * start and end state. - */ -class ViewHierarchyAnimator { - companion object { - /** Default values for the animation. These can all be overridden at call time. */ - private const val DEFAULT_DURATION = 500L - private val DEFAULT_INTERPOLATOR = Interpolators.STANDARD - private val DEFAULT_ADDITION_INTERPOLATOR = Interpolators.STANDARD_DECELERATE - private val DEFAULT_REMOVAL_INTERPOLATOR = Interpolators.STANDARD_ACCELERATE - private val DEFAULT_FADE_IN_INTERPOLATOR = Interpolators.ALPHA_IN - - /** The properties used to animate the view bounds. */ - private val PROPERTIES = - mapOf( - Bound.LEFT to createViewProperty(Bound.LEFT), - Bound.TOP to createViewProperty(Bound.TOP), - Bound.RIGHT to createViewProperty(Bound.RIGHT), - Bound.BOTTOM to createViewProperty(Bound.BOTTOM) - ) - - private fun createViewProperty(bound: Bound): IntProperty { - return object : IntProperty(bound.label) { - override fun setValue(view: View, value: Int) { - setBound(view, bound, value) - } - - override fun get(view: View): Int { - return getBound(view, bound) ?: bound.getValue(view) - } - } - } - - /** - * Instruct the animator to watch for changes to the layout of [rootView] and its children - * and animate them. It uses the given [interpolator] and [duration]. - * - * If a new layout change happens while an animation is already in progress, the animation - * is updated to continue from the current values to the new end state. - * - * By default, child views whole layout changes are animated as well. However, this can be - * controlled by [animateChildren]. If children are included, a set of [excludedViews] can - * be passed. If any dependent view from [rootView] matches an entry in this set, changes to - * that view will not be animated. - * - * The animator continues to respond to layout changes until [stopAnimating] is called. - * - * Successive calls to this method override the previous settings ([interpolator] and - * [duration]). The changes take effect on the next animation. - * - * Returns true if the [rootView] is already visible and will be animated, false otherwise. - * To animate the addition of a view, see [animateAddition]. - */ - @JvmOverloads - fun animate( - rootView: View, - interpolator: Interpolator = DEFAULT_INTERPOLATOR, - duration: Long = DEFAULT_DURATION, - animateChildren: Boolean = true, - excludedViews: Set = emptySet() - ): Boolean { - return animate( - rootView, - interpolator, - duration, - ephemeral = false, - animateChildren = animateChildren, - excludedViews = excludedViews - ) - } - - /** - * Like [animate], but only takes effect on the next layout update, then unregisters itself - * once the first animation is complete. - */ - @JvmOverloads - fun animateNextUpdate( - rootView: View, - interpolator: Interpolator = DEFAULT_INTERPOLATOR, - duration: Long = DEFAULT_DURATION, - animateChildren: Boolean = true, - excludedViews: Set = emptySet() - ): Boolean { - return animate( - rootView, - interpolator, - duration, - ephemeral = true, - animateChildren = animateChildren, - excludedViews = excludedViews - ) - } - - private fun animate( - rootView: View, - interpolator: Interpolator, - duration: Long, - ephemeral: Boolean, - animateChildren: Boolean, - excludedViews: Set = emptySet() - ): Boolean { - if ( - !occupiesSpace( - rootView.visibility, - rootView.left, - rootView.top, - rootView.right, - rootView.bottom - ) - ) { - return false - } - - val listener = createUpdateListener(interpolator, duration, ephemeral) - addListener( - rootView, - listener, - recursive = true, - animateChildren = animateChildren, - excludedViews = excludedViews - ) - return true - } - - /** - * Returns a new [View.OnLayoutChangeListener] that when called triggers a layout animation - * using [interpolator] and [duration]. - * - * If [ephemeral] is true, the listener is unregistered after the first animation. Otherwise - * it keeps listening for further updates. - */ - private fun createUpdateListener( - interpolator: Interpolator, - duration: Long, - ephemeral: Boolean - ): View.OnLayoutChangeListener { - return createListener(interpolator, duration, ephemeral) - } - - /** - * Instruct the animator to stop watching for changes to the layout of [rootView] and its - * children. - * - * Any animations already in progress continue until their natural conclusion. - */ - fun stopAnimating(rootView: View) { - recursivelyRemoveListener(rootView) - } - - /** - * Instruct the animator to watch for changes to the layout of [rootView] and its children, - * and animate the next time the hierarchy appears after not being visible. It uses the - * given [interpolator] and [duration]. - * - * The start state of the animation is controlled by [origin]. This value can be any of the - * four corners, any of the four edges, or the center of the view. If any margins are added - * on the side(s) of the origin, the translation of those margins can be included by - * specifying [includeMargins]. - * - * Returns true if the [rootView] is invisible and will be animated, false otherwise. To - * animate an already visible view, see [animate] and [animateNextUpdate]. - * - * Then animator unregisters itself once the first addition animation is complete. - * - * @param includeFadeIn true if the animator should also fade in the view and child views. - * @param fadeInInterpolator the interpolator to use when fading in the view. Unused if - * [includeFadeIn] is false. - * @param onAnimationEnd an optional runnable that will be run once the animation - * finishes successfully. Will not be run if the animation is cancelled. - */ - @JvmOverloads - fun animateAddition( - rootView: View, - origin: Hotspot = Hotspot.CENTER, - interpolator: Interpolator = DEFAULT_ADDITION_INTERPOLATOR, - duration: Long = DEFAULT_DURATION, - includeMargins: Boolean = false, - includeFadeIn: Boolean = false, - fadeInInterpolator: Interpolator = DEFAULT_FADE_IN_INTERPOLATOR, - onAnimationEnd: Runnable? = null, - ): Boolean { - if ( - occupiesSpace( - rootView.visibility, - rootView.left, - rootView.top, - rootView.right, - rootView.bottom - ) - ) { - return false - } - - val listener = - createAdditionListener( - origin, - interpolator, - duration, - ignorePreviousValues = !includeMargins, - onAnimationEnd, - ) - addListener(rootView, listener, recursive = true) - - if (!includeFadeIn) { - return true - } - - if (rootView is ViewGroup) { - // First, fade in the container view - val containerDuration = duration / 6 - createAndStartFadeInAnimator( - rootView, containerDuration, startDelay = 0, interpolator = fadeInInterpolator - ) - - // Then, fade in the child views - val childDuration = duration / 3 - for (i in 0 until rootView.childCount) { - val view = rootView.getChildAt(i) - createAndStartFadeInAnimator( - view, - childDuration, - // Wait until the container fades in before fading in the children - startDelay = containerDuration, - interpolator = fadeInInterpolator - ) - } - // For now, we don't recursively fade in additional sub views (e.g. grandchild - // views) since it hasn't been necessary, but we could add that functionality. - } else { - // Fade in the view during the first half of the addition - createAndStartFadeInAnimator( - rootView, - duration / 2, - startDelay = 0, - interpolator = fadeInInterpolator - ) - } - - return true - } - - /** - * Returns a new [View.OnLayoutChangeListener] that on the next call triggers a layout - * addition animation from the given [origin], using [interpolator] and [duration]. - * - * If [ignorePreviousValues] is true, the animation will only span the area covered by the - * new bounds. Otherwise it will include the margins between the previous and new bounds. - */ - private fun createAdditionListener( - origin: Hotspot, - interpolator: Interpolator, - duration: Long, - ignorePreviousValues: Boolean, - onAnimationEnd: Runnable? = null, - ): View.OnLayoutChangeListener { - return createListener( - interpolator, - duration, - ephemeral = true, - origin = origin, - ignorePreviousValues = ignorePreviousValues, - onAnimationEnd, - ) - } - - /** - * Returns a new [View.OnLayoutChangeListener] that when called triggers a layout animation - * using [interpolator] and [duration]. - * - * If [ephemeral] is true, the listener is unregistered after the first animation. Otherwise - * it keeps listening for further updates. - * - * [origin] specifies whether the start values should be determined by a hotspot, and - * [ignorePreviousValues] controls whether the previous values should be taken into account. - */ - private fun createListener( - interpolator: Interpolator, - duration: Long, - ephemeral: Boolean, - origin: Hotspot? = null, - ignorePreviousValues: Boolean = false, - onAnimationEnd: Runnable? = null, - ): View.OnLayoutChangeListener { - return object : View.OnLayoutChangeListener { - override fun onLayoutChange( - view: View?, - left: Int, - top: Int, - right: Int, - bottom: Int, - previousLeft: Int, - previousTop: Int, - previousRight: Int, - previousBottom: Int - ) { - if (view == null) return - - val startLeft = getBound(view, Bound.LEFT) ?: previousLeft - val startTop = getBound(view, Bound.TOP) ?: previousTop - val startRight = getBound(view, Bound.RIGHT) ?: previousRight - val startBottom = getBound(view, Bound.BOTTOM) ?: previousBottom - - (view.getTag(R.id.tag_animator) as? ObjectAnimator)?.cancel() - - if (!occupiesSpace(view.visibility, left, top, right, bottom)) { - setBound(view, Bound.LEFT, left) - setBound(view, Bound.TOP, top) - setBound(view, Bound.RIGHT, right) - setBound(view, Bound.BOTTOM, bottom) - return - } - - val startValues = - processStartValues( - origin, - left, - top, - right, - bottom, - startLeft, - startTop, - startRight, - startBottom, - ignorePreviousValues - ) - val endValues = - mapOf( - Bound.LEFT to left, - Bound.TOP to top, - Bound.RIGHT to right, - Bound.BOTTOM to bottom - ) - - val boundsToAnimate = mutableSetOf() - if (startValues.getValue(Bound.LEFT) != left) boundsToAnimate.add(Bound.LEFT) - if (startValues.getValue(Bound.TOP) != top) boundsToAnimate.add(Bound.TOP) - if (startValues.getValue(Bound.RIGHT) != right) boundsToAnimate.add(Bound.RIGHT) - if (startValues.getValue(Bound.BOTTOM) != bottom) { - boundsToAnimate.add(Bound.BOTTOM) - } - - if (boundsToAnimate.isNotEmpty()) { - startAnimation( - view, - boundsToAnimate, - startValues, - endValues, - interpolator, - duration, - ephemeral, - onAnimationEnd, - ) - } - } - } - } - - /** - * Animates the removal of [rootView] and its children from the hierarchy. It uses the given - * [interpolator] and [duration]. - * - * The end state of the animation is controlled by [destination]. This value can be any of - * the four corners, any of the four edges, or the center of the view. If any margins are - * added on the side(s) of the [destination], the translation of those margins can be - * included by specifying [includeMargins]. - * - * @param onAnimationEnd an optional runnable that will be run once the animation finishes - * successfully. Will not be run if the animation is cancelled. - */ - @JvmOverloads - fun animateRemoval( - rootView: View, - destination: Hotspot = Hotspot.CENTER, - interpolator: Interpolator = DEFAULT_REMOVAL_INTERPOLATOR, - duration: Long = DEFAULT_DURATION, - includeMargins: Boolean = false, - onAnimationEnd: Runnable? = null, - ): Boolean { - if ( - !occupiesSpace( - rootView.visibility, - rootView.left, - rootView.top, - rootView.right, - rootView.bottom - ) - ) { - return false - } - - val parent = rootView.parent as ViewGroup - - // Ensure that rootView's siblings animate nicely around the removal. - val listener = createUpdateListener(interpolator, duration, ephemeral = true) - for (i in 0 until parent.childCount) { - val child = parent.getChildAt(i) - if (child == rootView) continue - addListener(child, listener, recursive = false) - } - - val viewHasSiblings = parent.childCount > 1 - if (viewHasSiblings) { - // Remove the view so that a layout update is triggered for the siblings and they - // animate to their next position while the view's removal is also animating. - parent.removeView(rootView) - // By adding the view to the overlay, we can animate it while it isn't part of the - // view hierarchy. It is correctly positioned because we have its previous bounds, - // and we set them manually during the animation. - parent.overlay.add(rootView) - } - // If this view has no siblings, the parent view may shrink to (0,0) size and mess - // up the animation if we immediately remove the view. So instead, we just leave the - // view in the real hierarchy until the animation finishes. - - val endRunnable = Runnable { - if (viewHasSiblings) { - parent.overlay.remove(rootView) - } else { - parent.removeView(rootView) - } - onAnimationEnd?.run() - } - - val startValues = - mapOf( - Bound.LEFT to rootView.left, - Bound.TOP to rootView.top, - Bound.RIGHT to rootView.right, - Bound.BOTTOM to rootView.bottom - ) - val endValues = - processEndValuesForRemoval( - destination, - rootView, - rootView.left, - rootView.top, - rootView.right, - rootView.bottom, - includeMargins, - ) - - val boundsToAnimate = mutableSetOf() - if (rootView.left != endValues.getValue(Bound.LEFT)) boundsToAnimate.add(Bound.LEFT) - if (rootView.top != endValues.getValue(Bound.TOP)) boundsToAnimate.add(Bound.TOP) - if (rootView.right != endValues.getValue(Bound.RIGHT)) boundsToAnimate.add(Bound.RIGHT) - if (rootView.bottom != endValues.getValue(Bound.BOTTOM)) { - boundsToAnimate.add(Bound.BOTTOM) - } - - startAnimation( - rootView, - boundsToAnimate, - startValues, - endValues, - interpolator, - duration, - ephemeral = true, - endRunnable, - ) - - if (rootView is ViewGroup) { - // Shift the children so they maintain a consistent position within the shrinking - // view. - shiftChildrenForRemoval(rootView, destination, endValues, interpolator, duration) - - // Fade out the children during the first half of the removal, so they don't clutter - // too much once the view becomes very small. Then we fade out the view itself, in - // case it has its own content and/or background. - val startAlphas = FloatArray(rootView.childCount) - for (i in 0 until rootView.childCount) { - startAlphas[i] = rootView.getChildAt(i).alpha - } - - val animator = ValueAnimator.ofFloat(1f, 0f) - animator.interpolator = Interpolators.ALPHA_OUT - animator.duration = duration / 2 - animator.addUpdateListener { animation -> - for (i in 0 until rootView.childCount) { - rootView.getChildAt(i).alpha = - (animation.animatedValue as Float) * startAlphas[i] - } - } - animator.addListener( - object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - rootView - .animate() - .alpha(0f) - .setInterpolator(Interpolators.ALPHA_OUT) - .setDuration(duration / 2) - .start() - } - } - ) - animator.start() - } else { - // Fade out the view during the second half of the removal. - rootView - .animate() - .alpha(0f) - .setInterpolator(Interpolators.ALPHA_OUT) - .setDuration(duration / 2) - .setStartDelay(duration / 2) - .start() - } - - return true - } - - /** - * Animates the children of [rootView] so that its layout remains internally consistent as - * it shrinks towards [destination] and changes its bounds to [endValues]. - * - * Uses [interpolator] and [duration], which should match those of the removal animation. - */ - private fun shiftChildrenForRemoval( - rootView: ViewGroup, - destination: Hotspot, - endValues: Map, - interpolator: Interpolator, - duration: Long - ) { - for (i in 0 until rootView.childCount) { - val child = rootView.getChildAt(i) - val childStartValues = - mapOf( - Bound.LEFT to child.left, - Bound.TOP to child.top, - Bound.RIGHT to child.right, - Bound.BOTTOM to child.bottom - ) - val childEndValues = - processChildEndValuesForRemoval( - destination, - child.left, - child.top, - child.right, - child.bottom, - endValues.getValue(Bound.RIGHT) - endValues.getValue(Bound.LEFT), - endValues.getValue(Bound.BOTTOM) - endValues.getValue(Bound.TOP) - ) - - val boundsToAnimate = mutableSetOf() - if (child.left != endValues.getValue(Bound.LEFT)) boundsToAnimate.add(Bound.LEFT) - if (child.top != endValues.getValue(Bound.TOP)) boundsToAnimate.add(Bound.TOP) - if (child.right != endValues.getValue(Bound.RIGHT)) boundsToAnimate.add(Bound.RIGHT) - if (child.bottom != endValues.getValue(Bound.BOTTOM)) { - boundsToAnimate.add(Bound.BOTTOM) - } - - startAnimation( - child, - boundsToAnimate, - childStartValues, - childEndValues, - interpolator, - duration, - ephemeral = true - ) - } - } - - /** - * Returns whether the given [visibility] and bounds are consistent with a view being a - * contributing part of the hierarchy. - */ - private fun occupiesSpace( - visibility: Int, - left: Int, - top: Int, - right: Int, - bottom: Int - ): Boolean { - return visibility != View.GONE && left != right && top != bottom - } - - /** - * Computes the actual starting values based on the requested [origin] and on - * [ignorePreviousValues]. - * - * If [origin] is null, the resolved start values will be the same as those passed in, or - * the same as the new values if [ignorePreviousValues] is true. If [origin] is not null, - * the start values are resolved based on it, and [ignorePreviousValues] controls whether or - * not newly introduced margins are included. - * - * Base case - * ``` - * 1) origin=TOP - * x---------x x---------x x---------x x---------x x---------x - * x---------x | | | | | | - * -> -> x---------x -> | | -> | | - * x---------x | | - * x---------x - * 2) origin=BOTTOM_LEFT - * x---------x - * x-------x | | - * -> -> x----x -> | | -> | | - * x--x | | | | | | - * x x--x x----x x-------x x---------x - * 3) origin=CENTER - * x---------x - * x-----x x-------x | | - * x -> x---x -> | | -> | | -> | | - * x-----x x-------x | | - * x---------x - * ``` - * In case the start and end values differ in the direction of the origin, and - * [ignorePreviousValues] is false, the previous values are used and a translation is - * included in addition to the view expansion. - * ``` - * origin=TOP_LEFT - (0,0,0,0) -> (30,30,70,70) - * x - * x--x - * x--x x----x - * -> -> | | -> x------x - * x----x | | - * | | - * x------x - * ``` - */ - private fun processStartValues( - origin: Hotspot?, - newLeft: Int, - newTop: Int, - newRight: Int, - newBottom: Int, - previousLeft: Int, - previousTop: Int, - previousRight: Int, - previousBottom: Int, - ignorePreviousValues: Boolean - ): Map { - val startLeft = if (ignorePreviousValues) newLeft else previousLeft - val startTop = if (ignorePreviousValues) newTop else previousTop - val startRight = if (ignorePreviousValues) newRight else previousRight - val startBottom = if (ignorePreviousValues) newBottom else previousBottom - - var left = startLeft - var top = startTop - var right = startRight - var bottom = startBottom - - if (origin != null) { - left = - when (origin) { - Hotspot.CENTER -> (newLeft + newRight) / 2 - Hotspot.BOTTOM_LEFT, - Hotspot.LEFT, - Hotspot.TOP_LEFT -> min(startLeft, newLeft) - Hotspot.TOP, - Hotspot.BOTTOM -> newLeft - Hotspot.TOP_RIGHT, - Hotspot.RIGHT, - Hotspot.BOTTOM_RIGHT -> max(startRight, newRight) - } - top = - when (origin) { - Hotspot.CENTER -> (newTop + newBottom) / 2 - Hotspot.TOP_LEFT, - Hotspot.TOP, - Hotspot.TOP_RIGHT -> min(startTop, newTop) - Hotspot.LEFT, - Hotspot.RIGHT -> newTop - Hotspot.BOTTOM_RIGHT, - Hotspot.BOTTOM, - Hotspot.BOTTOM_LEFT -> max(startBottom, newBottom) - } - right = - when (origin) { - Hotspot.CENTER -> (newLeft + newRight) / 2 - Hotspot.TOP_RIGHT, - Hotspot.RIGHT, - Hotspot.BOTTOM_RIGHT -> max(startRight, newRight) - Hotspot.TOP, - Hotspot.BOTTOM -> newRight - Hotspot.BOTTOM_LEFT, - Hotspot.LEFT, - Hotspot.TOP_LEFT -> min(startLeft, newLeft) - } - bottom = - when (origin) { - Hotspot.CENTER -> (newTop + newBottom) / 2 - Hotspot.BOTTOM_RIGHT, - Hotspot.BOTTOM, - Hotspot.BOTTOM_LEFT -> max(startBottom, newBottom) - Hotspot.LEFT, - Hotspot.RIGHT -> newBottom - Hotspot.TOP_LEFT, - Hotspot.TOP, - Hotspot.TOP_RIGHT -> min(startTop, newTop) - } - } - - return mapOf( - Bound.LEFT to left, - Bound.TOP to top, - Bound.RIGHT to right, - Bound.BOTTOM to bottom - ) - } - - /** - * Computes a removal animation's end values based on the requested [destination] and the - * view's starting bounds. - * - * Examples: - * ``` - * 1) destination=TOP - * x---------x x---------x x---------x x---------x x---------x - * | | | | | | x---------x - * | | -> | | -> x---------x -> -> - * | | x---------x - * x---------x - * 2) destination=BOTTOM_LEFT - * x---------x - * | | x-------x - * | | -> | | -> x----x -> -> - * | | | | | | x--x - * x---------x x-------x x----x x--x x - * 3) destination=CENTER - * x---------x - * | | x-------x x-----x - * | | -> | | -> | | -> x---x -> x - * | | x-------x x-----x - * x---------x - * 4) destination=TOP, includeMargins=true (and view has large top margin) - * x---------x - * x---------x - * x---------x x---------x - * x---------x | | - * x---------x | | x---------x - * | | | | - * | | -> x---------x -> -> -> - * | | - * x---------x - * ``` - */ - private fun processEndValuesForRemoval( - destination: Hotspot, - rootView: View, - left: Int, - top: Int, - right: Int, - bottom: Int, - includeMargins: Boolean = false, - ): Map { - val marginAdjustment = - if (includeMargins && - (rootView.layoutParams is ViewGroup.MarginLayoutParams)) { - val marginLp = rootView.layoutParams as ViewGroup.MarginLayoutParams - DimenHolder( - left = marginLp.leftMargin, - top = marginLp.topMargin, - right = marginLp.rightMargin, - bottom = marginLp.bottomMargin - ) - } else { - DimenHolder(0, 0, 0, 0) - } - - // These are the end values to use *if* this bound is part of the destination. - val endLeft = left - marginAdjustment.left - val endTop = top - marginAdjustment.top - val endRight = right + marginAdjustment.right - val endBottom = bottom + marginAdjustment.bottom - - // For the below calculations: We need to ensure that the destination bound and the - // bound *opposite* to the destination bound end at the same value, to ensure that the - // view has size 0 for that dimension. - // For example, - // - If destination=TOP, then endTop == endBottom. Left and right stay the same. - // - If destination=RIGHT, then endRight == endLeft. Top and bottom stay the same. - // - If destination=BOTTOM_LEFT, then endBottom == endTop AND endLeft == endRight. - - return when (destination) { - Hotspot.TOP -> mapOf( - Bound.TOP to endTop, - Bound.BOTTOM to endTop, - Bound.LEFT to left, - Bound.RIGHT to right, - ) - Hotspot.TOP_RIGHT -> mapOf( - Bound.TOP to endTop, - Bound.BOTTOM to endTop, - Bound.RIGHT to endRight, - Bound.LEFT to endRight, - ) - Hotspot.RIGHT -> mapOf( - Bound.RIGHT to endRight, - Bound.LEFT to endRight, - Bound.TOP to top, - Bound.BOTTOM to bottom, - ) - Hotspot.BOTTOM_RIGHT -> mapOf( - Bound.BOTTOM to endBottom, - Bound.TOP to endBottom, - Bound.RIGHT to endRight, - Bound.LEFT to endRight, - ) - Hotspot.BOTTOM -> mapOf( - Bound.BOTTOM to endBottom, - Bound.TOP to endBottom, - Bound.LEFT to left, - Bound.RIGHT to right, - ) - Hotspot.BOTTOM_LEFT -> mapOf( - Bound.BOTTOM to endBottom, - Bound.TOP to endBottom, - Bound.LEFT to endLeft, - Bound.RIGHT to endLeft, - ) - Hotspot.LEFT -> mapOf( - Bound.LEFT to endLeft, - Bound.RIGHT to endLeft, - Bound.TOP to top, - Bound.BOTTOM to bottom, - ) - Hotspot.TOP_LEFT -> mapOf( - Bound.TOP to endTop, - Bound.BOTTOM to endTop, - Bound.LEFT to endLeft, - Bound.RIGHT to endLeft, - ) - Hotspot.CENTER -> mapOf( - Bound.LEFT to (endLeft + endRight) / 2, - Bound.RIGHT to (endLeft + endRight) / 2, - Bound.TOP to (endTop + endBottom) / 2, - Bound.BOTTOM to (endTop + endBottom) / 2, - ) - } - } - - /** - * Computes the end values for the child of a view being removed, based on the child's - * starting bounds, the removal's [destination], and the [parentWidth] and [parentHeight]. - * - * The end values always represent the child's position after it has been translated so that - * its center is at the [destination]. - * - * Examples: - * ``` - * 1) destination=TOP - * The child maintains its left and right positions, but is shifted up so that its - * center is on the parent's end top edge. - * 2) destination=BOTTOM_LEFT - * The child shifts so that its center is on the parent's end bottom left corner. - * 3) destination=CENTER - * The child shifts so that its own center is on the parent's end center. - * ``` - */ - private fun processChildEndValuesForRemoval( - destination: Hotspot, - left: Int, - top: Int, - right: Int, - bottom: Int, - parentWidth: Int, - parentHeight: Int - ): Map { - val halfWidth = (right - left) / 2 - val halfHeight = (bottom - top) / 2 - - val endLeft = - when (destination) { - Hotspot.CENTER -> (parentWidth / 2) - halfWidth - Hotspot.BOTTOM_LEFT, - Hotspot.LEFT, - Hotspot.TOP_LEFT -> -halfWidth - Hotspot.TOP_RIGHT, - Hotspot.RIGHT, - Hotspot.BOTTOM_RIGHT -> parentWidth - halfWidth - Hotspot.TOP, - Hotspot.BOTTOM -> left - } - val endTop = - when (destination) { - Hotspot.CENTER -> (parentHeight / 2) - halfHeight - Hotspot.TOP_LEFT, - Hotspot.TOP, - Hotspot.TOP_RIGHT -> -halfHeight - Hotspot.BOTTOM_RIGHT, - Hotspot.BOTTOM, - Hotspot.BOTTOM_LEFT -> parentHeight - halfHeight - Hotspot.LEFT, - Hotspot.RIGHT -> top - } - val endRight = - when (destination) { - Hotspot.CENTER -> (parentWidth / 2) + halfWidth - Hotspot.TOP_RIGHT, - Hotspot.RIGHT, - Hotspot.BOTTOM_RIGHT -> parentWidth + halfWidth - Hotspot.BOTTOM_LEFT, - Hotspot.LEFT, - Hotspot.TOP_LEFT -> halfWidth - Hotspot.TOP, - Hotspot.BOTTOM -> right - } - val endBottom = - when (destination) { - Hotspot.CENTER -> (parentHeight / 2) + halfHeight - Hotspot.BOTTOM_RIGHT, - Hotspot.BOTTOM, - Hotspot.BOTTOM_LEFT -> parentHeight + halfHeight - Hotspot.TOP_LEFT, - Hotspot.TOP, - Hotspot.TOP_RIGHT -> halfHeight - Hotspot.LEFT, - Hotspot.RIGHT -> bottom - } - - return mapOf( - Bound.LEFT to endLeft, - Bound.TOP to endTop, - Bound.RIGHT to endRight, - Bound.BOTTOM to endBottom - ) - } - - private fun addListener( - view: View, - listener: View.OnLayoutChangeListener, - recursive: Boolean = false, - animateChildren: Boolean = true, - excludedViews: Set = emptySet() - ) { - if (excludedViews.contains(view)) return - - // Make sure that only one listener is active at a time. - val previousListener = view.getTag(R.id.tag_layout_listener) - if (previousListener != null && previousListener is View.OnLayoutChangeListener) { - view.removeOnLayoutChangeListener(previousListener) - } - - view.addOnLayoutChangeListener(listener) - view.setTag(R.id.tag_layout_listener, listener) - if (animateChildren && view is ViewGroup && recursive) { - for (i in 0 until view.childCount) { - addListener( - view.getChildAt(i), - listener, - recursive = true, - animateChildren = animateChildren, - excludedViews = excludedViews - ) - } - } - } - - private fun recursivelyRemoveListener(view: View) { - val listener = view.getTag(R.id.tag_layout_listener) - if (listener != null && listener is View.OnLayoutChangeListener) { - view.setTag(R.id.tag_layout_listener, null /* tag */) - view.removeOnLayoutChangeListener(listener) - } - - if (view is ViewGroup) { - for (i in 0 until view.childCount) { - recursivelyRemoveListener(view.getChildAt(i)) - } - } - } - - private fun getBound(view: View, bound: Bound): Int? { - return view.getTag(bound.overrideTag) as? Int - } - - private fun setBound(view: View, bound: Bound, value: Int) { - view.setTag(bound.overrideTag, value) - bound.setValue(view, value) - } - - /** - * Initiates the animation of the requested [bounds] between [startValues] and [endValues] - * by creating the animator, registering it with the [view], and starting it using - * [interpolator] and [duration]. - * - * If [ephemeral] is true, the layout change listener is unregistered at the end of the - * animation, so no more animations happen. - */ - private fun startAnimation( - view: View, - bounds: Set, - startValues: Map, - endValues: Map, - interpolator: Interpolator, - duration: Long, - ephemeral: Boolean, - onAnimationEnd: Runnable? = null, - ) { - val propertyValuesHolders = - buildList { - bounds.forEach { bound -> - add( - PropertyValuesHolder.ofInt( - PROPERTIES[bound], - startValues.getValue(bound), - endValues.getValue(bound) - ) - ) - } - } - .toTypedArray() - - (view.getTag(R.id.tag_animator) as? ObjectAnimator)?.cancel() - - val animator = ObjectAnimator.ofPropertyValuesHolder(view, *propertyValuesHolders) - animator.interpolator = interpolator - animator.duration = duration - animator.addListener( - object : AnimatorListenerAdapter() { - var cancelled = false - - override fun onAnimationEnd(animation: Animator) { - view.setTag(R.id.tag_animator, null /* tag */) - bounds.forEach { view.setTag(it.overrideTag, null /* tag */) } - - // When an animation is cancelled, a new one might be taking over. We - // shouldn't unregister the listener yet. - if (ephemeral && !cancelled) { - // The duration is the same for the whole hierarchy, so it's safe to - // remove the listener recursively. We do this because some descendant - // views might not change bounds, and therefore not animate and leak the - // listener. - recursivelyRemoveListener(view) - } - if (!cancelled) { - onAnimationEnd?.run() - } - } - - override fun onAnimationCancel(animation: Animator) { - cancelled = true - } - } - ) - - bounds.forEach { bound -> setBound(view, bound, startValues.getValue(bound)) } - - view.setTag(R.id.tag_animator, animator) - animator.start() - } - - private fun createAndStartFadeInAnimator( - view: View, - duration: Long, - startDelay: Long, - interpolator: Interpolator - ) { - val animator = ObjectAnimator.ofFloat(view, "alpha", 1f) - animator.startDelay = startDelay - animator.duration = duration - animator.interpolator = interpolator - animator.addListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - view.setTag(R.id.tag_alpha_animator, null /* tag */) - } - }) - - (view.getTag(R.id.tag_alpha_animator) as? ObjectAnimator)?.cancel() - view.setTag(R.id.tag_alpha_animator, animator) - animator.start() - } - } - - /** An enum used to determine the origin of addition animations. */ - enum class Hotspot { - CENTER, - LEFT, - TOP_LEFT, - TOP, - TOP_RIGHT, - RIGHT, - BOTTOM_RIGHT, - BOTTOM, - BOTTOM_LEFT - } - - private enum class Bound(val label: String, val overrideTag: Int) { - LEFT("left", R.id.tag_override_left) { - override fun setValue(view: View, value: Int) { - view.left = value - } - - override fun getValue(view: View): Int { - return view.left - } - }, - TOP("top", R.id.tag_override_top) { - override fun setValue(view: View, value: Int) { - view.top = value - } - - override fun getValue(view: View): Int { - return view.top - } - }, - RIGHT("right", R.id.tag_override_right) { - override fun setValue(view: View, value: Int) { - view.right = value - } - - override fun getValue(view: View): Int { - return view.right - } - }, - BOTTOM("bottom", R.id.tag_override_bottom) { - override fun setValue(view: View, value: Int) { - view.bottom = value - } - - override fun getValue(view: View): Int { - return view.bottom - } - }; - - abstract fun setValue(view: View, value: Int) - abstract fun getValue(view: View): Int - } - - /** Simple data class to hold a set of dimens for left, top, right, bottom. */ - private data class DimenHolder( - val left: Int, - val top: Int, - val right: Int, - val bottom: Int, - ) -} diff --git a/systemUIAnim/src/com/android/systemui/animation/ViewRootSync.kt b/systemUIAnim/src/com/android/systemui/animation/ViewRootSync.kt deleted file mode 100644 index e4f6db57f6..0000000000 --- a/systemUIAnim/src/com/android/systemui/animation/ViewRootSync.kt +++ /dev/null @@ -1,43 +0,0 @@ -package com.android.systemui.animation - -import android.view.View -import android.window.SurfaceSyncGroup - -/** A util class to synchronize 2 view roots. */ -// TODO(b/200284684): Remove this class. -object ViewRootSync { - - /** - * Synchronize the next draw between the view roots of [view] and [otherView], then run [then]. - * - * Note that in some cases, the synchronization might not be possible (e.g. WM consumed the next - * transactions) or disabled (temporarily, on low ram devices). In this case, [then] will be - * called without synchronizing. - */ - fun synchronizeNextDraw(view: View, otherView: View, then: () -> Unit) { - if ( - !view.isAttachedToWindow || - view.viewRootImpl == null || - !otherView.isAttachedToWindow || - otherView.viewRootImpl == null || - view.viewRootImpl == otherView.viewRootImpl - ) { - // No need to synchronize if either the touch surface or dialog view is not attached - // to a window. - then() - return - } - - val syncGroup = SurfaceSyncGroup("SysUIAnimation") - syncGroup.addSyncCompleteCallback(view.context.mainExecutor) { then() } - syncGroup.add(view.rootSurfaceControl, null /* runnable */) - syncGroup.add(otherView.rootSurfaceControl, null /* runnable */) - syncGroup.markSyncReady() - } - - /** A Java-friendly API for [synchronizeNextDraw]. */ - @JvmStatic - fun synchronizeNextDraw(view: View, otherView: View, then: Runnable) { - synchronizeNextDraw(view, otherView, then::run) - } -} diff --git a/systemUIAnim/src/com/android/systemui/animation/back/BackAnimationSpec.kt b/systemUIAnim/src/com/android/systemui/animation/back/BackAnimationSpec.kt deleted file mode 100644 index 6c982a0450..0000000000 --- a/systemUIAnim/src/com/android/systemui/animation/back/BackAnimationSpec.kt +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.animation.back - -import android.util.DisplayMetrics -import android.view.animation.Interpolator -import android.window.BackEvent -import com.android.app.animation.Interpolators -import com.android.systemui.util.dpToPx - -/** Used to convert [BackEvent] into a [BackTransformation]. */ -fun interface BackAnimationSpec { - - /** Computes transformation based on a [backEvent] and sets it to [result]. */ - fun getBackTransformation( - backEvent: BackEvent, - progressY: Float, // TODO(b/265060720): Remove progressY. Could be retrieved from backEvent - result: BackTransformation, - ) - - companion object -} - -/** Create a [BackAnimationSpec] from [displayMetrics] and design specs. */ -fun BackAnimationSpec.Companion.createFloatingSurfaceAnimationSpec( - displayMetricsProvider: () -> DisplayMetrics, - maxMarginXdp: Float, - maxMarginYdp: Float, - minScale: Float, - translateXEasing: Interpolator = Interpolators.BACK_GESTURE, - translateYEasing: Interpolator = Interpolators.LINEAR, - scaleEasing: Interpolator = Interpolators.BACK_GESTURE, -): BackAnimationSpec { - return BackAnimationSpec { backEvent, progressY, result -> - val displayMetrics = displayMetricsProvider() - val screenWidthPx = displayMetrics.widthPixels - val screenHeightPx = displayMetrics.heightPixels - - val maxMarginXPx = maxMarginXdp.dpToPx(displayMetrics) - val maxMarginYPx = maxMarginYdp.dpToPx(displayMetrics) - val maxTranslationXByScale = (screenWidthPx - screenWidthPx * minScale) / 2 - val maxTranslationX = maxTranslationXByScale - maxMarginXPx - val maxTranslationYByScale = (screenHeightPx - screenHeightPx * minScale) / 2 - val maxTranslationY = maxTranslationYByScale - maxMarginYPx - val minScaleReversed = 1f - minScale - - val direction = if (backEvent.swipeEdge == BackEvent.EDGE_LEFT) 1 else -1 - val progressX = backEvent.progress - - val ratioTranslateX = translateXEasing.getInterpolation(progressX) - val ratioTranslateY = translateYEasing.getInterpolation(progressY) - val ratioScale = scaleEasing.getInterpolation(progressX) - - result.apply { - translateX = ratioTranslateX * direction * maxTranslationX - translateY = ratioTranslateY * maxTranslationY - scale = 1f - (ratioScale * minScaleReversed) - } - } -} diff --git a/systemUIAnim/src/com/android/systemui/animation/back/BackAnimationSpecForSysUi.kt b/systemUIAnim/src/com/android/systemui/animation/back/BackAnimationSpecForSysUi.kt deleted file mode 100644 index 536f2972ab..0000000000 --- a/systemUIAnim/src/com/android/systemui/animation/back/BackAnimationSpecForSysUi.kt +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.animation.back - -import android.util.DisplayMetrics - -/** - * SysUI transitions - Dismiss app (ST1) Return to launching surface or place of origin - * https://carbon.googleplex.com/predictive-back-for-apps/pages/st-1-dismiss-app - */ -fun BackAnimationSpec.Companion.dismissAppForSysUi( - displayMetricsProvider: () -> DisplayMetrics, -): BackAnimationSpec = - BackAnimationSpec.createFloatingSurfaceAnimationSpec( - displayMetricsProvider = displayMetricsProvider, - maxMarginXdp = 8f, - maxMarginYdp = 8f, - minScale = 0.8f, - ) - -/** - * SysUI transitions - Cross task (ST2) Return to previous task/app, keeping the current one open - * https://carbon.googleplex.com/predictive-back-for-apps/pages/st-2-cross-task - */ -fun BackAnimationSpec.Companion.crossTaskForSysUi( - displayMetricsProvider: () -> DisplayMetrics, -): BackAnimationSpec = - BackAnimationSpec.createFloatingSurfaceAnimationSpec( - displayMetricsProvider = displayMetricsProvider, - maxMarginXdp = 8f, - maxMarginYdp = 8f, - minScale = 0.8f, - ) - -/** - * SysUI transitions - Inner area dismiss (ST3) Dismiss non-detachable surface - * https://carbon.googleplex.com/predictive-back-for-apps/pages/st-3-inner-area-dismiss - */ -fun BackAnimationSpec.Companion.innerAreaDismissForSysUi( - displayMetricsProvider: () -> DisplayMetrics, -): BackAnimationSpec = - BackAnimationSpec.createFloatingSurfaceAnimationSpec( - displayMetricsProvider = displayMetricsProvider, - maxMarginXdp = 0f, - maxMarginYdp = 0f, - minScale = 0.9f, - ) - -/** - * SysUI transitions - Floating system surfaces (ST4) - * https://carbon.googleplex.com/predictive-back-for-apps/pages/st-4-floating-system-surfaces - */ -fun BackAnimationSpec.Companion.floatingSystemSurfacesForSysUi( - displayMetricsProvider: () -> DisplayMetrics, -): BackAnimationSpec = - BackAnimationSpec.createFloatingSurfaceAnimationSpec( - displayMetricsProvider = displayMetricsProvider, - maxMarginXdp = 8f, - maxMarginYdp = 8f, - minScale = 0.9f, - ) - -/** - * SysUI transitions - Bottomsheet (AT3) - * https://carbon.googleplex.com/predictive-back-for-apps/pages/at-3-bottom-sheets - */ -fun BackAnimationSpec.Companion.bottomSheetForSysUi( - displayMetricsProvider: () -> DisplayMetrics, -): BackAnimationSpec = BackAnimationSpec.createBottomsheetAnimationSpec(displayMetricsProvider) diff --git a/systemUIAnim/src/com/android/systemui/animation/back/BackTransformation.kt b/systemUIAnim/src/com/android/systemui/animation/back/BackTransformation.kt deleted file mode 100644 index 029f62c6e4..0000000000 --- a/systemUIAnim/src/com/android/systemui/animation/back/BackTransformation.kt +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.animation.back - -import android.view.View - -/** - * This object that represents the transformation to apply to the target. The properties of this - * object are mutable for performance reasons (avoid recreating this object) - */ -data class BackTransformation( - var translateX: Float = Float.NaN, - var translateY: Float = Float.NaN, - var scale: Float = Float.NaN, - var scalePivotPosition: ScalePivotPosition? = null, -) - -/** Enum that describes the location of the scale pivot position */ -enum class ScalePivotPosition { - // more options may be added in the future - CENTER, - BOTTOM_CENTER; - - fun applyTo(view: View) { - val pivotX = - when (this) { - CENTER -> view.width / 2f - BOTTOM_CENTER -> view.width / 2f - } - val pivotY = - when (this) { - CENTER -> view.height / 2f - BOTTOM_CENTER -> view.height.toFloat() - } - view.pivotX = pivotX - view.pivotY = pivotY - } -} - -/** Apply the transformation to the [targetView] */ -fun BackTransformation.applyTo(targetView: View) { - if (translateX.isFinite()) targetView.translationX = translateX - if (translateY.isFinite()) targetView.translationY = translateY - scalePivotPosition?.applyTo(targetView) - if (scale.isFinite()) { - targetView.scaleX = scale - targetView.scaleY = scale - } -} diff --git a/systemUIAnim/src/com/android/systemui/animation/back/BottomsheetBackAnimationSpec.kt b/systemUIAnim/src/com/android/systemui/animation/back/BottomsheetBackAnimationSpec.kt deleted file mode 100644 index b1945a1c37..0000000000 --- a/systemUIAnim/src/com/android/systemui/animation/back/BottomsheetBackAnimationSpec.kt +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2024 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.animation.back - -import android.util.DisplayMetrics -import android.view.animation.Interpolator -import com.android.app.animation.Interpolators -import com.android.systemui.util.dpToPx - -private const val MAX_SCALE_DELTA_DP = 48 - -/** Create a [BackAnimationSpec] from [displayMetrics] and design specs. */ -fun BackAnimationSpec.Companion.createBottomsheetAnimationSpec( - displayMetricsProvider: () -> DisplayMetrics, - scaleEasing: Interpolator = Interpolators.BACK_GESTURE, -): BackAnimationSpec { - return BackAnimationSpec { backEvent, _, result -> - val displayMetrics = displayMetricsProvider() - val screenWidthPx = displayMetrics.widthPixels - val minScale = 1 - MAX_SCALE_DELTA_DP.dpToPx(displayMetrics) / screenWidthPx - val progressX = backEvent.progress - val ratioScale = scaleEasing.getInterpolation(progressX) - result.apply { - scale = 1f - ratioScale * (1f - minScale) - scalePivotPosition = ScalePivotPosition.BOTTOM_CENTER - } - } -} diff --git a/systemUIAnim/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtension.kt b/systemUIAnim/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtension.kt deleted file mode 100644 index 8740d14672..0000000000 --- a/systemUIAnim/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtension.kt +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.animation.back - -import android.annotation.IntRange -import android.util.DisplayMetrics -import android.view.View -import android.window.BackEvent -import android.window.OnBackAnimationCallback -import android.window.OnBackInvokedDispatcher -import android.window.OnBackInvokedDispatcher.Priority - -/** - * Generates an [OnBackAnimationCallback] given a [backAnimationSpec]. [onBackProgressed] will be - * called on each update passing the current [BackTransformation]. - * - * Optionally, you can specify [onBackStarted], [onBackInvoked], and [onBackCancelled] callbacks. - * - * @sample com.android.systemui.util.registerAnimationOnBackInvoked - */ -fun onBackAnimationCallbackFrom( - backAnimationSpec: BackAnimationSpec, - displayMetrics: DisplayMetrics, // TODO(b/265060720): We could remove this - onBackProgressed: (BackTransformation) -> Unit, - onBackStarted: (BackEvent) -> Unit = {}, - onBackInvoked: () -> Unit = {}, - onBackCancelled: () -> Unit = {}, -): OnBackAnimationCallback { - return object : OnBackAnimationCallback { - private var initialY = 0f - private val lastTransformation = BackTransformation() - - override fun onBackStarted(backEvent: BackEvent) { - initialY = backEvent.touchY - onBackStarted(backEvent) - } - - override fun onBackProgressed(backEvent: BackEvent) { - val progressY = (backEvent.touchY - initialY) / displayMetrics.heightPixels - - backAnimationSpec.getBackTransformation( - backEvent = backEvent, - progressY = progressY, - result = lastTransformation, - ) - - onBackProgressed(lastTransformation) - } - - override fun onBackInvoked() { - onBackInvoked() - } - - override fun onBackCancelled() { - onBackCancelled() - } - } -} - -/** - * Register [OnBackAnimationCallback] when View is attached and unregister it when View is detached - * - * @sample com.android.systemui.util.registerAnimationOnBackInvoked - */ -fun View.registerOnBackInvokedCallbackOnViewAttached( - onBackInvokedDispatcher: OnBackInvokedDispatcher, - onBackAnimationCallback: OnBackAnimationCallback, - @Priority @IntRange(from = 0) priority: Int = OnBackInvokedDispatcher.PRIORITY_DEFAULT, -) { - addOnAttachStateChangeListener( - object : View.OnAttachStateChangeListener { - override fun onViewAttachedToWindow(v: View) { - onBackInvokedDispatcher.registerOnBackInvokedCallback( - priority, - onBackAnimationCallback - ) - } - - override fun onViewDetachedFromWindow(v: View) { - removeOnAttachStateChangeListener(this) - onBackInvokedDispatcher.unregisterOnBackInvokedCallback(onBackAnimationCallback) - } - } - ) - - if (isAttachedToWindow) { - onBackInvokedDispatcher.registerOnBackInvokedCallback(priority, onBackAnimationCallback) - } -} diff --git a/systemUIAnim/src/com/android/systemui/animation/view/LaunchableFrameLayout.kt b/systemUIAnim/src/com/android/systemui/animation/view/LaunchableFrameLayout.kt deleted file mode 100644 index 7538f188fb..0000000000 --- a/systemUIAnim/src/com/android/systemui/animation/view/LaunchableFrameLayout.kt +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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.animation.view - -import android.content.Context -import android.util.AttributeSet -import android.widget.FrameLayout -import com.android.systemui.animation.LaunchableView -import com.android.systemui.animation.LaunchableViewDelegate - -/** A [FrameLayout] that also implements [LaunchableView]. */ -open class LaunchableFrameLayout : FrameLayout, LaunchableView { - private val delegate = - LaunchableViewDelegate( - this, - superSetVisibility = { super.setVisibility(it) }, - ) - - constructor(context: Context) : super(context) - constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) - constructor( - context: Context, - attrs: AttributeSet?, - defStyleAttr: Int - ) : super(context, attrs, defStyleAttr) - - constructor( - context: Context, - attrs: AttributeSet?, - defStyleAttr: Int, - defStyleRes: Int - ) : super(context, attrs, defStyleAttr, defStyleRes) - - override fun setShouldBlockVisibilityChanges(block: Boolean) { - delegate.setShouldBlockVisibilityChanges(block) - } - - override fun setVisibility(visibility: Int) { - delegate.setVisibility(visibility) - } -} diff --git a/systemUIAnim/src/com/android/systemui/animation/view/LaunchableImageView.kt b/systemUIAnim/src/com/android/systemui/animation/view/LaunchableImageView.kt deleted file mode 100644 index e42b589f05..0000000000 --- a/systemUIAnim/src/com/android/systemui/animation/view/LaunchableImageView.kt +++ /dev/null @@ -1,56 +0,0 @@ -/* - * 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.animation.view - -import android.content.Context -import android.util.AttributeSet -import android.widget.ImageView -import com.android.systemui.animation.LaunchableView -import com.android.systemui.animation.LaunchableViewDelegate - -/** An [ImageView] that also implements [LaunchableView]. */ -open class LaunchableImageView : ImageView, LaunchableView { - private val delegate = - LaunchableViewDelegate( - this, - superSetVisibility = { super.setVisibility(it) }, - ) - - constructor(context: Context?) : super(context) - constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) - constructor( - context: Context?, - attrs: AttributeSet?, - defStyleAttr: Int, - ) : super(context, attrs, defStyleAttr) - - constructor( - context: Context?, - attrs: AttributeSet?, - defStyleAttr: Int, - defStyleRes: Int, - ) : super(context, attrs, defStyleAttr, defStyleRes) - - override fun setShouldBlockVisibilityChanges(block: Boolean) { - delegate.setShouldBlockVisibilityChanges(block) - } - - override fun setVisibility(visibility: Int) { - delegate.setVisibility(visibility) - } -} diff --git a/systemUIAnim/src/com/android/systemui/animation/view/LaunchableLinearLayout.kt b/systemUIAnim/src/com/android/systemui/animation/view/LaunchableLinearLayout.kt deleted file mode 100644 index bce262291f..0000000000 --- a/systemUIAnim/src/com/android/systemui/animation/view/LaunchableLinearLayout.kt +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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.animation.view - -import android.content.Context -import android.util.AttributeSet -import android.widget.LinearLayout -import com.android.systemui.animation.LaunchableView -import com.android.systemui.animation.LaunchableViewDelegate - -/** A [LinearLayout] that also implements [LaunchableView]. */ -open class LaunchableLinearLayout : LinearLayout, LaunchableView { - private val delegate = - LaunchableViewDelegate( - this, - superSetVisibility = { super.setVisibility(it) }, - ) - - constructor(context: Context?) : super(context) - constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) - constructor( - context: Context?, - attrs: AttributeSet?, - defStyleAttr: Int, - ) : super(context, attrs, defStyleAttr) - - constructor( - context: Context?, - attrs: AttributeSet?, - defStyleAttr: Int, - defStyleRes: Int, - ) : super(context, attrs, defStyleAttr, defStyleRes) - - override fun setShouldBlockVisibilityChanges(block: Boolean) { - delegate.setShouldBlockVisibilityChanges(block) - } - - override fun setVisibility(visibility: Int) { - delegate.setVisibility(visibility) - } -} diff --git a/systemUIAnim/src/com/android/systemui/animation/view/LaunchableTextView.kt b/systemUIAnim/src/com/android/systemui/animation/view/LaunchableTextView.kt deleted file mode 100644 index 147669528c..0000000000 --- a/systemUIAnim/src/com/android/systemui/animation/view/LaunchableTextView.kt +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package com.android.systemui.animation.view - -import android.content.Context -import android.util.AttributeSet -import android.widget.TextView -import com.android.systemui.animation.LaunchableView -import com.android.systemui.animation.LaunchableViewDelegate - -/** A [TextView] that also implements [LaunchableView]. */ -open class LaunchableTextView : TextView, LaunchableView { - private val delegate = - LaunchableViewDelegate( - this, - superSetVisibility = { super.setVisibility(it) }, - ) - - constructor(context: Context?) : super(context) - constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) - constructor( - context: Context?, - attrs: AttributeSet?, - defStyleAttr: Int, - ) : super(context, attrs, defStyleAttr) - - override fun setShouldBlockVisibilityChanges(block: Boolean) { - delegate.setShouldBlockVisibilityChanges(block) - } - - override fun setVisibility(visibility: Int) { - delegate.setVisibility(visibility) - } -} diff --git a/systemUIAnim/src/com/android/systemui/surfaceeffects/PaintDrawCallback.kt b/systemUIAnim/src/com/android/systemui/surfaceeffects/PaintDrawCallback.kt deleted file mode 100644 index d50979ccd0..0000000000 --- a/systemUIAnim/src/com/android/systemui/surfaceeffects/PaintDrawCallback.kt +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2024 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.surfaceeffects - -import android.graphics.Paint -import android.graphics.RenderEffect - -/** - * A callback with a [Paint] object that contains shader info, which is triggered every frame while - * animation is playing. Note that the [Paint] object here is always the same instance. - * - * This approach is more performant than other ones because [RenderEffect] forces an intermediate - * render pass of the View to a texture to feed into it. - * - * The usage of this callback is as follows: - *
{@code
- *     private var paint: Paint? = null
- *     // Override [View.onDraw].
- *     override fun onDraw(canvas: Canvas) {
- *         // RuntimeShader requires hardwareAcceleration.
- *         if (!canvas.isHardwareAccelerated) return
- *
- *         paint?.let { canvas.drawPaint(it) }
- *     }
- *
- *     // Given that this is called [PaintDrawCallback.onDraw]
- *     fun draw(paint: Paint) {
- *         this.paint = paint
- *
- *         // Must call invalidate to trigger View#onDraw
- *         invalidate()
- *     }
- * }
- * - * Please refer to [RenderEffectDrawCallback] for alternative approach. - */ -interface PaintDrawCallback { - fun onDraw(paint: Paint) -} diff --git a/systemUIAnim/src/com/android/systemui/surfaceeffects/RenderEffectDrawCallback.kt b/systemUIAnim/src/com/android/systemui/surfaceeffects/RenderEffectDrawCallback.kt deleted file mode 100644 index db7ee58090..0000000000 --- a/systemUIAnim/src/com/android/systemui/surfaceeffects/RenderEffectDrawCallback.kt +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2024 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.surfaceeffects - -import android.graphics.RenderEffect - -/** - * A callback with a [RenderEffect] object that contains shader info, which is triggered every frame - * while animation is playing. Note that the [RenderEffect] instance is different each time to - * update shader uniforms. - * - * The usage of this callback is as follows: - *
{@code
- *     private val xEffectDrawingCallback = RenderEffectDrawCallback() {
- *         val myOtherRenderEffect = createOtherRenderEffect()
- *         val chainEffect = RenderEffect.createChainEffect(renderEffect, myOtherRenderEffect)
- *         myView.setRenderEffect(chainEffect)
- *     }
- *
- *     private val xEffect = XEffect(config, xEffectDrawingCallback)
- * }
- */ -interface RenderEffectDrawCallback { - fun onDraw(renderEffect: RenderEffect) -} diff --git a/systemUIAnim/src/com/android/systemui/surfaceeffects/glowboxeffect/GlowBoxConfig.kt b/systemUIAnim/src/com/android/systemui/surfaceeffects/glowboxeffect/GlowBoxConfig.kt deleted file mode 100644 index 72f0e86f9d..0000000000 --- a/systemUIAnim/src/com/android/systemui/surfaceeffects/glowboxeffect/GlowBoxConfig.kt +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2024 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.surfaceeffects.glowboxeffect - -/** Parameters used to play [GlowBoxEffect]. */ -data class GlowBoxConfig( - /** Start center position X in px. */ - val startCenterX: Float, - /** Start center position Y in px. */ - val startCenterY: Float, - /** End center position X in px. */ - val endCenterX: Float, - /** End center position Y in px. */ - val endCenterY: Float, - /** Width of the box in px. */ - val width: Float, - /** Height of the box in px. */ - val height: Float, - /** Color of the box in ARGB, Apply alpha value if needed. */ - val color: Int, - /** Amount of blur (or glow) of the box. */ - val blurAmount: Float, - /** - * Duration of the animation. Note that the full duration of the animation is - * [duration] + [easeInDuration] + [easeOutDuration]. - */ - val duration: Long, - /** Ease in duration of the animation. */ - val easeInDuration: Long, - /** Ease out duration of the animation. */ - val easeOutDuration: Long, -) diff --git a/systemUIAnim/src/com/android/systemui/surfaceeffects/glowboxeffect/GlowBoxEffect.kt b/systemUIAnim/src/com/android/systemui/surfaceeffects/glowboxeffect/GlowBoxEffect.kt deleted file mode 100644 index 5e590c1ca0..0000000000 --- a/systemUIAnim/src/com/android/systemui/surfaceeffects/glowboxeffect/GlowBoxEffect.kt +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Copyright (C) 2024 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.surfaceeffects.glowboxeffect - -import android.animation.ValueAnimator -import android.graphics.Paint -import androidx.annotation.VisibleForTesting -import androidx.core.animation.doOnEnd -import com.android.systemui.surfaceeffects.PaintDrawCallback -import com.android.systemui.surfaceeffects.utils.MathUtils.lerp - -/** Glow box effect where the box moves from start to end positions defined in the [config]. */ -class GlowBoxEffect( - private var config: GlowBoxConfig, - private val paintDrawCallback: PaintDrawCallback, - private val stateChangedCallback: AnimationStateChangedCallback? = null -) { - private val glowBoxShader = - GlowBoxShader().apply { - setSize(config.width, config.height) - setCenter(config.startCenterX, config.startCenterY) - setBlur(config.blurAmount) - setColor(config.color) - } - private var animator: ValueAnimator? = null - @VisibleForTesting var state: AnimationState = AnimationState.NOT_PLAYING - private val paint = Paint().apply { shader = glowBoxShader } - - fun updateConfig(newConfig: GlowBoxConfig) { - this.config = newConfig - - with(glowBoxShader) { - setSize(config.width, config.height) - setCenter(config.startCenterX, config.startCenterY) - setBlur(config.blurAmount) - setColor(config.color) - } - } - - fun play() { - if (state != AnimationState.NOT_PLAYING) { - return - } - - playEaseIn() - } - - /** Finishes the animation with ease out. */ - fun finish(force: Boolean = false) { - // If it's playing ease out, cancel immediately. - if (force && state == AnimationState.EASE_OUT) { - animator?.cancel() - return - } - - // If it's playing either ease in or main, fast-forward to ease out. - if (state == AnimationState.EASE_IN || state == AnimationState.MAIN) { - animator?.pause() - playEaseOut() - } - - // At this point, animation state should be ease out. Cancel it if force is true. - if (force) { - animator?.cancel() - } - } - - private fun playEaseIn() { - if (state == AnimationState.EASE_IN) { - return - } - state = AnimationState.EASE_IN - stateChangedCallback?.onStart() - - animator = - ValueAnimator.ofFloat(0f, 1f).apply { - duration = config.easeInDuration - addUpdateListener { - val progress = it.animatedValue as Float - glowBoxShader.setCenter( - lerp(config.startCenterX, config.endCenterX, progress), - lerp(config.startCenterY, config.endCenterY, progress) - ) - - draw() - } - - doOnEnd { - animator = null - playMain() - } - - start() - } - } - - private fun playMain() { - if (state == AnimationState.MAIN) { - return - } - state = AnimationState.MAIN - - animator = - ValueAnimator.ofFloat(0f, 1f).apply { - duration = config.duration - addUpdateListener { draw() } - - doOnEnd { - animator = null - playEaseOut() - } - - start() - } - } - - private fun playEaseOut() { - if (state == AnimationState.EASE_OUT) return - state = AnimationState.EASE_OUT - - animator = - ValueAnimator.ofFloat(0f, 1f).apply { - duration = config.easeOutDuration - addUpdateListener { - val progress = it.animatedValue as Float - glowBoxShader.setCenter( - lerp(config.endCenterX, config.startCenterX, progress), - lerp(config.endCenterY, config.startCenterY, progress) - ) - - draw() - } - - doOnEnd { - animator = null - state = AnimationState.NOT_PLAYING - stateChangedCallback?.onEnd() - } - - start() - } - } - - private fun draw() { - paintDrawCallback.onDraw(paint) - } - - /** - * The animation state of the effect. The animation state transitions as follows: [EASE_IN] -> - * [MAIN] -> [EASE_OUT] -> [NOT_PLAYING]. - */ - enum class AnimationState { - EASE_IN, - MAIN, - EASE_OUT, - NOT_PLAYING, - } - - interface AnimationStateChangedCallback { - /** - * Triggered when the animation starts, specifically when the states goes from - * [AnimationState.NOT_PLAYING] to [AnimationState.EASE_IN]. - */ - fun onStart() - /** - * Triggered when the animation ends, specifically when the states goes from - * [AnimationState.EASE_OUT] to [AnimationState.NOT_PLAYING]. - */ - fun onEnd() - } -} diff --git a/systemUIAnim/src/com/android/systemui/surfaceeffects/glowboxeffect/GlowBoxShader.kt b/systemUIAnim/src/com/android/systemui/surfaceeffects/glowboxeffect/GlowBoxShader.kt deleted file mode 100644 index 36934086cc..0000000000 --- a/systemUIAnim/src/com/android/systemui/surfaceeffects/glowboxeffect/GlowBoxShader.kt +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2024 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.surfaceeffects.glowboxeffect - -import android.graphics.RuntimeShader -import com.android.systemui.surfaceeffects.shaderutil.SdfShaderLibrary - -/** Soft box shader. */ -class GlowBoxShader : RuntimeShader(GLOW_SHADER) { - // language=AGSL - private companion object { - private const val SHADER = - """ - uniform half2 in_center; - uniform half2 in_size; - uniform half in_blur; - layout(color) uniform half4 in_color; - - float4 main(float2 fragcoord) { - half glow = soften(sdBox(fragcoord - in_center, in_size), in_blur); - return in_color * (1. - glow); - } - """ - - private const val GLOW_SHADER = - SdfShaderLibrary.BOX_SDF + SdfShaderLibrary.SHADER_SDF_OPERATION_LIB + SHADER - } - - fun setCenter(x: Float, y: Float) { - setFloatUniform("in_center", x, y) - } - - fun setSize(width: Float, height: Float) { - setFloatUniform("in_size", width, height) - } - - fun setBlur(blurAmount: Float) { - setFloatUniform("in_blur", blurAmount) - } - - fun setColor(color: Int) { - setColorUniform("in_color", color) - } -} diff --git a/systemUIAnim/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffect.kt b/systemUIAnim/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffect.kt deleted file mode 100644 index 211b84f253..0000000000 --- a/systemUIAnim/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffect.kt +++ /dev/null @@ -1,366 +0,0 @@ -/* - * Copyright (C) 2024 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.surfaceeffects.loadingeffect - -import android.animation.Animator -import android.animation.AnimatorListenerAdapter -import android.animation.ValueAnimator -import android.graphics.Paint -import android.graphics.RenderEffect -import android.view.View -import com.android.systemui.surfaceeffects.PaintDrawCallback -import com.android.systemui.surfaceeffects.RenderEffectDrawCallback -import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseAnimationConfig -import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseShader - -/** - * Plays loading effect with the given configuration. - * - * @param baseType immutable base shader type. This is used for constructing the shader. Reconstruct - * the [LoadingEffect] if the base type needs to be changed. - * @param config immutable parameters that are used for drawing the effect. - * @param paintCallback triggered every frame when animation is playing. Use this to draw the effect - * with [Canvas.drawPaint]. - * @param renderEffectCallback triggered every frame when animation is playing. Use this to draw the - * effect with [RenderEffect]. - * @param animationStateChangedCallback triggered when the [AnimationState] changes. Optional. - * - * The client is responsible to actually draw the [Paint] or [RenderEffect] returned in the - * callback. Note that [View.invalidate] must be called on each callback. There are a few ways to - * render the effect: - * 1) Use [Canvas.drawPaint]. (Preferred. Significantly cheaper!) - * 2) Set [RenderEffect] to the [View]. (Good for chaining effects.) - * 3) Use [RenderNode.setRenderEffect]. (This may be least preferred, as 2 should do what you want.) - * - *

First approach is more performant than other ones because [RenderEffect] forces an - * intermediate render pass of the View to a texture to feed into it. - * - *

If going with the first approach, your custom [View] would look like as follow: - *

{@code
- *     private var paint: Paint? = null
- *     // Override [View.onDraw].
- *     override fun onDraw(canvas: Canvas) {
- *         // RuntimeShader requires hardwareAcceleration.
- *         if (!canvas.isHardwareAccelerated) return
- *
- *         paint?.let { canvas.drawPaint(it) }
- *     }
- *
- *     // This is called [Callback.onDraw]
- *     fun draw(paint: Paint) {
- *         this.paint = paint
- *
- *         // Must call invalidate to trigger View#onDraw
- *         invalidate()
- *     }
- * }
- * - *

If going with the second approach, it doesn't require an extra custom [View], and it is as - * simple as calling [View.setRenderEffect] followed by [View.invalidate]. You can also chain the - * effect with other [RenderEffect]. - * - *

Third approach is an option, but it's more of a boilerplate so you would like to stick with - * the second option. If you want to go with this option for some reason, below is the example: - *

{@code
- *     // Initialize the shader and paint to use to pass into the [Canvas].
- *     private val renderNode = RenderNode("LoadingEffect")
- *
- *     // Override [View.onDraw].
- *     override fun onDraw(canvas: Canvas) {
- *         // RuntimeShader requires hardwareAcceleration.
- *         if (!canvas.isHardwareAccelerated) return
- *
- *         if (renderNode.hasDisplayList()) {
- *             canvas.drawRenderNode(renderNode)
- *         }
- *     }
- *
- *     // This is called [Callback.onDraw]
- *     fun draw(renderEffect: RenderEffect) {
- *         renderNode.setPosition(0, 0, width, height)
- *         renderNode.setRenderEffect(renderEffect)
- *
- *         val recordingCanvas = renderNode.beginRecording()
- *         // We need at least 1 drawing instruction.
- *         recordingCanvas.drawColor(Color.TRANSPARENT)
- *         renderNode.endRecording()
- *
- *         // Must call invalidate to trigger View#onDraw
- *         invalidate()
- *     }
- * }
- */ -class LoadingEffect -private constructor( - baseType: TurbulenceNoiseShader.Companion.Type, - private val config: TurbulenceNoiseAnimationConfig, - private val paintCallback: PaintDrawCallback?, - private val renderEffectCallback: RenderEffectDrawCallback?, - private val animationStateChangedCallback: AnimationStateChangedCallback? = null -) { - constructor( - baseType: TurbulenceNoiseShader.Companion.Type, - config: TurbulenceNoiseAnimationConfig, - paintCallback: PaintDrawCallback, - animationStateChangedCallback: AnimationStateChangedCallback? = null - ) : this( - baseType, - config, - paintCallback, - renderEffectCallback = null, - animationStateChangedCallback - ) - constructor( - baseType: TurbulenceNoiseShader.Companion.Type, - config: TurbulenceNoiseAnimationConfig, - renderEffectCallback: RenderEffectDrawCallback, - animationStateChangedCallback: AnimationStateChangedCallback? = null - ) : this( - baseType, - config, - paintCallback = null, - renderEffectCallback, - animationStateChangedCallback - ) - - private val turbulenceNoiseShader: TurbulenceNoiseShader = - TurbulenceNoiseShader(baseType).apply { applyConfig(config) } - private var currentAnimator: ValueAnimator? = null - private var state: AnimationState = AnimationState.NOT_PLAYING - set(value) { - if (field != value) { - animationStateChangedCallback?.onStateChanged(field, value) - field = value - } - } - - // We create a paint instance only if the client renders it with Paint. - private val paint = - if (paintCallback != null) { - Paint().apply { this.shader = turbulenceNoiseShader } - } else { - null - } - - /** Plays LoadingEffect. */ - fun play() { - if (state != AnimationState.NOT_PLAYING) { - return // Ignore if any of the animation is playing. - } - - playEaseIn() - } - - // TODO(b/237282226): Support force finish. - /** Finishes the main animation, which triggers the ease-out animation. */ - fun finish() { - if (state == AnimationState.MAIN) { - // Calling Animator#end sets the animation state back to the initial state. Using pause - // to avoid visual artifacts. - currentAnimator?.pause() - currentAnimator = null - - playEaseOut() - } - } - - /** Updates the noise color dynamically. */ - fun updateColor(newColor: Int) { - turbulenceNoiseShader.setColor(newColor) - } - - /** Updates the noise color that's screen blended on top. */ - fun updateScreenColor(newColor: Int) { - turbulenceNoiseShader.setScreenColor(newColor) - } - - /** - * Retrieves the noise offset x, y, z values. This is useful for replaying the animation - * smoothly from the last animation, by passing in the last values to the next animation. - */ - fun getNoiseOffset(): Array { - return arrayOf( - turbulenceNoiseShader.noiseOffsetX, - turbulenceNoiseShader.noiseOffsetY, - turbulenceNoiseShader.noiseOffsetZ - ) - } - - private fun playEaseIn() { - if (state != AnimationState.NOT_PLAYING) { - return - } - state = AnimationState.EASE_IN - - val animator = ValueAnimator.ofFloat(0f, 1f) - animator.duration = config.easeInDuration.toLong() - - // Animation should start from the initial position to avoid abrupt transition. - val initialX = turbulenceNoiseShader.noiseOffsetX - val initialY = turbulenceNoiseShader.noiseOffsetY - val initialZ = turbulenceNoiseShader.noiseOffsetZ - - animator.addUpdateListener { updateListener -> - val timeInSec = updateListener.currentPlayTime * MS_TO_SEC - val progress = updateListener.animatedValue as Float - - turbulenceNoiseShader.setNoiseMove( - initialX + timeInSec * config.noiseMoveSpeedX, - initialY + timeInSec * config.noiseMoveSpeedY, - initialZ + timeInSec * config.noiseMoveSpeedZ - ) - - // TODO: Replace it with a better curve. - turbulenceNoiseShader.setOpacity(progress * config.luminosityMultiplier) - - draw() - } - - animator.addListener( - object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - currentAnimator = null - playMain() - } - } - ) - - animator.start() - this.currentAnimator = animator - } - - private fun playMain() { - if (state != AnimationState.EASE_IN) { - return - } - state = AnimationState.MAIN - - val animator = ValueAnimator.ofFloat(0f, 1f) - animator.duration = config.maxDuration.toLong() - - // Animation should start from the initial position to avoid abrupt transition. - val initialX = turbulenceNoiseShader.noiseOffsetX - val initialY = turbulenceNoiseShader.noiseOffsetY - val initialZ = turbulenceNoiseShader.noiseOffsetZ - - turbulenceNoiseShader.setOpacity(config.luminosityMultiplier) - - animator.addUpdateListener { updateListener -> - val timeInSec = updateListener.currentPlayTime * MS_TO_SEC - turbulenceNoiseShader.setNoiseMove( - initialX + timeInSec * config.noiseMoveSpeedX, - initialY + timeInSec * config.noiseMoveSpeedY, - initialZ + timeInSec * config.noiseMoveSpeedZ - ) - - draw() - } - - animator.addListener( - object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - currentAnimator = null - playEaseOut() - } - } - ) - - animator.start() - this.currentAnimator = animator - } - - private fun playEaseOut() { - if (state != AnimationState.MAIN) { - return - } - state = AnimationState.EASE_OUT - - val animator = ValueAnimator.ofFloat(0f, 1f) - animator.duration = config.easeOutDuration.toLong() - - // Animation should start from the initial position to avoid abrupt transition. - val initialX = turbulenceNoiseShader.noiseOffsetX - val initialY = turbulenceNoiseShader.noiseOffsetY - val initialZ = turbulenceNoiseShader.noiseOffsetZ - - animator.addUpdateListener { updateListener -> - val timeInSec = updateListener.currentPlayTime * MS_TO_SEC - val progress = updateListener.animatedValue as Float - - turbulenceNoiseShader.setNoiseMove( - initialX + timeInSec * config.noiseMoveSpeedX, - initialY + timeInSec * config.noiseMoveSpeedY, - initialZ + timeInSec * config.noiseMoveSpeedZ - ) - - // TODO: Replace it with a better curve. - turbulenceNoiseShader.setOpacity((1f - progress) * config.luminosityMultiplier) - - draw() - } - - animator.addListener( - object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - currentAnimator = null - state = AnimationState.NOT_PLAYING - } - } - ) - - animator.start() - this.currentAnimator = animator - } - - private fun draw() { - paintCallback?.onDraw(paint!!) - renderEffectCallback?.onDraw( - RenderEffect.createRuntimeShaderEffect( - turbulenceNoiseShader, - TurbulenceNoiseShader.BACKGROUND_UNIFORM - ) - ) - } - - /** - * States of the loading effect animation. - * - *

The state is designed to be follow the order below: [AnimationState.EASE_IN], - * [AnimationState.MAIN], [AnimationState.EASE_OUT]. Note that ease in and out don't necessarily - * mean the acceleration and deceleration in the animation curve. They simply mean each stage of - * the animation. (i.e. Intro, core, and rest) - */ - enum class AnimationState { - EASE_IN, - MAIN, - EASE_OUT, - NOT_PLAYING - } - - /** Optional callback that is triggered when the animation state changes. */ - interface AnimationStateChangedCallback { - /** - * A callback that's triggered when the [AnimationState] changes. Example usage is - * performing a cleanup when [AnimationState] becomes [NOT_PLAYING]. - */ - fun onStateChanged(oldState: AnimationState, newState: AnimationState) {} - } - - private companion object { - private const val MS_TO_SEC = 0.001f - } -} diff --git a/systemUIAnim/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffectView.kt b/systemUIAnim/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffectView.kt deleted file mode 100644 index aad593eb6a..0000000000 --- a/systemUIAnim/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffectView.kt +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2024 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.surfaceeffects.loadingeffect - -import android.content.Context -import android.graphics.BlendMode -import android.graphics.Canvas -import android.graphics.Paint -import android.util.AttributeSet -import android.view.View - -/** Custom View for drawing the [LoadingEffect] with [Canvas.drawPaint]. */ -open class LoadingEffectView(context: Context?, attrs: AttributeSet?) : View(context, attrs) { - - private var paint: Paint? = null - private var blendMode: BlendMode = BlendMode.SRC_OVER - - override fun onDraw(canvas: Canvas) { - if (!canvas.isHardwareAccelerated) { - return - } - paint?.let { canvas.drawPaint(it) } - } - - /** Designed to be called on [LoadingEffect.PaintDrawCallback.onDraw]. */ - fun draw(paint: Paint) { - this.paint = paint - this.paint!!.blendMode = blendMode - - invalidate() - } - - /** Sets the blend mode of the [Paint]. */ - fun setBlendMode(blendMode: BlendMode) { - this.blendMode = blendMode - } -} diff --git a/systemUIAnim/src/com/android/systemui/surfaceeffects/ripple/MultiRippleController.kt b/systemUIAnim/src/com/android/systemui/surfaceeffects/ripple/MultiRippleController.kt deleted file mode 100644 index d8e17c9c82..0000000000 --- a/systemUIAnim/src/com/android/systemui/surfaceeffects/ripple/MultiRippleController.kt +++ /dev/null @@ -1,49 +0,0 @@ -/* - * 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.surfaceeffects.ripple - -import androidx.annotation.VisibleForTesting - -/** Controller that handles playing [RippleAnimation]. */ -class MultiRippleController(private val multipleRippleView: MultiRippleView) { - - companion object { - /** Max number of ripple animations at a time. */ - @VisibleForTesting const val MAX_RIPPLE_NUMBER = 10 - } - - /** Updates all the ripple colors during the animation. */ - fun updateColor(color: Int) { - multipleRippleView.ripples.forEach { anim -> anim.updateColor(color) } - } - - fun play(rippleAnimation: RippleAnimation) { - if (multipleRippleView.ripples.size >= MAX_RIPPLE_NUMBER) { - return - } - - multipleRippleView.ripples.add(rippleAnimation) - - rippleAnimation.play { - // Remove ripple once the animation is done - multipleRippleView.ripples.remove(rippleAnimation) - } - - // Trigger drawing - multipleRippleView.invalidate() - } -} diff --git a/systemUIAnim/src/com/android/systemui/surfaceeffects/ripple/MultiRippleView.kt b/systemUIAnim/src/com/android/systemui/surfaceeffects/ripple/MultiRippleView.kt deleted file mode 100644 index 6c175ddf1e..0000000000 --- a/systemUIAnim/src/com/android/systemui/surfaceeffects/ripple/MultiRippleView.kt +++ /dev/null @@ -1,61 +0,0 @@ -/* - * 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.surfaceeffects.ripple - -import android.content.Context -import android.graphics.Canvas -import android.graphics.Paint -import android.util.AttributeSet -import android.view.View -import androidx.annotation.VisibleForTesting - -/** - * A view that allows multiple ripples to play. - * - * Use [MultiRippleController] to play ripple animations. - */ -class MultiRippleView(context: Context?, attrs: AttributeSet?) : View(context, attrs) { - - @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) - val ripples = ArrayList() - private val ripplePaint = Paint() - - companion object { - private const val TAG = "MultiRippleView" - } - - override fun onDraw(canvas: Canvas) { - if (!canvas.isHardwareAccelerated) { - // Drawing with the ripple shader requires hardware acceleration, so skip if it's - // unsupported. - return - } - - var shouldInvalidate = false - - ripples.forEach { anim -> - ripplePaint.shader = anim.rippleShader - canvas.drawPaint(ripplePaint) - - shouldInvalidate = shouldInvalidate || anim.isPlaying() - } - - if (shouldInvalidate) { - invalidate() - } - } -} diff --git a/systemUIAnim/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt b/systemUIAnim/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt deleted file mode 100644 index d4372507e2..0000000000 --- a/systemUIAnim/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt +++ /dev/null @@ -1,93 +0,0 @@ -/* - * 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.surfaceeffects.ripple - -import android.animation.Animator -import android.animation.AnimatorListenerAdapter -import android.animation.ValueAnimator -import androidx.annotation.VisibleForTesting -import androidx.core.graphics.ColorUtils - -/** A single ripple animation. */ -class RippleAnimation(private val config: RippleAnimationConfig) { - @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) - val rippleShader: RippleShader = RippleShader(config.rippleShape) - private val animator: ValueAnimator = ValueAnimator.ofFloat(0f, 1f) - - init { - applyConfigToShader() - } - - /** Updates the ripple color during the animation. */ - fun updateColor(color: Int) { - config.apply { config.color = color } - applyConfigToShader() - } - - @JvmOverloads - fun play(onAnimationEnd: Runnable? = null) { - if (isPlaying()) { - return // Ignore if ripple effect is already playing - } - - animator.duration = config.duration - animator.addUpdateListener { updateListener -> - val now = updateListener.currentPlayTime - val progress = updateListener.animatedValue as Float - rippleShader.rawProgress = progress - rippleShader.distortionStrength = if (config.shouldDistort) 1 - progress else 0f - rippleShader.time = now.toFloat() - } - animator.addListener( - object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - onAnimationEnd?.run() - } - } - ) - animator.start() - } - - /** Indicates whether the animation is playing. */ - fun isPlaying(): Boolean = animator.isRunning - - private fun applyConfigToShader() { - with(rippleShader) { - setCenter(config.centerX, config.centerY) - rippleSize.setMaxSize(config.maxWidth, config.maxHeight) - pixelDensity = config.pixelDensity - color = ColorUtils.setAlphaComponent(config.color, config.opacity) - sparkleStrength = config.sparkleStrength - - assignFadeParams(baseRingFadeParams, config.baseRingFadeParams) - assignFadeParams(sparkleRingFadeParams, config.sparkleRingFadeParams) - assignFadeParams(centerFillFadeParams, config.centerFillFadeParams) - } - } - - private fun assignFadeParams( - destFadeParams: RippleShader.FadeParams, - srcFadeParams: RippleShader.FadeParams? - ) { - srcFadeParams?.let { - destFadeParams.fadeInStart = it.fadeInStart - destFadeParams.fadeInEnd = it.fadeInEnd - destFadeParams.fadeOutStart = it.fadeOutStart - destFadeParams.fadeOutEnd = it.fadeOutEnd - } - } -} diff --git a/systemUIAnim/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationConfig.kt b/systemUIAnim/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationConfig.kt deleted file mode 100644 index 91c0a5b635..0000000000 --- a/systemUIAnim/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationConfig.kt +++ /dev/null @@ -1,29 +0,0 @@ -package com.android.systemui.surfaceeffects.ripple - -import android.graphics.Color - -/** - * A struct that holds the ripple animation configurations. - * - *

This configuration is designed to play a SINGLE animation. Do not reuse or modify the - * configuration parameters to play different animations, unless the value has to change within the - * single animation (e.g. Change color or opacity during the animation). Note that this data class - * is pulled out to make the [RippleAnimation] constructor succinct. - */ -data class RippleAnimationConfig( - val rippleShape: RippleShader.RippleShape = RippleShader.RippleShape.CIRCLE, - val duration: Long = 0L, - val centerX: Float = 0f, - val centerY: Float = 0f, - val maxWidth: Float = 0f, - val maxHeight: Float = 0f, - val pixelDensity: Float = 1f, - var color: Int = Color.WHITE, - val opacity: Int = RippleShader.RIPPLE_DEFAULT_ALPHA, - val sparkleStrength: Float = RippleShader.RIPPLE_SPARKLE_STRENGTH, - // Null means it uses default fade parameter values. - val baseRingFadeParams: RippleShader.FadeParams? = null, - val sparkleRingFadeParams: RippleShader.FadeParams? = null, - val centerFillFadeParams: RippleShader.FadeParams? = null, - val shouldDistort: Boolean = true -) diff --git a/systemUIAnim/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt b/systemUIAnim/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt deleted file mode 100644 index 7e56f4b3d2..0000000000 --- a/systemUIAnim/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt +++ /dev/null @@ -1,460 +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.surfaceeffects.ripple - -import android.graphics.RuntimeShader -import android.util.Log -import android.view.animation.Interpolator -import android.view.animation.PathInterpolator -import androidx.annotation.VisibleForTesting -import com.android.systemui.surfaceeffects.shaderutil.SdfShaderLibrary -import com.android.systemui.surfaceeffects.shaderutil.ShaderUtilLibrary - -/** - * Shader class that renders an expanding ripple effect. The ripple contains three elements: - * 1. an expanding filled [RippleShape] that appears in the beginning and quickly fades away - * 2. an expanding ring that appears throughout the effect - * 3. an expanding ring-shaped area that reveals noise over #2. - * - * The ripple shader will be default to the circle shape if not specified. - * - * Modeled after frameworks/base/graphics/java/android/graphics/drawable/RippleShader.java. - */ -class RippleShader(rippleShape: RippleShape = RippleShape.CIRCLE) : - RuntimeShader(buildShader(rippleShape)) { - - /** Shapes that the [RippleShader] supports. */ - enum class RippleShape { - CIRCLE, - ROUNDED_BOX, - ELLIPSE - } - // language=AGSL - companion object { - private val TAG = RippleShader::class.simpleName - - // Default fade in/ out values. The value range is [0,1]. - const val DEFAULT_FADE_IN_START = 0f - const val DEFAULT_FADE_OUT_END = 1f - - const val DEFAULT_BASE_RING_FADE_IN_END = 0.1f - const val DEFAULT_BASE_RING_FADE_OUT_START = 0.3f - - const val DEFAULT_SPARKLE_RING_FADE_IN_END = 0.1f - const val DEFAULT_SPARKLE_RING_FADE_OUT_START = 0.4f - - const val DEFAULT_CENTER_FILL_FADE_IN_END = 0f - const val DEFAULT_CENTER_FILL_FADE_OUT_START = 0f - const val DEFAULT_CENTER_FILL_FADE_OUT_END = 0.6f - - const val RIPPLE_SPARKLE_STRENGTH: Float = 0.3f - const val RIPPLE_DEFAULT_COLOR: Int = 0xffffffff.toInt() - const val RIPPLE_DEFAULT_ALPHA: Int = 115 // full opacity is 255. - - private const val SHADER_UNIFORMS = - """ - uniform vec2 in_center; - uniform vec2 in_size; - uniform float in_cornerRadius; - uniform float in_thickness; - uniform float in_time; - uniform float in_distort_radial; - uniform float in_distort_xy; - uniform float in_fadeSparkle; - uniform float in_fadeFill; - uniform float in_fadeRing; - uniform float in_blur; - uniform float in_pixelDensity; - layout(color) uniform vec4 in_color; - uniform float in_sparkle_strength; - """ - - private const val SHADER_CIRCLE_MAIN = - """ - vec4 main(vec2 p) { - vec2 p_distorted = distort(p, in_time, in_distort_radial, in_distort_xy); - float radius = in_size.x * 0.5; - float sparkleRing = soften(circleRing(p_distorted-in_center, radius), in_blur); - float inside = soften(sdCircle(p_distorted-in_center, radius * 1.25), in_blur); - float sparkle = sparkles(p - mod(p, in_pixelDensity * 0.8), in_time * 0.00175) - * (1.-sparkleRing) * in_fadeSparkle; - - float rippleInsideAlpha = (1.-inside) * in_fadeFill; - float rippleRingAlpha = (1.-sparkleRing) * in_fadeRing; - float rippleAlpha = max(rippleInsideAlpha, rippleRingAlpha) * in_color.a; - vec4 ripple = vec4(in_color.rgb, 1.0) * rippleAlpha; - return mix(ripple, vec4(sparkle), sparkle * in_sparkle_strength); - } - """ - - private const val SHADER_ROUNDED_BOX_MAIN = - """ - vec4 main(vec2 p) { - float sparkleRing = soften(roundedBoxRing(p-in_center, in_size, in_cornerRadius, - in_thickness), in_blur); - float inside = soften(sdRoundedBox(p-in_center, in_size * 1.25, in_cornerRadius), - in_blur); - float sparkle = sparkles(p - mod(p, in_pixelDensity * 0.8), in_time * 0.00175) - * (1.-sparkleRing) * in_fadeSparkle; - - float rippleInsideAlpha = (1.-inside) * in_fadeFill; - float rippleRingAlpha = (1.-sparkleRing) * in_fadeRing; - float rippleAlpha = max(rippleInsideAlpha, rippleRingAlpha) * in_color.a; - vec4 ripple = vec4(in_color.rgb, 1.0) * rippleAlpha; - return mix(ripple, vec4(sparkle), sparkle * in_sparkle_strength); - } - """ - - private const val SHADER_ELLIPSE_MAIN = - """ - vec4 main(vec2 p) { - vec2 p_distorted = distort(p, in_time, in_distort_radial, in_distort_xy); - - float sparkleRing = soften(ellipseRing(p_distorted-in_center, in_size), in_blur); - float inside = soften(sdEllipse(p_distorted-in_center, in_size * 1.2), in_blur); - float sparkle = sparkles(p - mod(p, in_pixelDensity * 0.8), in_time * 0.00175) - * (1.-sparkleRing) * in_fadeSparkle; - - float rippleInsideAlpha = (1.-inside) * in_fadeFill; - float rippleRingAlpha = (1.-sparkleRing) * in_fadeRing; - float rippleAlpha = max(rippleInsideAlpha, rippleRingAlpha) * in_color.a; - vec4 ripple = vec4(in_color.rgb, 1.0) * rippleAlpha; - return mix(ripple, vec4(sparkle), sparkle * in_sparkle_strength); - } - """ - - private const val CIRCLE_SHADER = - SHADER_UNIFORMS + - ShaderUtilLibrary.SHADER_LIB + - SdfShaderLibrary.SHADER_SDF_OPERATION_LIB + - SdfShaderLibrary.CIRCLE_SDF + - SHADER_CIRCLE_MAIN - private const val ROUNDED_BOX_SHADER = - SHADER_UNIFORMS + - ShaderUtilLibrary.SHADER_LIB + - SdfShaderLibrary.SHADER_SDF_OPERATION_LIB + - SdfShaderLibrary.ROUNDED_BOX_SDF + - SHADER_ROUNDED_BOX_MAIN - private const val ELLIPSE_SHADER = - SHADER_UNIFORMS + - ShaderUtilLibrary.SHADER_LIB + - SdfShaderLibrary.SHADER_SDF_OPERATION_LIB + - SdfShaderLibrary.ELLIPSE_SDF + - SHADER_ELLIPSE_MAIN - - private fun buildShader(rippleShape: RippleShape): String = - when (rippleShape) { - RippleShape.CIRCLE -> CIRCLE_SHADER - RippleShape.ROUNDED_BOX -> ROUNDED_BOX_SHADER - RippleShape.ELLIPSE -> ELLIPSE_SHADER - } - - private fun subProgress(start: Float, end: Float, progress: Float): Float { - // Avoid division by 0. - if (start == end) { - // If start and end are the same and progress has exceeded the start/ end point, - // treat it as 1, otherwise 0. - return if (progress > start) 1f else 0f - } - - val min = Math.min(start, end) - val max = Math.max(start, end) - val sub = Math.min(Math.max(progress, min), max) - return (sub - start) / (end - start) - } - - private fun getFade(fadeParams: FadeParams, rawProgress: Float): Float { - val fadeIn = subProgress(fadeParams.fadeInStart, fadeParams.fadeInEnd, rawProgress) - val fadeOut = - 1f - subProgress(fadeParams.fadeOutStart, fadeParams.fadeOutEnd, rawProgress) - - return Math.min(fadeIn, fadeOut) - } - - private fun lerp(start: Float, stop: Float, amount: Float): Float { - return start + (stop - start) * amount - } - - // Copied from [Interpolators#STANDARD]. This is to remove dependency on AnimationLib. - private val STANDARD: Interpolator = PathInterpolator(0.2f, 0f, 0f, 1f) - } - - /** Sets the center position of the ripple. */ - fun setCenter(x: Float, y: Float) { - setFloatUniform("in_center", x, y) - } - - /** - * Blur multipliers for the ripple. - * - *

It interpolates from [blurStart] to [blurEnd] based on the [progress]. Increase number to - * add more blur. - */ - var blurStart: Float = 1.25f - var blurEnd: Float = 0.5f - - /** Size of the ripple. */ - val rippleSize = RippleSize() - - /** - * Linear progress of the ripple. Float value between [0, 1]. - * - *

Note that the progress here is expected to be linear without any curve applied. - */ - var rawProgress: Float = 0.0f - set(value) { - field = value - progress = STANDARD.getInterpolation(value) - - setFloatUniform("in_fadeSparkle", getFade(sparkleRingFadeParams, value)) - setFloatUniform("in_fadeRing", getFade(baseRingFadeParams, value)) - setFloatUniform("in_fadeFill", getFade(centerFillFadeParams, value)) - } - - /** Progress with Standard easing curve applied. */ - private var progress: Float = 0.0f - set(value) { - field = value - - rippleSize.update(value) - - setFloatUniform("in_size", rippleSize.currentWidth, rippleSize.currentHeight) - setFloatUniform("in_thickness", rippleSize.currentHeight * 0.5f) - // Corner radius is always max of the min between the current width and height. - setFloatUniform( - "in_cornerRadius", - Math.min(rippleSize.currentWidth, rippleSize.currentHeight) - ) - setFloatUniform("in_blur", lerp(1.25f, 0.5f, value)) - } - - /** Play time since the start of the effect. */ - var time: Float = 0.0f - set(value) { - field = value - setFloatUniform("in_time", value) - } - - /** A hex value representing the ripple color, in the format of ARGB */ - var color: Int = 0xffffff - set(value) { - field = value - setColorUniform("in_color", value) - } - - /** - * Noise sparkle intensity. Expected value between [0, 1]. The sparkle is white, and thus with - * strength 0 it's transparent, leaving the ripple fully smooth, while with strength 1 it's - * opaque white and looks the most grainy. - */ - var sparkleStrength: Float = 0.0f - set(value) { - field = value - setFloatUniform("in_sparkle_strength", value) - } - - /** Distortion strength of the ripple. Expected value between[0, 1]. */ - var distortionStrength: Float = 0.0f - set(value) { - field = value - setFloatUniform("in_distort_radial", 75 * rawProgress * value) - setFloatUniform("in_distort_xy", 75 * value) - } - - /** - * Pixel density of the screen that the effects are rendered to. - * - *

This value should come from [resources.displayMetrics.density]. - */ - var pixelDensity: Float = 1.0f - set(value) { - field = value - setFloatUniform("in_pixelDensity", value) - } - - /** Parameters that are used to fade in/ out of the sparkle ring. */ - val sparkleRingFadeParams = - FadeParams( - DEFAULT_FADE_IN_START, - DEFAULT_SPARKLE_RING_FADE_IN_END, - DEFAULT_SPARKLE_RING_FADE_OUT_START, - DEFAULT_FADE_OUT_END - ) - - /** - * Parameters that are used to fade in/ out of the base ring. - * - *

Note that the shader draws the sparkle ring on top of the base ring. - */ - val baseRingFadeParams = - FadeParams( - DEFAULT_FADE_IN_START, - DEFAULT_BASE_RING_FADE_IN_END, - DEFAULT_BASE_RING_FADE_OUT_START, - DEFAULT_FADE_OUT_END - ) - - /** Parameters that are used to fade in/ out of the center fill. */ - val centerFillFadeParams = - FadeParams( - DEFAULT_FADE_IN_START, - DEFAULT_CENTER_FILL_FADE_IN_END, - DEFAULT_CENTER_FILL_FADE_OUT_START, - DEFAULT_CENTER_FILL_FADE_OUT_END - ) - - /** - * Parameters used for fade in and outs of the ripple. - * - *

Note that all the fade in/ outs are "linear" progression. - * - * ``` - * (opacity) - * 1 - * │ - * maxAlpha ← ―――――――――――― - * │ / \ - * │ / \ - * minAlpha ←――――/ \―――― (alpha change) - * │ - * │ - * 0 ―――↑―――↑―――――――――↑―――↑――――1 (progress) - * fadeIn fadeOut - * Start & End Start & End - * ``` - * - *

If no fade in/ out is needed, set [fadeInStart] and [fadeInEnd] to 0; [fadeOutStart] and - * [fadeOutEnd] to 1. - */ - data class FadeParams( - /** - * The starting point of the fade out which ends at [fadeInEnd], given that the animation - * goes from 0 to 1. - */ - var fadeInStart: Float = DEFAULT_FADE_IN_START, - /** - * The endpoint of the fade in when the fade in starts at [fadeInStart], given that the - * animation goes from 0 to 1. - */ - var fadeInEnd: Float, - /** - * The starting point of the fade out which ends at 1, given that the animation goes from 0 - * to 1. - */ - var fadeOutStart: Float, - - /** The endpoint of the fade out, given that the animation goes from 0 to 1. */ - var fadeOutEnd: Float = DEFAULT_FADE_OUT_END, - ) - - /** - * Desired size of the ripple at a point t in [progress]. - * - *

Note that [progress] is curved and normalized. Below is an example usage: - * SizeAtProgress(t= 0f, width= 0f, height= 0f), SizeAtProgress(t= 0.2f, width= 500f, height= - * 700f), SizeAtProgress(t= 1f, width= 100f, height= 300f) - * - *

For simple ripple effects, you will want to use [setMaxSize] as it is translated into: - * SizeAtProgress(t= 0f, width= 0f, height= 0f), SizeAtProgress(t= 1f, width= maxWidth, height= - * maxHeight) - */ - data class SizeAtProgress( - /** Time t in [0,1] progress range. */ - var t: Float, - /** Target width size of the ripple at time [t]. */ - var width: Float, - /** Target height size of the ripple at time [t]. */ - var height: Float - ) - - /** Updates and stores the ripple size. */ - inner class RippleSize { - @VisibleForTesting var sizes = mutableListOf() - @VisibleForTesting var currentSizeIndex = 0 - @VisibleForTesting val initialSize = SizeAtProgress(0f, 0f, 0f) - - var currentWidth: Float = 0f - private set - var currentHeight: Float = 0f - private set - - /** - * Sets the max size of the ripple. - * - *

Use this if the ripple shape simply changes linearly. - */ - fun setMaxSize(width: Float, height: Float) { - setSizeAtProgresses(initialSize, SizeAtProgress(1f, width, height)) - } - - /** - * Sets the list of [sizes]. - * - *

Note that setting this clears the existing sizes. - */ - fun setSizeAtProgresses(vararg sizes: SizeAtProgress) { - // Reset everything. - this.sizes.clear() - currentSizeIndex = 0 - - this.sizes.addAll(sizes) - this.sizes.sortBy { it.t } - } - - /** - * Updates the current ripple size based on the progress. - * - *

Should be called when progress updates. - */ - fun update(progress: Float) { - val targetIndex = updateTargetIndex(progress) - val prevIndex = Math.max(targetIndex - 1, 0) - - val targetSize = sizes[targetIndex] - val prevSize = sizes[prevIndex] - - val subProgress = subProgress(prevSize.t, targetSize.t, progress) - - currentWidth = targetSize.width * subProgress + prevSize.width - currentHeight = targetSize.height * subProgress + prevSize.height - } - - private fun updateTargetIndex(progress: Float): Int { - if (sizes.isEmpty()) { - // It could be empty on init. - if (progress > 0f) { - Log.e( - TAG, - "Did you forget to set the ripple size? Use [setMaxSize] or " + - "[setSizeAtProgresses] before playing the animation." - ) - } - // If there's no size is set, we set everything to 0 and return early. - setSizeAtProgresses(initialSize) - return currentSizeIndex - } - - var candidate = sizes[currentSizeIndex] - - while (progress > candidate.t) { - currentSizeIndex = Math.min(currentSizeIndex + 1, sizes.size - 1) - candidate = sizes[currentSizeIndex] - } - - return currentSizeIndex - } - } -} diff --git a/systemUIAnim/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt b/systemUIAnim/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt deleted file mode 100644 index b899127095..0000000000 --- a/systemUIAnim/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt +++ /dev/null @@ -1,252 +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.surfaceeffects.ripple - -import android.animation.Animator -import android.animation.AnimatorListenerAdapter -import android.animation.ValueAnimator -import android.content.Context -import android.content.res.Configuration -import android.graphics.Canvas -import android.graphics.Paint -import android.util.AttributeSet -import android.view.View -import androidx.core.graphics.ColorUtils -import com.android.systemui.surfaceeffects.ripple.RippleShader.RippleShape - -/** - * A generic expanding ripple effect. - * - * Set up the shader with a desired [RippleShape] using [setupShader], [setMaxSize] and [setCenter], - * then call [startRipple] to trigger the ripple expansion. - */ -open class RippleView(context: Context?, attrs: AttributeSet?) : View(context, attrs) { - - protected lateinit var rippleShader: RippleShader - lateinit var rippleShape: RippleShape - private set - - private val ripplePaint = Paint() - protected val animator: ValueAnimator = ValueAnimator.ofFloat(0f, 1f) - - var duration: Long = 1750 - - fun setMaxSize(maxWidth: Float, maxHeight: Float) { - rippleShader.rippleSize.setMaxSize(maxWidth, maxHeight) - } - - private var centerX: Float = 0.0f - private var centerY: Float = 0.0f - fun setCenter(x: Float, y: Float) { - this.centerX = x - this.centerY = y - rippleShader.setCenter(x, y) - } - - override fun onConfigurationChanged(newConfig: Configuration?) { - rippleShader.pixelDensity = resources.displayMetrics.density - super.onConfigurationChanged(newConfig) - } - - override fun onAttachedToWindow() { - rippleShader.pixelDensity = resources.displayMetrics.density - super.onAttachedToWindow() - } - - /** Initializes the shader. Must be called before [startRipple]. */ - fun setupShader(rippleShape: RippleShape = RippleShape.CIRCLE) { - this.rippleShape = rippleShape - rippleShader = RippleShader(rippleShape) - - rippleShader.color = RippleShader.RIPPLE_DEFAULT_COLOR - rippleShader.rawProgress = 0f - rippleShader.sparkleStrength = RippleShader.RIPPLE_SPARKLE_STRENGTH - rippleShader.pixelDensity = resources.displayMetrics.density - - ripplePaint.shader = rippleShader - } - - /** - * Sets the fade parameters for the base ring. - * - *

Base ring indicates a blurred ring below the sparkle ring. See - * [RippleShader.baseRingFadeParams]. - */ - @JvmOverloads - fun setBaseRingFadeParams( - fadeInStart: Float = rippleShader.baseRingFadeParams.fadeInStart, - fadeInEnd: Float = rippleShader.baseRingFadeParams.fadeInEnd, - fadeOutStart: Float = rippleShader.baseRingFadeParams.fadeOutStart, - fadeOutEnd: Float = rippleShader.baseRingFadeParams.fadeOutEnd - ) { - setFadeParams( - rippleShader.baseRingFadeParams, - fadeInStart, - fadeInEnd, - fadeOutStart, - fadeOutEnd - ) - } - - /** - * Sets the fade parameters for the sparkle ring. - * - *

Sparkle ring refers to the ring that's drawn on top of the base ring. See - * [RippleShader.sparkleRingFadeParams]. - */ - @JvmOverloads - fun setSparkleRingFadeParams( - fadeInStart: Float = rippleShader.sparkleRingFadeParams.fadeInStart, - fadeInEnd: Float = rippleShader.sparkleRingFadeParams.fadeInEnd, - fadeOutStart: Float = rippleShader.sparkleRingFadeParams.fadeOutStart, - fadeOutEnd: Float = rippleShader.sparkleRingFadeParams.fadeOutEnd - ) { - setFadeParams( - rippleShader.sparkleRingFadeParams, - fadeInStart, - fadeInEnd, - fadeOutStart, - fadeOutEnd - ) - } - - /** - * Sets the fade parameters for the center fill. - * - *

One common use case is set all the params to 1, which completely removes the center fill. - * See [RippleShader.centerFillFadeParams]. - */ - @JvmOverloads - fun setCenterFillFadeParams( - fadeInStart: Float = rippleShader.centerFillFadeParams.fadeInStart, - fadeInEnd: Float = rippleShader.centerFillFadeParams.fadeInEnd, - fadeOutStart: Float = rippleShader.centerFillFadeParams.fadeOutStart, - fadeOutEnd: Float = rippleShader.centerFillFadeParams.fadeOutEnd - ) { - setFadeParams( - rippleShader.centerFillFadeParams, - fadeInStart, - fadeInEnd, - fadeOutStart, - fadeOutEnd - ) - } - - private fun setFadeParams( - fadeParams: RippleShader.FadeParams, - fadeInStart: Float, - fadeInEnd: Float, - fadeOutStart: Float, - fadeOutEnd: Float - ) { - with(fadeParams) { - this.fadeInStart = fadeInStart - this.fadeInEnd = fadeInEnd - this.fadeOutStart = fadeOutStart - this.fadeOutEnd = fadeOutEnd - } - } - - /** - * Sets blur multiplier at start and end of the progress. - * - *

It interpolates between [start] and [end]. No need to set it if using default blur. - */ - fun setBlur(start: Float, end: Float) { - rippleShader.blurStart = start - rippleShader.blurEnd = end - } - - /** - * Sets the list of [RippleShader.SizeAtProgress]. - * - *

Note that this clears the list before it sets with the new data. - */ - fun setSizeAtProgresses(vararg targetSizes: RippleShader.SizeAtProgress) { - rippleShader.rippleSize.setSizeAtProgresses(*targetSizes) - } - - @JvmOverloads - fun startRipple(onAnimationEnd: Runnable? = null) { - if (animator.isRunning) { - return // Ignore if ripple effect is already playing - } - animator.duration = duration - animator.addUpdateListener { updateListener -> - val now = updateListener.currentPlayTime - val progress = updateListener.animatedValue as Float - rippleShader.rawProgress = progress - rippleShader.distortionStrength = 1 - progress - rippleShader.time = now.toFloat() - invalidate() - } - animator.addListener( - object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - onAnimationEnd?.run() - } - } - ) - animator.start() - } - - /** - * Set the color to be used for the ripple. - * - * The alpha value of the color will be applied to the ripple. The alpha range is [0-255]. - */ - fun setColor(color: Int, alpha: Int = RippleShader.RIPPLE_DEFAULT_ALPHA) { - rippleShader.color = ColorUtils.setAlphaComponent(color, alpha) - } - - /** Set the intensity of the sparkles. */ - fun setSparkleStrength(strength: Float) { - rippleShader.sparkleStrength = strength - } - - /** Indicates whether the ripple animation is playing. */ - fun rippleInProgress(): Boolean = animator.isRunning - - override fun onDraw(canvas: Canvas) { - if (!canvas.isHardwareAccelerated) { - // Drawing with the ripple shader requires hardware acceleration, so skip if it's - // unsupported. - return - } - // To reduce overdraw, we mask the effect to a circle or a rectangle that's bigger than the - // active effect area. Values here should be kept in sync with the animation implementation - // in the ripple shader. - if (rippleShape == RippleShape.CIRCLE) { - val maskRadius = rippleShader.rippleSize.currentWidth - canvas.drawCircle(centerX, centerY, maskRadius, ripplePaint) - } else if (rippleShape == RippleShape.ELLIPSE) { - val maskWidth = rippleShader.rippleSize.currentWidth * 2 - val maskHeight = rippleShader.rippleSize.currentHeight * 2 - canvas.drawRect( - /* left= */ centerX - maskWidth, - /* top= */ centerY - maskHeight, - /* right= */ centerX + maskWidth, - /* bottom= */ centerY + maskHeight, - ripplePaint - ) - } else { // RippleShape.RoundedBox - // No masking for the rounded box, as it has more blur which requires larger bounds. - // Masking creates sharp bounds even when the masking is 4 times bigger. - canvas.drawPaint(ripplePaint) - } - } -} diff --git a/systemUIAnim/src/com/android/systemui/surfaceeffects/shaders/SolidColorShader.kt b/systemUIAnim/src/com/android/systemui/surfaceeffects/shaders/SolidColorShader.kt deleted file mode 100644 index c94fad7246..0000000000 --- a/systemUIAnim/src/com/android/systemui/surfaceeffects/shaders/SolidColorShader.kt +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.systemui.surfaceeffects.shaders - -import android.graphics.RuntimeShader - -/** Simply renders a solid color. */ -class SolidColorShader(color: Int) : RuntimeShader(SHADER) { - // language=AGSL - private companion object { - private const val SHADER = - """ - layout(color) uniform vec4 in_color; - vec4 main(vec2 p) { - return in_color; - } - """ - } - - init { - setColorUniform("in_color", color) - } -} diff --git a/systemUIAnim/src/com/android/systemui/surfaceeffects/shaders/SparkleShader.kt b/systemUIAnim/src/com/android/systemui/surfaceeffects/shaders/SparkleShader.kt deleted file mode 100644 index df07856e32..0000000000 --- a/systemUIAnim/src/com/android/systemui/surfaceeffects/shaders/SparkleShader.kt +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.systemui.surfaceeffects.shaders - -import android.graphics.Color -import android.graphics.RuntimeShader -import android.graphics.Shader -import com.android.systemui.surfaceeffects.shaderutil.ShaderUtilLibrary - -/** - * Renders sparkles based on the luma matte. - * - * For example, you can pass in simplex noise as the luma matte and have a cloud looking sparkles. - * - * You may want to utilize this shader by: (Preferred) 1. Create a RuntimeShaderEffect and set the - * [RenderEffect] to the target [View]. - * 2. Create a custom [View], set the shader to the [Paint] and use [Canvas.drawPaint] in [onDraw]. - */ -class SparkleShader : RuntimeShader(SPARKLE_SHADER) { - // language=AGSL - companion object { - private const val UNIFORMS = - """ - // Used it for RenderEffect. For example: - // myView.setRenderEffect( - // RenderEffect.createRuntimeShaderEffect(SparkleShader(), "in_src") - // ) - uniform shader in_src; - uniform half in_time; - uniform half in_pixelate; - uniform shader in_lumaMatte; - layout(color) uniform vec4 in_color; - """ - private const val MAIN_SHADER = - """vec4 main(vec2 p) { - half3 src = in_src.eval(p).rgb; - half luma = getLuminosity(in_lumaMatte.eval(p).rgb); - half sparkle = sparkles(p - mod(p, in_pixelate), in_time); - half3 mask = maskLuminosity(in_color.rgb * sparkle, luma); - - return vec4(src * mask * in_color.a, in_color.a); - } - """ - private const val SPARKLE_SHADER = UNIFORMS + ShaderUtilLibrary.SHADER_LIB + MAIN_SHADER - - /** Highly recommended to use this value unless specified by design spec. */ - const val DEFAULT_SPARKLE_PIXELATE_AMOUNT = 0.8f - } - - init { - // Initializes the src and luma matte to be white. - setInputShader("in_src", SolidColorShader(Color.WHITE)) - setLumaMatteColor(Color.WHITE) - } - - /** - * Sets the time of the sparkle animation. - * - * This is used for animating sparkles. Note that this only makes the sparkles sparkle in place. - * In order to move the sparkles in x, y directions, move the luma matte input instead. - */ - fun setTime(time: Float) { - setFloatUniform("in_time", time) - } - - /** - * Sets pixelated amount of the sparkle. - * - * This value *must* be based on [resources.displayMetrics.density]. Otherwise, this will result - * in having different sparkle sizes on different screens. - * - * Expected to be used as follows: - *

-     *     {@code
-     *     val pixelDensity = context.resources.displayMetrics.density
-     *     // Sparkles will be 0.8 of the pixel size.
-     *     val sparkleShader = SparkleShader().apply { setPixelateAmount(pixelDensity * 0.8f) }
-     *     }
-     * 
- */ - fun setPixelateAmount(pixelateAmount: Float) { - setFloatUniform("in_pixelate", pixelateAmount) - } - - /** - * Sets the luma matte for the sparkles. The luminosity determines the sparkle's visibility. - * Useful for setting a complex mask (e.g. simplex noise, texture, etc.) - */ - fun setLumaMatte(lumaMatte: Shader) { - setInputShader("in_lumaMatte", lumaMatte) - } - - /** Sets the luma matte for the sparkles. Useful for setting a solid color. */ - fun setLumaMatteColor(color: Int) { - setInputShader("in_lumaMatte", SolidColorShader(color)) - } - - /** Sets the color of the sparkles. Expect to have the alpha value encoded. */ - fun setColor(color: Int) { - setColorUniform("in_color", color) - } -} diff --git a/systemUIAnim/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt b/systemUIAnim/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt deleted file mode 100644 index 4efab58347..0000000000 --- a/systemUIAnim/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt +++ /dev/null @@ -1,128 +0,0 @@ -/* - * 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.surfaceeffects.shaderutil - -/** Library class that contains 2D signed distance functions. */ -class SdfShaderLibrary { - // language=AGSL - companion object { - const val CIRCLE_SDF = - """ - float sdCircle(vec2 p, float r) { - return (length(p)-r) / r; - } - - float circleRing(vec2 p, float radius) { - float thicknessHalf = radius * 0.25; - - float outerCircle = sdCircle(p, radius + thicknessHalf); - float innerCircle = sdCircle(p, radius); - - return subtract(outerCircle, innerCircle); - } - """ - - const val BOX_SDF = - """ - float sdBox(vec2 p, vec2 size) { - size = size * 0.5; - vec2 d = abs(p) - size; - return length(max(d, 0.)) + min(max(d.x, d.y), 0.) / size.y; - } - """ - - const val ROUNDED_BOX_SDF = - """ - float sdRoundedBox(vec2 p, vec2 size, float cornerRadius) { - size *= 0.5; - cornerRadius *= 0.5; - vec2 d = abs(p) - size + cornerRadius; - - float outside = length(max(d, 0.0)); - float inside = min(max(d.x, d.y), 0.0); - - return (outside + inside - cornerRadius) / size.y; - } - - float roundedBoxRing(vec2 p, vec2 size, float cornerRadius, - float borderThickness) { - float outerRoundBox = sdRoundedBox(p, size + vec2(borderThickness), - cornerRadius + borderThickness); - float innerRoundBox = sdRoundedBox(p, size, cornerRadius); - return subtract(outerRoundBox, innerRoundBox); - } - """ - - // Used non-trigonometry parametrization and Halley's method (iterative) for root finding. - // This is more expensive than the regular circle SDF, recommend to use the circle SDF if - // possible. - const val ELLIPSE_SDF = - """float sdEllipse(vec2 p, vec2 wh) { - wh *= 0.5; - - // symmetry - (wh.x > wh.y) ? wh = wh.yx, p = abs(p.yx) : p = abs(p); - - vec2 u = wh*p, v = wh*wh; - - float U1 = u.y/2.0; - float U2 = v.y-v.x; - float U3 = u.x-U2; - float U4 = u.x+U2; - float U5 = 4.0*U1; - float U6 = 6.0*U1; - float U7 = 3.0*U3; - - float t = 0.5; - for (int i = 0; i < 3; i ++) { - float F1 = t*(t*t*(U1*t+U3)+U4)-U1; - float F2 = t*t*(U5*t+U7)+U4; - float F3 = t*(U6*t+U7); - - t += (F1*F2)/(F1*F3-F2*F2); - } - - t = clamp(t, 0.0, 1.0); - - float d = distance(p, wh*vec2(1.0-t*t,2.0*t)/(t*t+1.0)); - d /= wh.y; - - return (dot(p/wh,p/wh)>1.0) ? d : -d; - } - - float ellipseRing(vec2 p, vec2 wh) { - vec2 thicknessHalf = wh * 0.25; - - float outerEllipse = sdEllipse(p, wh + thicknessHalf); - float innerEllipse = sdEllipse(p, wh); - - return subtract(outerEllipse, innerEllipse); - } - """ - - const val SHADER_SDF_OPERATION_LIB = - """ - float soften(float d, float blur) { - float blurHalf = blur * 0.5; - return smoothstep(-blurHalf, blurHalf, d); - } - - float subtract(float outer, float inner) { - return max(outer, -inner); - } - """ - } -} diff --git a/systemUIAnim/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt b/systemUIAnim/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt deleted file mode 100644 index 867bbb7d74..0000000000 --- a/systemUIAnim/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt +++ /dev/null @@ -1,201 +0,0 @@ -/* - * 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.surfaceeffects.shaderutil - -/** Common utility functions that are used for computing shaders. */ -object ShaderUtilLibrary { - // language=AGSL - const val SHADER_LIB = - """ - float triangleNoise(vec2 n) { - n = fract(n * vec2(5.3987, 5.4421)); - n += dot(n.yx, n.xy + vec2(21.5351, 14.3137)); - float xy = n.x * n.y; - // compute in [0..2[ and remap to [-1.0..1.0[ - return fract(xy * 95.4307) + fract(xy * 75.04961) - 1.0; - } - - const float PI = 3.1415926535897932384626; - - float sparkles(vec2 uv, float t) { - float n = triangleNoise(uv); - float s = 0.0; - for (float i = 0; i < 4; i += 1) { - float l = i * 0.01; - float h = l + 0.1; - float o = smoothstep(n - l, h, n); - o *= abs(sin(PI * o * (t + 0.55 * i))); - s += o; - } - return s; - } - - vec2 distort(vec2 p, float time, float distort_amount_radial, - float distort_amount_xy) { - float angle = atan(p.y, p.x); - return p + vec2(sin(angle * 8 + time * 0.003 + 1.641), - cos(angle * 5 + 2.14 + time * 0.00412)) * distort_amount_radial - + vec2(sin(p.x * 0.01 + time * 0.00215 + 0.8123), - cos(p.y * 0.01 + time * 0.005931)) * distort_amount_xy; - } - - // Perceived luminosity (L′), not absolute luminosity. - half getLuminosity(vec3 c) { - return 0.3 * c.r + 0.59 * c.g + 0.11 * c.b; - } - - // Creates a luminosity mask and clamp to the legal range. - vec3 maskLuminosity(vec3 dest, float lum) { - dest.rgb *= vec3(lum); - // Clip back into the legal range - dest = clamp(dest, vec3(0.), vec3(1.0)); - return dest; - } - - // Integer mod. GLSL es 1.0 doesn't have integer mod :( - int imod(int a, int b) { - return a - (b * (a / b)); - } - - ivec3 imod(ivec3 a, int b) { - return ivec3(imod(a.x, b), imod(a.y, b), imod(a.z, b)); - } - - // Integer based hash function with the return range of [-1, 1]. - vec3 hash(vec3 p) { - ivec3 v = ivec3(p); - v = v * 1671731 + 10139267; - - v.x += v.y * v.z; - v.y += v.z * v.x; - v.z += v.x * v.y; - - ivec3 v2 = v / 65536; // v >> 16 - v = imod((10 - imod((v + v2), 10)), 10); // v ^ v2 - - v.x += v.y * v.z; - v.y += v.z * v.x; - v.z += v.x * v.y; - - // Use sin and cos to map the range to [-1, 1]. - return vec3(sin(float(v.x)), cos(float(v.y)), sin(float(v.z))); - } - - // Skew factors (non-uniform). - const half SKEW = 0.3333333; // 1/3 - const half UNSKEW = 0.1666667; // 1/6 - - // Return range roughly [-1,1]. - // It's because the hash function (that returns a random gradient vector) returns - // different magnitude of vectors. Noise doesn't have to be in the precise range thus - // skipped normalize. - half simplex3d(vec3 p) { - // Skew the input coordinate, so that we get squashed cubical grid - vec3 s = floor(p + (p.x + p.y + p.z) * SKEW); - - // Unskew back - vec3 u = s - (s.x + s.y + s.z) * UNSKEW; - - // Unskewed coordinate that is relative to p, to compute the noise contribution - // based on the distance. - vec3 c0 = p - u; - - // We have six simplices (in this case tetrahedron, since we are in 3D) that we - // could possibly in. - // Here, we are finding the correct tetrahedron (simplex shape), and traverse its - // four vertices (c0..3) when computing noise contribution. - // The way we find them is by comparing c0's x,y,z values. - // For example in 2D, we can find the triangle (simplex shape in 2D) that we are in - // by comparing x and y values. i.e. x>y lower, xy0>z0: (1,0,0), (1,1,0), (1,1,1) - // x0>z0>y0: (1,0,0), (1,0,1), (1,1,1) - // z0>x0>y0: (0,0,1), (1,0,1), (1,1,1) - // z0>y0>x0: (0,0,1), (0,1,1), (1,1,1) - // y0>z0>x0: (0,1,0), (0,1,1), (1,1,1) - // y0>x0>z0: (0,1,0), (1,1,0), (1,1,1) - // - // The rule is: - // * For offset1, set 1 at the max component, otherwise 0. - // * For offset2, set 0 at the min component, otherwise 1. - // * For offset3, set 1 for all. - // - // Encode x0-y0, y0-z0, z0-x0 in a vec3 - vec3 en = c0 - c0.yzx; - // Each represents whether x0>y0, y0>z0, z0>x0 - en = step(vec3(0.), en); - // en.zxy encodes z0>x0, x0>y0, y0>x0 - vec3 offset1 = en * (1. - en.zxy); // find max - vec3 offset2 = 1. - en.zxy * (1. - en); // 1-(find min) - vec3 offset3 = vec3(1.); - - vec3 c1 = c0 - offset1 + UNSKEW; - vec3 c2 = c0 - offset2 + UNSKEW * 2.; - vec3 c3 = c0 - offset3 + UNSKEW * 3.; - - // Kernel summation: dot(max(0, r^2-d^2))^4, noise contribution) - // - // First compute d^2, squared distance to the point. - vec4 w; // w = max(0, r^2 - d^2)) - w.x = dot(c0, c0); - w.y = dot(c1, c1); - w.z = dot(c2, c2); - w.w = dot(c3, c3); - - // Noise contribution should decay to zero before they cross the simplex boundary. - // Usually r^2 is 0.5 or 0.6; - // 0.5 ensures continuity but 0.6 increases the visual quality for the application - // where discontinuity isn't noticeable. - w = max(0.6 - w, 0.); - - // Noise contribution from each point. - vec4 nc; - nc.x = dot(hash(s), c0); - nc.y = dot(hash(s + offset1), c1); - nc.z = dot(hash(s + offset2), c2); - nc.w = dot(hash(s + offset3), c3); - - nc *= w*w*w*w; - - // Add all the noise contributions. - // Should multiply by the possible max contribution to adjust the range in [-1,1]. - return dot(vec4(32.), nc); - } - - // Random rotations. - // The way you create fractal noise is layering simplex noise with some rotation. - // To make random cloud looking noise, the rotations should not align. (Otherwise it - // creates patterned noise). - // Below rotations only rotate in one axis. - const mat3 rot1 = mat3(1.0, 0. ,0., 0., 0.15, -0.98, 0., 0.98, 0.15); - const mat3 rot2 = mat3(-0.95, 0. ,-0.3, 0., 1., 0., 0.3, 0., -0.95); - const mat3 rot3 = mat3(1.0, 0. ,0., 0., -0.44, -0.89, 0., 0.89, -0.44); - - // Octave = 4 - // Divide each coefficient by 3 to produce more grainy noise. - half simplex3d_fractal(vec3 p) { - return 0.675 * simplex3d(p * rot1) + 0.225 * simplex3d(2.0 * p * rot2) - + 0.075 * simplex3d(4.0 * p * rot3) + 0.025 * simplex3d(8.0 * p); - } - - // Screen blend - vec3 screen(vec3 dest, vec3 src) { - return dest + src - dest * src; - } - """ -} diff --git a/systemUIAnim/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt b/systemUIAnim/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt deleted file mode 100644 index ba8f1ace02..0000000000 --- a/systemUIAnim/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt +++ /dev/null @@ -1,89 +0,0 @@ -/* - * 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.surfaceeffects.turbulencenoise - -import android.graphics.Color -import java.util.Random - -/** Turbulence noise animation configuration. */ -data class TurbulenceNoiseAnimationConfig( - /** The number of grids that is used to generate noise. */ - val gridCount: Float = DEFAULT_NOISE_GRID_COUNT, - - /** Multiplier for the noise luma matte. Increase this for brighter effects. */ - val luminosityMultiplier: Float = DEFAULT_LUMINOSITY_MULTIPLIER, - - /** Initial noise offsets. */ - val noiseOffsetX: Float = random.nextFloat(), - val noiseOffsetY: Float = random.nextFloat(), - val noiseOffsetZ: Float = random.nextFloat(), - - /** - * Noise move speed variables. - * - * Its sign determines the direction; magnitude determines the speed.
    - * - * ``` - *
  • [noiseMoveSpeedX] positive: right to left; negative: left to right. - *
  • [noiseMoveSpeedY] positive: bottom to top; negative: top to bottom. - *
  • [noiseMoveSpeedZ] its sign doesn't matter much, as it moves in Z direction. Use it - * to add turbulence in place. - * ``` - * - *
- */ - val noiseMoveSpeedX: Float = 0f, - val noiseMoveSpeedY: Float = 0f, - val noiseMoveSpeedZ: Float = DEFAULT_NOISE_SPEED_Z, - - /** Color of the effect. */ - val color: Int = DEFAULT_COLOR, - /** Background color of the effect. */ - val screenColor: Int = DEFAULT_SCREEN_COLOR, - val width: Float = 0f, - val height: Float = 0f, - val maxDuration: Float = DEFAULT_MAX_DURATION_IN_MILLIS, - val easeInDuration: Float = DEFAULT_EASING_DURATION_IN_MILLIS, - val easeOutDuration: Float = DEFAULT_EASING_DURATION_IN_MILLIS, - val pixelDensity: Float = 1f, - /** - * Variants in noise. Higher number means more contrast; lower number means less contrast but - * make the noise dimmed. You may want to increase the [lumaMatteBlendFactor] to compensate. - * Expected range [0, 1]. - */ - val lumaMatteBlendFactor: Float = DEFAULT_LUMA_MATTE_BLEND_FACTOR, - /** - * Offset for the overall brightness in noise. Higher number makes the noise brighter. You may - * want to use this if you have made the noise softer using [lumaMatteBlendFactor]. Expected - * range [0, 1]. - */ - val lumaMatteOverallBrightness: Float = DEFAULT_LUMA_MATTE_OVERALL_BRIGHTNESS, - /** Whether to flip the luma mask. */ - val shouldInverseNoiseLuminosity: Boolean = false, -) { - companion object { - const val DEFAULT_MAX_DURATION_IN_MILLIS = 30_000f // Max 30 sec - const val DEFAULT_EASING_DURATION_IN_MILLIS = 750f - const val DEFAULT_LUMINOSITY_MULTIPLIER = 1f - const val DEFAULT_NOISE_GRID_COUNT = 1.2f - const val DEFAULT_NOISE_SPEED_Z = 0.3f - const val DEFAULT_COLOR = Color.WHITE - const val DEFAULT_LUMA_MATTE_BLEND_FACTOR = 1f - const val DEFAULT_LUMA_MATTE_OVERALL_BRIGHTNESS = 0f - const val DEFAULT_SCREEN_COLOR = Color.BLACK - private val random = Random() - } -} diff --git a/systemUIAnim/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseController.kt b/systemUIAnim/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseController.kt deleted file mode 100644 index e862f0c43a..0000000000 --- a/systemUIAnim/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseController.kt +++ /dev/null @@ -1,115 +0,0 @@ -/* - * 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.surfaceeffects.turbulencenoise - -import android.view.View -import androidx.annotation.VisibleForTesting - -/** Plays [TurbulenceNoiseView] in ease-in, main (no easing), and ease-out order. */ -class TurbulenceNoiseController(private val turbulenceNoiseView: TurbulenceNoiseView) { - - companion object { - /** - * States of the turbulence noise animation. - * - *

The state is designed to be follow the order below: [AnimationState.EASE_IN], - * [AnimationState.MAIN], [AnimationState.EASE_OUT]. - */ - enum class AnimationState { - EASE_IN, - MAIN, - EASE_OUT, - NOT_PLAYING - } - } - - /** Current state of the animation. */ - @VisibleForTesting - var state: AnimationState = AnimationState.NOT_PLAYING - set(value) { - field = value - if (state == AnimationState.NOT_PLAYING) { - turbulenceNoiseView.visibility = View.INVISIBLE - turbulenceNoiseView.clearConfig() - } else { - turbulenceNoiseView.visibility = View.VISIBLE - } - } - - init { - turbulenceNoiseView.visibility = View.INVISIBLE - } - - /** Updates the color of the noise. */ - fun updateNoiseColor(color: Int) { - if (state == AnimationState.NOT_PLAYING) { - return - } - turbulenceNoiseView.updateColor(color) - } - - /** - * Plays [TurbulenceNoiseView] with the given config. - * - *

It plays ease-in, main, and ease-out animations in sequence. - */ - fun play( - baseType: TurbulenceNoiseShader.Companion.Type, - config: TurbulenceNoiseAnimationConfig - ) { - if (state != AnimationState.NOT_PLAYING) { - return // Ignore if any of the animation is playing. - } - - turbulenceNoiseView.initShader(baseType, config) - playEaseInAnimation() - } - - // TODO(b/237282226): Support force finish. - /** Finishes the main animation, which triggers the ease-out animation. */ - fun finish() { - if (state == AnimationState.MAIN) { - turbulenceNoiseView.finish(nextAnimation = this::playEaseOutAnimation) - } - } - - private fun playEaseInAnimation() { - if (state != AnimationState.NOT_PLAYING) { - return - } - state = AnimationState.EASE_IN - - turbulenceNoiseView.playEaseIn(this::playMainAnimation) - } - - private fun playMainAnimation() { - if (state != AnimationState.EASE_IN) { - return - } - state = AnimationState.MAIN - - turbulenceNoiseView.play(this::playEaseOutAnimation) - } - - private fun playEaseOutAnimation() { - if (state != AnimationState.MAIN) { - return - } - state = AnimationState.EASE_OUT - - turbulenceNoiseView.playEaseOut(onAnimationEnd = { state = AnimationState.NOT_PLAYING }) - } -} diff --git a/systemUIAnim/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt b/systemUIAnim/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt deleted file mode 100644 index 025c8b9dce..0000000000 --- a/systemUIAnim/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt +++ /dev/null @@ -1,294 +0,0 @@ -/* - * 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.surfaceeffects.turbulencenoise - -import android.graphics.RuntimeShader -import com.android.systemui.surfaceeffects.shaders.SolidColorShader -import com.android.systemui.surfaceeffects.shaderutil.ShaderUtilLibrary -import java.lang.Float.max - -/** - * Shader that renders turbulence simplex noise, by default no octave. - * - * @param baseType the base [Type] of the shader. - */ -class TurbulenceNoiseShader(val baseType: Type = Type.SIMPLEX_NOISE) : - RuntimeShader(getShader(baseType)) { - // language=AGSL - companion object { - /** Uniform name for the background buffer (e.g. image, solid color, etc.). */ - const val BACKGROUND_UNIFORM = "in_src" - private const val UNIFORMS = - """ - uniform shader ${BACKGROUND_UNIFORM}; - uniform float in_gridNum; - uniform vec3 in_noiseMove; - uniform vec2 in_size; - uniform float in_aspectRatio; - uniform float in_opacity; - uniform float in_pixelDensity; - uniform float in_inverseLuma; - uniform half in_lumaMatteBlendFactor; - uniform half in_lumaMatteOverallBrightness; - layout(color) uniform vec4 in_color; - layout(color) uniform vec4 in_screenColor; - """ - - private const val SIMPLEX_SHADER = - """ - vec4 main(vec2 p) { - vec2 uv = p / in_size.xy; - uv.x *= in_aspectRatio; - - // Compute turbulence effect with the uv distorted with simplex noise. - vec3 noiseP = vec3(uv + in_noiseMove.xy, in_noiseMove.z) * in_gridNum; - vec3 color = getColorTurbulenceMask(simplex3d(noiseP) * in_inverseLuma); - - // Blend the result with the background color. - color = in_src.eval(p).rgb + color * 0.6; - - // Add dither with triangle distribution to avoid color banding. Dither in the - // shader here as we are in gamma space. - float dither = triangleNoise(p * in_pixelDensity) / 255.; - color += dither.rrr; - - // Return the pre-multiplied alpha result, i.e. [R*A, G*A, B*A, A]. - return vec4(color * in_opacity, in_opacity); - } - """ - - private const val FRACTAL_SHADER = - """ - vec4 main(vec2 p) { - vec2 uv = p / in_size.xy; - uv.x *= in_aspectRatio; - - vec3 noiseP = vec3(uv + in_noiseMove.xy, in_noiseMove.z) * in_gridNum; - vec3 color = getColorTurbulenceMask(simplex3d_fractal(noiseP) * in_inverseLuma); - - // Blend the result with the background color. - color = in_src.eval(p).rgb + color * 0.6; - - // Skip dithering. - return vec4(color * in_opacity, in_opacity); - } - """ - - /** - * This effect has two layers: color turbulence effect with sparkles on top. - * 1. Gets the luma matte using Simplex noise. - * 2. Generate a colored turbulence layer with the luma matte. - * 3. Generate a colored sparkle layer with the same luma matter. - * 4. Apply a screen color to the background image. - * 5. Composite the previous result with the color turbulence. - * 6. Composite the latest result with the sparkles. - */ - private const val SIMPLEX_SPARKLE_SHADER = - """ - vec4 main(vec2 p) { - vec2 uv = p / in_size.xy; - uv.x *= in_aspectRatio; - - vec3 noiseP = vec3(uv + in_noiseMove.xy, in_noiseMove.z) * in_gridNum; - // Luma is used for both color and sparkle masks. - float luma = simplex3d(noiseP) * in_inverseLuma; - - // Get color layer (color mask with in_color applied) - vec3 colorLayer = getColorTurbulenceMask(simplex3d(noiseP) * in_inverseLuma); - float dither = triangleNoise(p * in_pixelDensity) / 255.; - colorLayer += dither.rrr; - - // Get sparkle layer (sparkle mask with particles & in_color applied) - vec3 sparkleLayer = getSparkleTurbulenceMask(luma, p); - - // Composite with the background. - half4 bgColor = in_src.eval(p); - half sparkleOpacity = smoothstep(0, 0.75, in_opacity); - - half3 effect = screen(bgColor.rgb, in_screenColor.rgb); - effect = screen(effect, colorLayer * 0.22); - effect += sparkleLayer * sparkleOpacity; - - return mix(bgColor, vec4(effect, 1.), in_opacity); - } - """ - - private const val COMMON_FUNCTIONS = - /** - * Below two functions generate turbulence layers (color or sparkles applied) with the - * given luma matte. They both return a mask with in_color applied. - */ - """ - vec3 getColorTurbulenceMask(float luma) { - // Bring it to [0, 1] range. - luma = luma * 0.5 + 0.5; - - half colorLuma = - saturate(luma * in_lumaMatteBlendFactor + in_lumaMatteOverallBrightness) - * in_opacity; - vec3 colorLayer = maskLuminosity(in_color.rgb, colorLuma); - - return colorLayer; - } - - vec3 getSparkleTurbulenceMask(float luma, vec2 p) { - half lumaIntensity = 1.75; - half lumaBrightness = -1.3; - half sparkleLuma = max(luma * lumaIntensity + lumaBrightness, 0.); - - float sparkle = sparkles(p - mod(p, in_pixelDensity * 0.8), in_noiseMove.z); - vec3 sparkleLayer = maskLuminosity(in_color.rgb * sparkle, sparkleLuma); - - return sparkleLayer; - } - """ - private const val SIMPLEX_NOISE_SHADER = - ShaderUtilLibrary.SHADER_LIB + UNIFORMS + COMMON_FUNCTIONS + SIMPLEX_SHADER - private const val FRACTAL_NOISE_SHADER = - ShaderUtilLibrary.SHADER_LIB + UNIFORMS + COMMON_FUNCTIONS + FRACTAL_SHADER - private const val SPARKLE_NOISE_SHADER = - ShaderUtilLibrary.SHADER_LIB + UNIFORMS + COMMON_FUNCTIONS + SIMPLEX_SPARKLE_SHADER - - enum class Type { - /** Effect with a simple color noise turbulence. */ - SIMPLEX_NOISE, - /** Effect with a simple color noise turbulence, with fractal. */ - SIMPLEX_NOISE_FRACTAL, - /** Effect with color & sparkle turbulence with screen color layer. */ - SIMPLEX_NOISE_SPARKLE - } - - fun getShader(type: Type): String { - return when (type) { - Type.SIMPLEX_NOISE -> SIMPLEX_NOISE_SHADER - Type.SIMPLEX_NOISE_FRACTAL -> FRACTAL_NOISE_SHADER - Type.SIMPLEX_NOISE_SPARKLE -> SPARKLE_NOISE_SHADER - } - } - } - - /** Convenient way for updating multiple uniform values via config object. */ - fun applyConfig(config: TurbulenceNoiseAnimationConfig) { - setGridCount(config.gridCount) - setPixelDensity(config.pixelDensity) - setColor(config.color) - setScreenColor(config.screenColor) - setSize(config.width, config.height) - setLumaMatteFactors(config.lumaMatteBlendFactor, config.lumaMatteOverallBrightness) - setInverseNoiseLuminosity(config.shouldInverseNoiseLuminosity) - setNoiseMove(config.noiseOffsetX, config.noiseOffsetY, config.noiseOffsetZ) - } - - /** Sets the number of grid for generating noise. */ - fun setGridCount(gridNumber: Float = 1.0f) { - setFloatUniform("in_gridNum", gridNumber) - } - - /** - * Sets the pixel density of the screen. - * - * Used it for noise dithering. - */ - fun setPixelDensity(pixelDensity: Float) { - setFloatUniform("in_pixelDensity", pixelDensity) - } - - /** Sets the noise color of the effect. Alpha is ignored. */ - fun setColor(color: Int) { - setColorUniform("in_color", color) - } - - /** - * Sets the color that is used for blending on top of the background color/image. Only relevant - * to [Type.SIMPLEX_NOISE_SPARKLE]. - */ - fun setScreenColor(color: Int) { - setColorUniform("in_screenColor", color) - } - - /** - * Sets the background color of the effect. Alpha is ignored. If you are using [RenderEffect], - * no need to call this function since the background image of the View will be used. - */ - fun setBackgroundColor(color: Int) { - setInputShader(BACKGROUND_UNIFORM, SolidColorShader(color)) - } - - /** - * Sets the opacity of the effect. Not intended to set by the client as it is used for - * ease-in/out animations. - * - * Expected value range is [1, 0]. - */ - fun setOpacity(opacity: Float) { - setFloatUniform("in_opacity", opacity) - } - - /** Sets the size of the shader. */ - fun setSize(width: Float, height: Float) { - setFloatUniform("in_size", width, height) - setFloatUniform("in_aspectRatio", width / max(height, 0.001f)) - } - - /** - * Sets blend and brightness factors of the luma matte. - * - * @param lumaMatteBlendFactor increases or decreases the amount of variance in noise. Setting - * this a lower number removes variations. I.e. the turbulence noise will look more blended. - * Expected input range is [0, 1]. - * @param lumaMatteOverallBrightness adds the overall brightness of the turbulence noise. - * Expected input range is [0, 1]. - * - * Example usage: You may want to apply a small number to [lumaMatteBlendFactor], such as 0.2, - * which makes the noise look softer. However it makes the overall noise look dim, so you want - * offset something like 0.3 for [lumaMatteOverallBrightness] to bring back its overall - * brightness. - */ - fun setLumaMatteFactors( - lumaMatteBlendFactor: Float = 1f, - lumaMatteOverallBrightness: Float = 0f - ) { - setFloatUniform("in_lumaMatteBlendFactor", lumaMatteBlendFactor) - setFloatUniform("in_lumaMatteOverallBrightness", lumaMatteOverallBrightness) - } - - /** - * Sets whether to inverse the luminosity of the noise. - * - * By default noise will be used as a luma matte as is. This means that you will see color in - * the brighter area. If you want to invert it, meaning blend color onto the darker side, set to - * true. - */ - fun setInverseNoiseLuminosity(inverse: Boolean) { - setFloatUniform("in_inverseLuma", if (inverse) -1f else 1f) - } - - /** Current noise movements in x, y, and z axes. */ - var noiseOffsetX: Float = 0f - private set - var noiseOffsetY: Float = 0f - private set - var noiseOffsetZ: Float = 0f - private set - - /** Sets noise move offset in x, y, and z direction. */ - fun setNoiseMove(x: Float, y: Float, z: Float) { - noiseOffsetX = x - noiseOffsetY = y - noiseOffsetZ = z - setFloatUniform("in_noiseMove", noiseOffsetX, noiseOffsetY, noiseOffsetZ) - } -} diff --git a/systemUIAnim/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt b/systemUIAnim/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt deleted file mode 100644 index 5e72e3bd1e..0000000000 --- a/systemUIAnim/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt +++ /dev/null @@ -1,236 +0,0 @@ -/* - * 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.surfaceeffects.turbulencenoise - -import android.animation.Animator -import android.animation.AnimatorListenerAdapter -import android.animation.ValueAnimator -import android.content.Context -import android.graphics.BlendMode -import android.graphics.Canvas -import android.graphics.Paint -import android.util.AttributeSet -import android.view.View -import androidx.annotation.VisibleForTesting - -/** - * View that renders turbulence noise effect. - * - *

Use [TurbulenceNoiseController] to control the turbulence animation. If you want to make some - * other turbulence noise effects, either add functionality to [TurbulenceNoiseController] or create - * another controller instead of extend or modify the [TurbulenceNoiseView]. - * - *

Please keep the [TurbulenceNoiseView] (or View in general) not aware of the state. - * - *

Please avoid inheriting the View if possible. Instead, reconsider adding a controller for a - * new case. - */ -class TurbulenceNoiseView(context: Context?, attrs: AttributeSet?) : View(context, attrs) { - - companion object { - private const val MS_TO_SEC = 0.001f - } - - private val paint = Paint() - @VisibleForTesting var turbulenceNoiseShader: TurbulenceNoiseShader? = null - @VisibleForTesting var noiseConfig: TurbulenceNoiseAnimationConfig? = null - @VisibleForTesting var currentAnimator: ValueAnimator? = null - - override fun onDraw(canvas: Canvas) { - if (!canvas.isHardwareAccelerated) { - // Drawing with the turbulence noise shader requires hardware acceleration, so skip - // if it's unsupported. - return - } - - canvas.drawPaint(paint) - } - - /** Updates the color during the animation. No-op if there's no animation playing. */ - internal fun updateColor(color: Int) { - turbulenceNoiseShader?.setColor(color) - } - - /** Plays the turbulence noise with no easing. */ - @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) - fun play(onAnimationEnd: Runnable? = null) { - if (noiseConfig == null) { - return - } - val config = noiseConfig!! - val shader = turbulenceNoiseShader!! - - val animator = ValueAnimator.ofFloat(0f, 1f) - animator.duration = config.maxDuration.toLong() - - // Animation should start from the initial position to avoid abrupt transition. - val initialX = shader.noiseOffsetX - val initialY = shader.noiseOffsetY - val initialZ = shader.noiseOffsetZ - - animator.addUpdateListener { updateListener -> - val timeInSec = updateListener.currentPlayTime * MS_TO_SEC - shader.setNoiseMove( - initialX + timeInSec * config.noiseMoveSpeedX, - initialY + timeInSec * config.noiseMoveSpeedY, - initialZ + timeInSec * config.noiseMoveSpeedZ - ) - - shader.setOpacity(config.luminosityMultiplier) - - invalidate() - } - - animator.addListener( - object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - currentAnimator = null - onAnimationEnd?.run() - } - } - ) - - animator.start() - currentAnimator = animator - } - - /** Plays the turbulence noise with linear ease-in. */ - @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) - fun playEaseIn(onAnimationEnd: Runnable? = null) { - if (noiseConfig == null) { - return - } - val config = noiseConfig!! - val shader = turbulenceNoiseShader!! - - val animator = ValueAnimator.ofFloat(0f, 1f) - animator.duration = config.easeInDuration.toLong() - - // Animation should start from the initial position to avoid abrupt transition. - val initialX = shader.noiseOffsetX - val initialY = shader.noiseOffsetY - val initialZ = shader.noiseOffsetZ - - animator.addUpdateListener { updateListener -> - val timeInSec = updateListener.currentPlayTime * MS_TO_SEC - val progress = updateListener.animatedValue as Float - - shader.setNoiseMove( - initialX + timeInSec * config.noiseMoveSpeedX, - initialY + timeInSec * config.noiseMoveSpeedY, - initialZ + timeInSec * config.noiseMoveSpeedZ - ) - - // TODO: Replace it with a better curve. - shader.setOpacity(progress * config.luminosityMultiplier) - - invalidate() - } - - animator.addListener( - object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - currentAnimator = null - onAnimationEnd?.run() - } - } - ) - - animator.start() - currentAnimator = animator - } - - /** Plays the turbulence noise with linear ease-out. */ - @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) - fun playEaseOut(onAnimationEnd: Runnable? = null) { - if (noiseConfig == null) { - return - } - val config = noiseConfig!! - val shader = turbulenceNoiseShader!! - - val animator = ValueAnimator.ofFloat(0f, 1f) - animator.duration = config.easeOutDuration.toLong() - - // Animation should start from the initial position to avoid abrupt transition. - val initialX = shader.noiseOffsetX - val initialY = shader.noiseOffsetY - val initialZ = shader.noiseOffsetZ - - animator.addUpdateListener { updateListener -> - val timeInSec = updateListener.currentPlayTime * MS_TO_SEC - val progress = updateListener.animatedValue as Float - - shader.setNoiseMove( - initialX + timeInSec * config.noiseMoveSpeedX, - initialY + timeInSec * config.noiseMoveSpeedY, - initialZ + timeInSec * config.noiseMoveSpeedZ - ) - - // TODO: Replace it with a better curve. - shader.setOpacity((1f - progress) * config.luminosityMultiplier) - - invalidate() - } - - animator.addListener( - object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - currentAnimator = null - onAnimationEnd?.run() - } - } - ) - - animator.start() - currentAnimator = animator - } - - /** Finishes the current animation if playing and plays the next animation if given. */ - @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) - fun finish(nextAnimation: Runnable? = null) { - // Calling Animator#end sets the animation state back to the initial state. Using pause to - // avoid visual artifacts. - currentAnimator?.pause() - currentAnimator = null - - nextAnimation?.run() - } - - /** Applies shader uniforms. Must be called before playing animation. */ - @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) - fun initShader( - baseType: TurbulenceNoiseShader.Companion.Type, - config: TurbulenceNoiseAnimationConfig - ) { - noiseConfig = config - if (turbulenceNoiseShader == null || turbulenceNoiseShader?.baseType != baseType) { - turbulenceNoiseShader = TurbulenceNoiseShader(baseType) - - paint.shader = turbulenceNoiseShader!! - } - turbulenceNoiseShader!!.applyConfig(config) - } - - /** Sets the blend mode of the View. */ - fun setBlendMode(blendMode: BlendMode) { - paint.blendMode = blendMode - } - - internal fun clearConfig() { - noiseConfig = null - } -} diff --git a/systemUIAnim/src/com/android/systemui/surfaceeffects/utils/MathUtils.kt b/systemUIAnim/src/com/android/systemui/surfaceeffects/utils/MathUtils.kt deleted file mode 100644 index 7ed3b87f68..0000000000 --- a/systemUIAnim/src/com/android/systemui/surfaceeffects/utils/MathUtils.kt +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (C) 2024 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.surfaceeffects.utils - -/** Copied from android.utils.MathUtils */ -object MathUtils { - fun lerp(start: Float, stop: Float, amount: Float): Float { - return start + (stop - start) * amount - } -} diff --git a/systemUIAnim/src/com/android/systemui/util/AnimatorExtensions.kt b/systemUIAnim/src/com/android/systemui/util/AnimatorExtensions.kt deleted file mode 100644 index 35dbb89ad8..0000000000 --- a/systemUIAnim/src/com/android/systemui/util/AnimatorExtensions.kt +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.util - -import androidx.core.animation.Animator - -/** - * Add an action which will be invoked when the animation has ended. - * - * @return the [Animator.AnimatorListener] added to the Animator - * @see Animator.end - */ -inline fun Animator.doOnEnd( - crossinline action: (animator: Animator) -> Unit -): Animator.AnimatorListener = addListener(onEnd = action) - -/** - * Add an action which will be invoked when the animation has started. - * - * @return the [Animator.AnimatorListener] added to the Animator - * @see Animator.start - */ -inline fun Animator.doOnStart( - crossinline action: (animator: Animator) -> Unit -): Animator.AnimatorListener = addListener(onStart = action) - -/** - * Add an action which will be invoked when the animation has been cancelled. - * - * @return the [Animator.AnimatorListener] added to the Animator - * @see Animator.cancel - */ -inline fun Animator.doOnCancel( - crossinline action: (animator: Animator) -> Unit -): Animator.AnimatorListener = addListener(onCancel = action) - -/** - * Add an action which will be invoked when the animation has repeated. - * - * @return the [Animator.AnimatorListener] added to the Animator - */ -inline fun Animator.doOnRepeat( - crossinline action: (animator: Animator) -> Unit -): Animator.AnimatorListener = addListener(onRepeat = action) - -/** - * Add a listener to this Animator using the provided actions. - * - * @return the [Animator.AnimatorListener] added to the Animator - */ -inline fun Animator.addListener( - crossinline onEnd: (animator: Animator) -> Unit = {}, - crossinline onStart: (animator: Animator) -> Unit = {}, - crossinline onCancel: (animator: Animator) -> Unit = {}, - crossinline onRepeat: (animator: Animator) -> Unit = {} -): Animator.AnimatorListener { - val listener = - object : Animator.AnimatorListener { - override fun onAnimationRepeat(animator: Animator) = onRepeat(animator) - override fun onAnimationEnd(animator: Animator) = onEnd(animator) - override fun onAnimationCancel(animator: Animator) = onCancel(animator) - override fun onAnimationStart(animator: Animator) = onStart(animator) - } - addListener(listener) - return listener -} diff --git a/systemUIAnim/src/com/android/systemui/util/Dialog.kt b/systemUIAnim/src/com/android/systemui/util/Dialog.kt deleted file mode 100644 index 9dd23289d8..0000000000 --- a/systemUIAnim/src/com/android/systemui/util/Dialog.kt +++ /dev/null @@ -1,162 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.util - -import android.app.Dialog -import android.view.View -import android.view.ViewGroup -import android.view.ViewGroup.LayoutParams.MATCH_PARENT -import android.widget.FrameLayout -import android.window.OnBackInvokedDispatcher -import com.android.systemui.animation.back.BackAnimationSpec -import com.android.systemui.animation.back.BackTransformation -import com.android.systemui.animation.back.applyTo -import com.android.systemui.animation.back.floatingSystemSurfacesForSysUi -import com.android.systemui.animation.back.onBackAnimationCallbackFrom -import com.android.systemui.animation.back.registerOnBackInvokedCallbackOnViewAttached -import com.android.systemui.animation.view.LaunchableFrameLayout - -/** - * Register on the Dialog's [OnBackInvokedDispatcher] an animation using the [BackAnimationSpec]. - * The [BackTransformation] will be applied on the [targetView]. - */ -@JvmOverloads -fun Dialog.registerAnimationOnBackInvoked( - targetView: View, - backAnimationSpec: BackAnimationSpec = - BackAnimationSpec.floatingSystemSurfacesForSysUi( - displayMetricsProvider = { targetView.resources.displayMetrics }, - ), -) { - targetView.registerOnBackInvokedCallbackOnViewAttached( - onBackInvokedDispatcher = onBackInvokedDispatcher, - onBackAnimationCallback = - onBackAnimationCallbackFrom( - backAnimationSpec = backAnimationSpec, - displayMetrics = targetView.resources.displayMetrics, - onBackProgressed = { backTransformation -> backTransformation.applyTo(targetView) }, - onBackInvoked = { dismiss() }, - ), - ) -} - -/** - * Make the dialog window (and therefore its DecorView) fullscreen to make it possible to animate - * outside its bounds. No-op if the dialog is already fullscreen. - * - *

Returns null if the dialog is already fullscreen. Otherwise, returns a pair containing a view - * and a layout listener. The new view matches the original dialog DecorView in size, position, and - * background. This new view will be a child of the modified, transparent, fullscreen DecorView. The - * layout listener is listening to changes to the modified DecorView. It is the responsibility of - * the caller to deregister the listener when the dialog is dismissed. - */ -fun Dialog.maybeForceFullscreen(): Pair? { - // Create the dialog so that its onCreate() method is called, which usually sets the dialog - // content. - create() - - val window = window!! - val decorView = window.decorView as ViewGroup - - val isWindowFullscreen = - window.attributes.width == MATCH_PARENT && window.attributes.height == MATCH_PARENT - if (isWindowFullscreen) { - return null - } - - // We will make the dialog window (and therefore its DecorView) fullscreen to make it possible - // to animate outside its bounds. - // - // Before that, we add a new View as a child of the DecorView with the same size and gravity as - // that DecorView, then we add all original children of the DecorView to that new View. Finally - // we remove the background of the DecorView and add it to the new View, then we make the - // DecorView fullscreen. This new View now acts as a fake (non fullscreen) window. - // - // On top of that, we also add a fullscreen transparent background between the DecorView and the - // view that we added so that we can dismiss the dialog when this view is clicked. This is - // necessary because DecorView overrides onTouchEvent and therefore we can't set the click - // listener directly on the (now fullscreen) DecorView. - val fullscreenTransparentBackground = FrameLayout(context) - decorView.addView( - fullscreenTransparentBackground, - 0 /* index */, - FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT) - ) - - val dialogContentWithBackground = LaunchableFrameLayout(context) - dialogContentWithBackground.background = decorView.background - - // Make the window background transparent. Note that setting the window (or DecorView) - // background drawable to null leads to issues with background color (not being transparent) or - // with insets that are not refreshed. Therefore we need to set it to something not null, hence - // we are using android.R.color.transparent here. - window.setBackgroundDrawableResource(android.R.color.transparent) - - // Close the dialog when clicking outside of it. - fullscreenTransparentBackground.setOnClickListener { dismiss() } - dialogContentWithBackground.isClickable = true - - // Make sure the transparent and dialog backgrounds are not focusable by accessibility - // features. - fullscreenTransparentBackground.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO - dialogContentWithBackground.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO - - fullscreenTransparentBackground.addView( - dialogContentWithBackground, - FrameLayout.LayoutParams( - window.attributes.width, - window.attributes.height, - window.attributes.gravity - ) - ) - - // Move all original children of the DecorView to the new View we just added. - for (i in 1 until decorView.childCount) { - val view = decorView.getChildAt(1) - decorView.removeViewAt(1) - dialogContentWithBackground.addView(view) - } - - // Make the window fullscreen and add a layout listener to ensure it stays fullscreen. - window.setLayout(MATCH_PARENT, MATCH_PARENT) - val decorViewLayoutListener = - View.OnLayoutChangeListener { - v, - left, - top, - right, - bottom, - oldLeft, - oldTop, - oldRight, - oldBottom -> - if ( - window.attributes.width != MATCH_PARENT || window.attributes.height != MATCH_PARENT - ) { - // The dialog size changed, copy its size to dialogContentWithBackground and make - // the dialog window full screen again. - val layoutParams = dialogContentWithBackground.layoutParams - layoutParams.width = window.attributes.width - layoutParams.height = window.attributes.height - dialogContentWithBackground.layoutParams = layoutParams - window.setLayout(MATCH_PARENT, MATCH_PARENT) - } - } - decorView.addOnLayoutChangeListener(decorViewLayoutListener) - - return dialogContentWithBackground to decorViewLayoutListener -} diff --git a/systemUIAnim/src/com/android/systemui/util/Dimension.kt b/systemUIAnim/src/com/android/systemui/util/Dimension.kt deleted file mode 100644 index 4bc9972dd5..0000000000 --- a/systemUIAnim/src/com/android/systemui/util/Dimension.kt +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.util - -import android.content.Context -import android.content.res.Resources -import android.util.DisplayMetrics -import android.util.TypedValue - -/** Convert [this] number of dps to device pixels. */ -fun Number.dpToPx(context: Context): Float = dpToPx(resources = context.resources) - -/** Convert [this] number of dps to device pixels. */ -fun Number.dpToPx(resources: Resources): Float = dpToPx(displayMetrics = resources.displayMetrics) - -/** Convert [this] number of dps to device pixels. */ -fun Number.dpToPx(displayMetrics: DisplayMetrics): Float = - TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, toFloat(), displayMetrics) diff --git a/systemUICommon/.gitignore b/systemUICommon/.gitignore deleted file mode 100644 index f9a33dbbcc..0000000000 --- a/systemUICommon/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -.idea/ -.gradle/ -gradle/ -build/ -gradlew* -local.properties -*.iml -android.properties -buildSrc \ No newline at end of file diff --git a/systemUICommon/Android.bp b/systemUICommon/Android.bp deleted file mode 100644 index 6fc13d7e0d..0000000000 --- a/systemUICommon/Android.bp +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (C) 2023 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package { - default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_", - // 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: "SystemUICommon", - use_resource_processor: true, - - srcs: [ - "src/**/*.java", - "src/**/*.kt", - ], - - static_libs: [ - "androidx.core_core-ktx", - ], - - manifest: "AndroidManifest.xml", - kotlincflags: ["-Xjvm-default=all"], -} diff --git a/systemUICommon/AndroidManifest.xml b/systemUICommon/AndroidManifest.xml deleted file mode 100644 index e07df26b5a..0000000000 --- a/systemUICommon/AndroidManifest.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/systemUICommon/OWNERS b/systemUICommon/OWNERS deleted file mode 100644 index 9b8a79e6f3..0000000000 --- a/systemUICommon/OWNERS +++ /dev/null @@ -1,2 +0,0 @@ -darrellshi@google.com -evanlaird@google.com diff --git a/systemUICommon/README.md b/systemUICommon/README.md deleted file mode 100644 index 1cc5277aa8..0000000000 --- a/systemUICommon/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# SystemUICommon - -`SystemUICommon` is a module within SystemUI that hosts standalone helper libraries. It is intended to be used by other modules, and therefore should not have other SystemUI dependencies to avoid circular dependencies. - -To maintain the structure of this module, please refrain from adding components at the top level. Instead, add them to specific sub-packages, such as `systemui/common/buffer/`. This will help to keep the module organized and easy to navigate. diff --git a/systemUICommon/build.gradle b/systemUICommon/build.gradle deleted file mode 100644 index 38842570a7..0000000000 --- a/systemUICommon/build.gradle +++ /dev/null @@ -1,17 +0,0 @@ -plugins { - id 'com.android.library' - id 'org.jetbrains.kotlin.android' -} - -android { - namespace "com.android.systemui.common" - - sourceSets { - main { - java.srcDirs = ['src'] - manifest.srcFile 'AndroidManifest.xml' - } - } -} - -addFrameworkJar('framework-15.jar') diff --git a/systemUICommon/src/com/android/systemui/common/buffer/RingBuffer.kt b/systemUICommon/src/com/android/systemui/common/buffer/RingBuffer.kt deleted file mode 100644 index 4734a3887f..0000000000 --- a/systemUICommon/src/com/android/systemui/common/buffer/RingBuffer.kt +++ /dev/null @@ -1,118 +0,0 @@ -/* - * 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.common.buffer - -import kotlin.math.max - -/** - * A simple ring buffer of recycled items - * - * Use [advance] to add items to the buffer. - * - * As the buffer is used, it will grow, allocating new instances of T using [factory] until it - * reaches [maxSize]. After this point, no new instances will be created. Instead, calls to - * [advance] will recycle the "oldest" instance from the start of the buffer, placing it at the end. - * - * The items in the buffer are "recycled" in that they are reused, but it is up to the caller of - * [advance] to properly reset any data that was previously stored on those items. - * - * @param maxSize The maximum size the buffer can grow to before it begins functioning as a ring. - * @param factory A function that creates a fresh instance of T. Used by the buffer while it's - * growing to [maxSize]. - */ -class RingBuffer(private val maxSize: Int, private val factory: () -> T) : Iterable { - - private val buffer = MutableList(maxSize) { null } - - /** - * An abstract representation that points to the "end" of the buffer, i.e. one beyond the - * location of the last item. Increments every time [advance] is called and is never wrapped. - * - * Use [indexOf] to calculate the associated index into the backing array. Before the buffer has - * been completely filled, this will point to the next empty slot to fill; afterwards it will - * point to the next item that should be recycled (which, because the buffer is a ring, is the - * "start" of the buffer). - * - * This value is unlikely to overflow. Assuming [advance] is called at rate of 100 calls/ms, - * omega will overflow after a little under three million years of continuous operation. - */ - private var omega: Long = 0 - - /** - * The number of items currently stored in the buffer. Calls to [advance] will cause this value - * to increase by one until it reaches [maxSize]. - */ - val size: Int - get() = if (omega < maxSize) omega.toInt() else maxSize - - /** - * Adds an item to the end of the buffer. The caller should reset the returned item's contents - * and then fill it with appropriate data. - * - * If the buffer is not yet full, uses [factory] to create a new item. Otherwise, it recycles - * the oldest item from the front of the buffer and moves it to the end. - * - * Importantly, recycled items are returned as-is, without being reset. They will retain any - * data that was previously stored on them. Callers must make sure to clear any historical data, - * if necessary. - */ - fun advance(): T { - val index = indexOf(omega) - omega += 1 - val entry = buffer[index] ?: factory().also { buffer[index] = it } - return entry - } - - /** - * Returns the value stored at [index], which can range from 0 (the "start", or oldest element - * of the buffer) to [size] - 1 (the "end", or newest element of the buffer). - */ - operator fun get(index: Int): T { - if (index < 0 || index >= size) { - throw IndexOutOfBoundsException("Index $index is out of bounds") - } - - // If omega is larger than the maxSize, then the buffer is full, and omega is equivalent - // to the "start" of the buffer. If omega is smaller than the maxSize, then the buffer is - // not yet full and our start should be 0. However, in modspace, maxSize and 0 are - // equivalent, so we can get away with using it as the start value instead. - val start = max(omega, maxSize.toLong()) - - return buffer[indexOf(start + index)]!! - } - - override fun iterator(): Iterator { - return object : Iterator { - private var position: Int = 0 - - override fun next(): T { - if (position >= size) { - throw NoSuchElementException() - } - return get(position).also { position += 1 } - } - - override fun hasNext(): Boolean { - return position < size - } - } - } - - private fun indexOf(position: Long): Int { - return (position % maxSize).toInt() - } -} diff --git a/systemUILog/.gitignore b/systemUILog/.gitignore deleted file mode 100644 index f9a33dbbcc..0000000000 --- a/systemUILog/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -.idea/ -.gradle/ -gradle/ -build/ -gradlew* -local.properties -*.iml -android.properties -buildSrc \ No newline at end of file diff --git a/systemUILog/Android.bp b/systemUILog/Android.bp deleted file mode 100644 index 627ac4b7c3..0000000000 --- a/systemUILog/Android.bp +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (C) 2023 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package { - // 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: "SystemUILogLib", - srcs: [ - "src/**/*.java", - "src/**/*.kt", - ], - static_libs: [ - "androidx.core_core-ktx", - "androidx.annotation_annotation", - "error_prone_annotations", - "SystemUICommon", - ], - manifest: "AndroidManifest.xml", - kotlincflags: ["-Xjvm-default=all"], -} diff --git a/systemUILog/AndroidManifest.xml b/systemUILog/AndroidManifest.xml deleted file mode 100644 index 4021e1a5f7..0000000000 --- a/systemUILog/AndroidManifest.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - diff --git a/systemUILog/build.gradle b/systemUILog/build.gradle deleted file mode 100644 index c1c63a67bb..0000000000 --- a/systemUILog/build.gradle +++ /dev/null @@ -1,27 +0,0 @@ -plugins { - id 'com.android.library' - id 'org.jetbrains.kotlin.android' -} - -android { - namespace "com.android.systemui.log" - buildFeatures { - aidl true - } - sourceSets { - main { - java.srcDirs = ['src'] - aidl.srcDirs = ['src'] - manifest.srcFile 'AndroidManifest.xml' - } - } -} - -addFrameworkJar('framework-15.jar') -compileOnlyCommonJars() - -dependencies { - compileOnly projects.systemUIPluginCore - compileOnly projects.systemUICommon - implementation 'com.google.errorprone:error_prone_annotations:2.33.0' -} diff --git a/systemUILog/src/com/android/systemui/log/ConstantStringsLogger.kt b/systemUILog/src/com/android/systemui/log/ConstantStringsLogger.kt deleted file mode 100644 index bc35095a34..0000000000 --- a/systemUILog/src/com/android/systemui/log/ConstantStringsLogger.kt +++ /dev/null @@ -1,33 +0,0 @@ -/* - * 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.log - -import com.google.errorprone.annotations.CompileTimeConstant - -/** - * Handy for adding basic logging with CompileTimeConstant strings - so logging with no variables. - * Most likely you want to delegate it to [ConstantStringsLoggerImpl]. - */ -interface ConstantStringsLogger { - fun v(@CompileTimeConstant msg: String) - - fun d(@CompileTimeConstant msg: String) - - fun w(@CompileTimeConstant msg: String) - - fun e(@CompileTimeConstant msg: String) -} diff --git a/systemUILog/src/com/android/systemui/log/ConstantStringsLoggerImpl.kt b/systemUILog/src/com/android/systemui/log/ConstantStringsLoggerImpl.kt deleted file mode 100644 index a4f4e13473..0000000000 --- a/systemUILog/src/com/android/systemui/log/ConstantStringsLoggerImpl.kt +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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.log - -import com.android.systemui.log.core.LogLevel -import com.google.errorprone.annotations.CompileTimeConstant - -class ConstantStringsLoggerImpl(val buffer: LogBuffer, val tag: String) : ConstantStringsLogger { - override fun v(@CompileTimeConstant msg: String) = buffer.log(tag, LogLevel.VERBOSE, msg) - - override fun d(@CompileTimeConstant msg: String) = buffer.log(tag, LogLevel.DEBUG, msg) - - override fun w(@CompileTimeConstant msg: String) = buffer.log(tag, LogLevel.WARNING, msg) - - override fun e(@CompileTimeConstant msg: String) = buffer.log(tag, LogLevel.ERROR, msg) -} diff --git a/systemUILog/src/com/android/systemui/log/LogBuffer.kt b/systemUILog/src/com/android/systemui/log/LogBuffer.kt deleted file mode 100644 index e0051f5946..0000000000 --- a/systemUILog/src/com/android/systemui/log/LogBuffer.kt +++ /dev/null @@ -1,301 +0,0 @@ -/* - * Copyright (C) 2020 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.log - -import android.os.Trace -import android.util.Log -import com.android.systemui.common.buffer.RingBuffer -import com.android.systemui.log.core.LogLevel -import com.android.systemui.log.core.LogMessage -import com.android.systemui.log.core.MessageBuffer -import com.android.systemui.log.core.MessageInitializer -import com.android.systemui.log.core.MessagePrinter -import com.google.errorprone.annotations.CompileTimeConstant -import java.io.PrintWriter -import java.util.concurrent.ArrayBlockingQueue -import java.util.concurrent.BlockingQueue -import kotlin.concurrent.thread -import kotlin.math.max - -/** - * A simple ring buffer of recyclable log messages - * - * The goal of this class is to enable logging that is both extremely chatty and extremely - * lightweight. If done properly, logging a message will not result in any heap allocations or - * string generation. Messages are only converted to strings if the log is actually dumped (usually - * as the result of taking a bug report). - * - * You can dump the entire buffer at any time by running: - * ``` - * $ adb shell dumpsys activity service com.android.systemui/.SystemUIService - * ``` - * - * ...where `bufferName` is the (case-sensitive) [name] passed to the constructor. - * - * By default, only messages of WARN level or higher are echoed to logcat, but this can be adjusted - * locally (usually for debugging purposes). - * - * To enable logcat echoing for an entire buffer: - * ``` - * $ adb shell settings put global systemui/buffer/ - * ``` - * - * To enable logcat echoing for a specific tag: - * ``` - * $ adb shell settings put global systemui/tag/ - * ``` - * - * In either case, `level` can be any of `verbose`, `debug`, `info`, `warn`, `error`, `assert`, or - * the first letter of any of the previous. - * - * In SystemUI, buffers are provided by LogModule. Instances should be created using a SysUI - * LogBufferFactory. - * - * @param name The name of this buffer, printed when the buffer is dumped and in some other - * situations. - * @param maxSize The maximum number of messages to keep in memory at any one time. Buffers start - * out empty and grow up to [maxSize] as new messages are logged. Once the buffer's size reaches - * the maximum, it behaves like a ring buffer. - */ -class LogBuffer -@JvmOverloads -constructor( - private val name: String, - private val maxSize: Int, - private val logcatEchoTracker: LogcatEchoTracker, - private val systrace: Boolean = true, -) : MessageBuffer { - private val buffer = RingBuffer(maxSize) { LogMessageImpl.create() } - - private val echoMessageQueue: BlockingQueue? = - if (logcatEchoTracker.logInBackgroundThread) ArrayBlockingQueue(10) else null - - init { - if (logcatEchoTracker.logInBackgroundThread && echoMessageQueue != null) { - thread(start = true, name = "LogBuffer-$name", priority = Thread.NORM_PRIORITY) { - try { - while (true) { - echoToDesiredEndpoints(echoMessageQueue.take()) - } - } catch (e: InterruptedException) { - Thread.currentThread().interrupt() - } - } - } - } - - var frozen = false - private set - - private val mutable - get() = !frozen && maxSize > 0 - - /** - * Logs a message to the log buffer - * - * May also log the message to logcat if echoing is enabled for this buffer or tag. - * - * The actual string of the log message is not constructed until it is needed. To accomplish - * this, logging a message is a two-step process. First, a fresh instance of [LogMessage] is - * obtained and is passed to the [messageInitializer]. The initializer stores any relevant data - * on the message's fields. The message is then inserted into the buffer where it waits until it - * is either pushed out by newer messages or it needs to printed. If and when this latter moment - * occurs, the [messagePrinter] function is called on the message. It reads whatever data the - * initializer stored and converts it to a human-readable log message. - * - * @param tag A string of at most 23 characters, used for grouping logs into categories or - * subjects. If this message is echoed to logcat, this will be the tag that is used. - * @param level Which level to log the message at, both to the buffer and to logcat if it's - * echoed. In general, a module should split most of its logs into either INFO or DEBUG level. - * INFO level should be reserved for information that other parts of the system might care - * about, leaving the specifics of code's day-to-day operations to DEBUG. - * @param messageInitializer A function that will be called immediately to store relevant data - * on the log message. The value of `this` will be the LogMessage to be initialized. - * @param messagePrinter A function that will be called if and when the message needs to be - * dumped to logcat or a bug report. It should read the data stored by the initializer and - * convert it to a human-readable string. The value of `this` will be the LogMessage to be - * printed. **IMPORTANT:** The printer should ONLY ever reference fields on the LogMessage and - * NEVER any variables in its enclosing scope. Otherwise, the runtime will need to allocate a - * new instance of the printer for each call, thwarting our attempts at avoiding any sort of - * allocation. - * @param exception Provide any exception that need to be logged. This is saved as - * [LogMessage.exception] - */ - @JvmOverloads - inline fun log( - tag: String, - level: LogLevel, - messageInitializer: MessageInitializer, - noinline messagePrinter: MessagePrinter, - exception: Throwable? = null, - ) { - val message = obtain(tag, level, messagePrinter, exception) - messageInitializer(message) - commit(message) - } - - /** - * Logs a compile-time string constant [message] to the log buffer. Use sparingly. - * - * May also log the message to logcat if echoing is enabled for this buffer or tag. This is for - * simpler use-cases where [message] is a compile time string constant. For use-cases where the - * log message is built during runtime, use the [LogBuffer.log] overloaded method that takes in - * an initializer and a message printer. - * - * Log buffers are limited by the number of entries, so logging more frequently will limit the - * time window that the LogBuffer covers in a bug report. Richer logs, on the other hand, make a - * bug report more actionable, so using the [log] with a messagePrinter to add more detail to - * every log may do more to improve overall logging than adding more logs with this method. - */ - @JvmOverloads - fun log( - tag: String, - level: LogLevel, - @CompileTimeConstant message: String, - exception: Throwable? = null, - ) = log(tag, level, { str1 = message }, { str1!! }, exception) - - /** - * You should call [log] instead of this method. - * - * Obtains the next [LogMessage] from the ring buffer. If the buffer is not yet at max size, - * grows the buffer by one. - * - * After calling [obtain], the message will now be at the end of the buffer. The caller must - * store any relevant data on the message and then call [commit]. - */ - @Synchronized - override fun obtain( - tag: String, - level: LogLevel, - messagePrinter: MessagePrinter, - exception: Throwable?, - ): LogMessage { - if (!mutable) { - return FROZEN_MESSAGE - } - val message = buffer.advance() - message.reset(tag, level, System.currentTimeMillis(), messagePrinter, exception) - return message - } - - /** - * You should call [log] instead of this method. - * - * After acquiring a message via [obtain], call this method to signal to the buffer that you - * have finished filling in its data fields. The message will be echoed to logcat if necessary. - */ - @Synchronized - override fun commit(message: LogMessage) { - if (!mutable) { - return - } - // Log in the background thread only if echoMessageQueue exists and has capacity (checking - // capacity avoids the possibility of blocking this thread) - if (echoMessageQueue != null && echoMessageQueue.remainingCapacity() > 0) { - try { - echoMessageQueue.put(message) - } catch (e: InterruptedException) { - // the background thread has been shut down, so just log on this one - echoToDesiredEndpoints(message) - } - } else { - echoToDesiredEndpoints(message) - } - } - - /** Sends message to echo after determining whether to use Logcat and/or systrace. */ - private fun echoToDesiredEndpoints(message: LogMessage) { - val includeInLogcat = - logcatEchoTracker.isBufferLoggable(name, message.level) || - logcatEchoTracker.isTagLoggable(message.tag, message.level) - echo(message, toLogcat = includeInLogcat, toSystrace = systrace) - } - - /** Converts the entire buffer to a newline-delimited string */ - @Synchronized - fun dump(pw: PrintWriter, tailLength: Int) { - val iterationStart = - if (tailLength <= 0) { - 0 - } else { - max(0, buffer.size - tailLength) - } - - for (i in iterationStart until buffer.size) { - buffer[i].dump(pw) - } - } - - /** - * "Freezes" the contents of the buffer, making it immutable until [unfreeze] is called. Calls - * to [log], [obtain], and [commit] will not affect the buffer and will return dummy values if - * necessary. - */ - @Synchronized - fun freeze() { - if (!frozen) { - log(TAG, LogLevel.DEBUG, { str1 = name }, { "$str1 frozen" }) - frozen = true - } - } - - /** Undoes the effects of calling [freeze]. */ - @Synchronized - fun unfreeze() { - if (frozen) { - frozen = false - log(TAG, LogLevel.DEBUG, { str1 = name }, { "$str1 unfrozen" }) - } - } - - private fun echo(message: LogMessage, toLogcat: Boolean, toSystrace: Boolean) { - if (toLogcat || toSystrace) { - val strMessage = message.messagePrinter(message) - if (toSystrace) { - echoToSystrace(message, strMessage) - } - if (toLogcat) { - echoToLogcat(message, strMessage) - } - } - } - - private fun echoToSystrace(message: LogMessage, strMessage: String) { - if (Trace.isTagEnabled(Trace.TRACE_TAG_APP)) { - Trace.instantForTrack( - Trace.TRACE_TAG_APP, - "UI Events", - "$name - ${message.level.shortString} ${message.tag}: $strMessage" - ) - } - } - - private fun echoToLogcat(message: LogMessage, strMessage: String) { - when (message.level) { - LogLevel.VERBOSE -> Log.v(message.tag, strMessage, message.exception) - LogLevel.DEBUG -> Log.d(message.tag, strMessage, message.exception) - LogLevel.INFO -> Log.i(message.tag, strMessage, message.exception) - LogLevel.WARNING -> Log.w(message.tag, strMessage, message.exception) - LogLevel.ERROR -> Log.e(message.tag, strMessage, message.exception) - LogLevel.WTF -> Log.wtf(message.tag, strMessage, message.exception) - } - } -} - -private const val TAG = "LogBuffer" -private val FROZEN_MESSAGE = LogMessageImpl.create() diff --git a/systemUILog/src/com/android/systemui/log/LogMessageImpl.kt b/systemUILog/src/com/android/systemui/log/LogMessageImpl.kt deleted file mode 100644 index 33cc199e71..0000000000 --- a/systemUILog/src/com/android/systemui/log/LogMessageImpl.kt +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (C) 2020 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.log - -import com.android.systemui.log.core.LogLevel -import com.android.systemui.log.core.LogMessage -import com.android.systemui.log.core.MessagePrinter - -/** Recyclable implementation of [LogMessage]. */ -data class LogMessageImpl( - override var level: LogLevel, - override var tag: String, - override var timestamp: Long, - override var messagePrinter: MessagePrinter, - override var exception: Throwable?, - override var str1: String?, - override var str2: String?, - override var str3: String?, - override var int1: Int, - override var int2: Int, - override var long1: Long, - override var long2: Long, - override var double1: Double, - override var bool1: Boolean, - override var bool2: Boolean, - override var bool3: Boolean, - override var bool4: Boolean, -) : LogMessage { - - fun reset( - tag: String, - level: LogLevel, - timestamp: Long, - renderer: MessagePrinter, - exception: Throwable? = null, - ) { - this.level = level - this.tag = tag - this.timestamp = timestamp - this.messagePrinter = renderer - this.exception = exception - str1 = null - str2 = null - str3 = null - int1 = 0 - int2 = 0 - long1 = 0 - long2 = 0 - double1 = 0.0 - bool1 = false - bool2 = false - bool3 = false - bool4 = false - } - - companion object Factory { - fun create(): LogMessageImpl { - return LogMessageImpl( - LogLevel.DEBUG, - DEFAULT_TAG, - 0, - DEFAULT_PRINTER, - null, - null, - null, - null, - 0, - 0, - 0, - 0, - 0.0, - false, - false, - false, - false - ) - } - } -} - -private const val DEFAULT_TAG = "UnknownTag" -private val DEFAULT_PRINTER: MessagePrinter = { "Unknown message: $this" } diff --git a/systemUILog/src/com/android/systemui/log/LogcatEchoTracker.kt b/systemUILog/src/com/android/systemui/log/LogcatEchoTracker.kt deleted file mode 100644 index ae717df50f..0000000000 --- a/systemUILog/src/com/android/systemui/log/LogcatEchoTracker.kt +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2020 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.log - -import com.android.systemui.log.core.LogLevel - -/** Keeps track of which [LogBuffer] messages should also appear in logcat. */ -interface LogcatEchoTracker { - /** Whether [bufferName] should echo messages of [level] or higher to logcat. */ - fun isBufferLoggable(bufferName: String, level: LogLevel): Boolean - - /** Whether [tagName] should echo messages of [level] or higher to logcat. */ - fun isTagLoggable(tagName: String, level: LogLevel): Boolean - - /** Whether to log messages in a background thread. */ - val logInBackgroundThread: Boolean -} diff --git a/systemUILog/src/com/android/systemui/log/LogcatEchoTrackerDebug.kt b/systemUILog/src/com/android/systemui/log/LogcatEchoTrackerDebug.kt deleted file mode 100644 index 9ff48cabc6..0000000000 --- a/systemUILog/src/com/android/systemui/log/LogcatEchoTrackerDebug.kt +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright (C) 2020 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.log - -import android.content.ContentResolver -import android.database.ContentObserver -import android.net.Uri -import android.os.Handler -import android.os.Looper -import android.os.Trace -import android.provider.Settings -import com.android.systemui.log.core.LogLevel - -/** - * Version of [LogcatEchoTracker] for debuggable builds - * - * The log level of individual buffers or tags can be controlled via global settings: - * ``` - * # Echo any message to of or higher - * $ adb shell settings put global systemui/buffer/ - * - * # Echo any message of and of or higher - * $ adb shell settings put global systemui/tag/ - * ``` - */ -class LogcatEchoTrackerDebug private constructor(private val contentResolver: ContentResolver) : - LogcatEchoTracker { - private val cachedBufferLevels: MutableMap = mutableMapOf() - private val cachedTagLevels: MutableMap = mutableMapOf() - override val logInBackgroundThread = true - - companion object Factory { - @JvmStatic - fun create(contentResolver: ContentResolver, mainLooper: Looper): LogcatEchoTrackerDebug { - val tracker = LogcatEchoTrackerDebug(contentResolver) - tracker.attach(mainLooper) - return tracker - } - } - - private fun clearCache() { - Trace.beginSection("LogcatEchoTrackerDebug#clearCache") - cachedBufferLevels.clear() - Trace.endSection() - } - - private fun attach(mainLooper: Looper) { - Trace.beginSection("LogcatEchoTrackerDebug#attach") - contentResolver.registerContentObserver( - Settings.Global.getUriFor(BUFFER_PATH), - true, - object : ContentObserver(Handler(mainLooper)) { - override fun onChange(selfChange: Boolean, uri: Uri?) { - super.onChange(selfChange, uri) - clearCache() - } - } - ) - - contentResolver.registerContentObserver( - Settings.Global.getUriFor(TAG_PATH), - true, - object : ContentObserver(Handler(mainLooper)) { - override fun onChange(selfChange: Boolean, uri: Uri?) { - super.onChange(selfChange, uri) - clearCache() - } - } - ) - Trace.endSection() - } - - /** Whether [bufferName] should echo messages of [level] or higher to logcat. */ - @Synchronized - override fun isBufferLoggable(bufferName: String, level: LogLevel): Boolean { - return level.ordinal >= getLogLevel(bufferName, BUFFER_PATH, cachedBufferLevels).ordinal - } - - /** Whether [tagName] should echo messages of [level] or higher to logcat. */ - @Synchronized - override fun isTagLoggable(tagName: String, level: LogLevel): Boolean { - return level >= getLogLevel(tagName, TAG_PATH, cachedTagLevels) - } - - private fun getLogLevel( - name: String, - path: String, - cache: MutableMap - ): LogLevel { - return cache[name] ?: readSetting("$path/$name").also { cache[name] = it } - } - - private fun readSetting(path: String): LogLevel { - return try { - Trace.beginSection("LogcatEchoTrackerDebug#readSetting") - parseProp(Settings.Global.getString(contentResolver, path)) - } catch (_: Settings.SettingNotFoundException) { - DEFAULT_LEVEL - } finally { - Trace.endSection() - } - } - - private fun parseProp(propValue: String?): LogLevel { - return when (propValue?.lowercase()) { - "verbose" -> LogLevel.VERBOSE - "v" -> LogLevel.VERBOSE - "debug" -> LogLevel.DEBUG - "d" -> LogLevel.DEBUG - "info" -> LogLevel.INFO - "i" -> LogLevel.INFO - "warning" -> LogLevel.WARNING - "warn" -> LogLevel.WARNING - "w" -> LogLevel.WARNING - "error" -> LogLevel.ERROR - "e" -> LogLevel.ERROR - "assert" -> LogLevel.WTF - "wtf" -> LogLevel.WTF - else -> DEFAULT_LEVEL - } - } -} - -private val DEFAULT_LEVEL = LogLevel.WARNING -private const val BUFFER_PATH = "systemui/buffer" -private const val TAG_PATH = "systemui/tag" diff --git a/systemUILog/src/com/android/systemui/log/LogcatEchoTrackerProd.kt b/systemUILog/src/com/android/systemui/log/LogcatEchoTrackerProd.kt deleted file mode 100644 index 044d97f92b..0000000000 --- a/systemUILog/src/com/android/systemui/log/LogcatEchoTrackerProd.kt +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2020 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.log - -import com.android.systemui.log.core.LogLevel - -/** Production version of [LogcatEchoTracker] that isn't configurable. */ -class LogcatEchoTrackerProd : LogcatEchoTracker { - override val logInBackgroundThread = false - - override fun isBufferLoggable(bufferName: String, level: LogLevel): Boolean { - return level >= LogLevel.WARNING - } - - override fun isTagLoggable(tagName: String, level: LogLevel): Boolean { - return level >= LogLevel.WARNING - } -} diff --git a/systemUILog/src/com/android/systemui/log/core/LogLevel.kt b/systemUILog/src/com/android/systemui/log/core/LogLevel.kt deleted file mode 100644 index d30d8e9fe0..0000000000 --- a/systemUILog/src/com/android/systemui/log/core/LogLevel.kt +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (C) 2020 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.log.core - -import android.util.Log - -/** Enum version of @Log.Level */ -enum class LogLevel(@Log.Level val nativeLevel: Int, val shortString: String) { - VERBOSE(Log.VERBOSE, "V"), - DEBUG(Log.DEBUG, "D"), - INFO(Log.INFO, "I"), - WARNING(Log.WARN, "W"), - ERROR(Log.ERROR, "E"), - WTF(Log.ASSERT, "WTF") -} diff --git a/systemUILog/src/com/android/systemui/log/core/LogMessage.kt b/systemUILog/src/com/android/systemui/log/core/LogMessage.kt deleted file mode 100644 index 3bd6473738..0000000000 --- a/systemUILog/src/com/android/systemui/log/core/LogMessage.kt +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (C) 2020 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.log.core - -import android.icu.text.SimpleDateFormat -import java.io.PrintWriter -import java.util.Locale - -/** - * Generic data class for storing messages logged to a [LogBuffer] - * - * Each LogMessage has a few standard fields ([level], [tag], and [timestamp]). The rest are generic - * data slots that may or may not be used, depending on the nature of the specific message being - * logged. - * - * When a message is logged, the code doing the logging stores data in one or more of the generic - * fields ([str1], [int1], etc). When it comes time to dump the message to logcat/bugreport/etc, the - * [messagePrinter] function reads the data stored in the generic fields and converts that to a - * human- readable string. Thus, for every log type there must be a specialized initializer function - * that stores data specific to that log type and a specialized printer function that prints that - * data. - * - * See [LogBuffer.log] for more information. - */ -interface LogMessage { - val level: LogLevel - val tag: String - val timestamp: Long - val messagePrinter: MessagePrinter - val exception: Throwable? - - var str1: String? - var str2: String? - var str3: String? - var int1: Int - var int2: Int - var long1: Long - var long2: Long - var double1: Double - var bool1: Boolean - var bool2: Boolean - var bool3: Boolean - var bool4: Boolean - - /** Function that dumps the [LogMessage] to the provided [writer]. */ - fun dump(writer: PrintWriter) { - val formattedTimestamp = DATE_FORMAT.format(timestamp) - val shortLevel = level.shortString - val messageToPrint = messagePrinter(this) - printLikeLogcat(writer, formattedTimestamp, shortLevel, tag, messageToPrint) - exception?.printStackTrace(writer) - } -} - -/** - * A function that will be called immediately to store relevant data on the log message. The value - * of `this` will be the LogMessage to be initialized. - */ -typealias MessageInitializer = LogMessage.() -> Unit - -/** - * A function that will be called if and when the message needs to be dumped to logcat or a bug - * report. It should read the data stored by the initializer and convert it to a human-readable - * string. The value of `this` will be the LogMessage to be printed. **IMPORTANT:** The printer - * should ONLY ever reference fields on the LogMessage and NEVER any variables in its enclosing - * scope. Otherwise, the runtime will need to allocate a new instance of the printer for each call, - * thwarting our attempts at avoiding any sort of allocation. - */ -typealias MessagePrinter = LogMessage.() -> String - -private fun printLikeLogcat( - pw: PrintWriter, - formattedTimestamp: String, - shortLogLevel: String, - tag: String, - message: String -) { - pw.print(formattedTimestamp) - pw.print(" ") - pw.print(shortLogLevel) - pw.print(" ") - pw.print(tag) - pw.print(": ") - pw.println(message) -} - -private val DATE_FORMAT = SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US) diff --git a/systemUILog/src/com/android/systemui/log/core/Logger.kt b/systemUILog/src/com/android/systemui/log/core/Logger.kt deleted file mode 100644 index 5729ab2704..0000000000 --- a/systemUILog/src/com/android/systemui/log/core/Logger.kt +++ /dev/null @@ -1,224 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.log.core - -import com.google.errorprone.annotations.CompileTimeConstant - -/** Logs messages to the [MessageBuffer] with [tag]. */ -open class Logger(val buffer: MessageBuffer, val tag: String) { - /** - * Logs a message to the buffer. - * - * The actual string of the log message is not constructed until it is needed. To accomplish - * this, logging a message is a two-step process. First, a fresh instance of [LogMessage] is - * obtained and is passed to the [messageInitializer]. The initializer stores any relevant data - * on the message's fields. The message is then inserted into the buffer where it waits until it - * is either pushed out by newer messages or it needs to printed. If and when this latter moment - * occurs, the [messagePrinter] function is called on the message. It reads whatever data the - * initializer stored and converts it to a human-readable log message. - * - * @param level Which level to log the message at, both to the buffer and to logcat if it's - * echoed. In general, a module should split most of its logs into either INFO or DEBUG level. - * INFO level should be reserved for information that other parts of the system might care - * about, leaving the specifics of code's day-to-day operations to DEBUG. - * @param messagePrinter A function that will be called if and when the message needs to be - * dumped to logcat or a bug report. It should read the data stored by the initializer and - * convert it to a human-readable string. The value of `this` will be the [LogMessage] to be - * printed. **IMPORTANT:** The printer should ONLY ever reference fields on the [LogMessage] - * and NEVER any variables in its enclosing scope. Otherwise, the runtime will need to - * allocate a new instance of the printer for each call, thwarting our attempts at avoiding - * any sort of allocation. - * @param exception Provide any exception that need to be logged. This is saved as - * [LogMessage.exception] - * @param messageInitializer A function that will be called immediately to store relevant data - * on the log message. The value of `this` will be the [LogMessage] to be initialized. - */ - @JvmOverloads - inline fun log( - level: LogLevel, - noinline messagePrinter: MessagePrinter, - exception: Throwable? = null, - messageInitializer: MessageInitializer, - ) { - val message = buffer.obtain(tag, level, messagePrinter, exception) - messageInitializer(message) - buffer.commit(message) - } - - /** - * Logs a compile-time string constant [message] to the log buffer. Use sparingly. - * - * This is for simpler use-cases where [message] is a compile time string constant. For - * use-cases where the log message is built during runtime, use the [log] overloaded method that - * takes in an initializer and a message printer. - * - * Buffers are limited by the number of entries, so logging more frequently will limit the time - * window that the [MessageBuffer] covers in a bug report. Richer logs, on the other hand, make - * a bug report more actionable, so using the [log] with a [MessagePrinter] to add more details - * to every log may do more to improve overall logging than adding more logs with this method. - */ - @JvmOverloads - fun log( - level: LogLevel, - @CompileTimeConstant message: String, - exception: Throwable? = null, - ) = log(level, { str1!! }, exception) { str1 = message } - - /** - * Logs a message to the buffer at [LogLevel.VERBOSE]. - * - * @see log - */ - @JvmOverloads - inline fun v( - noinline messagePrinter: MessagePrinter, - exception: Throwable? = null, - messageInitializer: MessageInitializer, - ) = log(LogLevel.VERBOSE, messagePrinter, exception, messageInitializer) - - /** - * Logs a compile-time string constant [message] to the log buffer at [LogLevel.VERBOSE]. Use - * sparingly. - * - * @see log - */ - @JvmOverloads - fun v( - @CompileTimeConstant message: String, - exception: Throwable? = null, - ) = log(LogLevel.VERBOSE, message, exception) - - /** - * Logs a message to the buffer at [LogLevel.DEBUG]. - * - * @see log - */ - @JvmOverloads - inline fun d( - noinline messagePrinter: MessagePrinter, - exception: Throwable? = null, - messageInitializer: MessageInitializer, - ) = log(LogLevel.DEBUG, messagePrinter, exception, messageInitializer) - - /** - * Logs a compile-time string constant [message] to the log buffer at [LogLevel.DEBUG]. Use - * sparingly. - * - * @see log - */ - @JvmOverloads - fun d( - @CompileTimeConstant message: String, - exception: Throwable? = null, - ) = log(LogLevel.DEBUG, message, exception) - - /** - * Logs a message to the buffer at [LogLevel.INFO]. - * - * @see log - */ - @JvmOverloads - inline fun i( - noinline messagePrinter: MessagePrinter, - exception: Throwable? = null, - messageInitializer: MessageInitializer, - ) = log(LogLevel.INFO, messagePrinter, exception, messageInitializer) - - /** - * Logs a compile-time string constant [message] to the log buffer at [LogLevel.INFO]. Use - * sparingly. - * - * @see log - */ - @JvmOverloads - fun i( - @CompileTimeConstant message: String, - exception: Throwable? = null, - ) = log(LogLevel.INFO, message, exception) - - /** - * Logs a message to the buffer at [LogLevel.WARNING]. - * - * @see log - */ - @JvmOverloads - inline fun w( - noinline messagePrinter: MessagePrinter, - exception: Throwable? = null, - messageInitializer: MessageInitializer, - ) = log(LogLevel.WARNING, messagePrinter, exception, messageInitializer) - - /** - * Logs a compile-time string constant [message] to the log buffer at [LogLevel.WARNING]. Use - * sparingly. - * - * @see log - */ - @JvmOverloads - fun w( - @CompileTimeConstant message: String, - exception: Throwable? = null, - ) = log(LogLevel.WARNING, message, exception) - - /** - * Logs a message to the buffer at [LogLevel.ERROR]. - * - * @see log - */ - @JvmOverloads - inline fun e( - noinline messagePrinter: MessagePrinter, - exception: Throwable? = null, - messageInitializer: MessageInitializer, - ) = log(LogLevel.ERROR, messagePrinter, exception, messageInitializer) - - /** - * Logs a compile-time string constant [message] to the log buffer at [LogLevel.ERROR]. Use - * sparingly. - * - * @see log - */ - @JvmOverloads - fun e( - @CompileTimeConstant message: String, - exception: Throwable? = null, - ) = log(LogLevel.ERROR, message, exception) - - /** - * Logs a message to the buffer at [LogLevel.WTF]. - * - * @see log - */ - @JvmOverloads - inline fun wtf( - noinline messagePrinter: MessagePrinter, - exception: Throwable? = null, - messageInitializer: MessageInitializer, - ) = log(LogLevel.WTF, messagePrinter, exception, messageInitializer) - - /** - * Logs a compile-time string constant [message] to the log buffer at [LogLevel.WTF]. Use - * sparingly. - * - * @see log - */ - @JvmOverloads - fun wtf( - @CompileTimeConstant message: String, - exception: Throwable? = null, - ) = log(LogLevel.WTF, message, exception) -} diff --git a/systemUILog/src/com/android/systemui/log/core/MessageBuffer.kt b/systemUILog/src/com/android/systemui/log/core/MessageBuffer.kt deleted file mode 100644 index bb91633c4d..0000000000 --- a/systemUILog/src/com/android/systemui/log/core/MessageBuffer.kt +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.log.core - -/** - * [MessageBuffer] is an interface that represents a buffer of log messages, and provides methods to - * [obtain] a log message and [commit] it to the buffer. - */ -interface MessageBuffer { - /** - * Obtains the next [LogMessage] from the buffer. - * - * After calling [obtain], the caller must store any relevant data on the message and then call - * [commit]. - */ - fun obtain( - tag: String, - level: LogLevel, - messagePrinter: MessagePrinter, - exception: Throwable? = null, - ): LogMessage - - /** - * After acquiring a log message via [obtain], call this method to signal to the buffer that - * data fields have been filled. - */ - fun commit(message: LogMessage) -} diff --git a/systemUIPlugin/Android.bp b/systemUIPlugin/Android.bp deleted file mode 100644 index bb47a2f472..0000000000 --- a/systemUIPlugin/Android.bp +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (C) 2016 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"], -} - -java_library { - - name: "SystemUIPluginLib", - - srcs: [ - "src/**/*.java", - "src/**/*.kt", - "bcsmartspace/src/**/*.java", - "bcsmartspace/src/**/*.kt", - ], - - // If you add a static lib here, you may need to also add the package to the ClassLoaderFilter - // in PluginInstance. That will ensure that loaded plugins have access to the related classes. - // You should also add it to proguard_common.flags so that proguard does not remove the portions - // of the library which are used by the plugins but not by systemui itself. - static_libs: [ - "androidx.annotation_annotation", - "PluginCoreLib", - "SystemUIAnimationLib", - "SystemUICommon", - "SystemUILogLib", - ], - -} - -android_app { - - // Dummy to generate .toc files. - name: "PluginDummyLib", - platform_apis: true, - srcs: ["src/**/*.java"], - - libs: ["SystemUIPluginLib"], - - optimize: { - enabled: false, - }, - -} diff --git a/systemUIPlugin/AndroidManifest.xml b/systemUIPlugin/AndroidManifest.xml deleted file mode 100644 index 811595ade9..0000000000 --- a/systemUIPlugin/AndroidManifest.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - diff --git a/systemUIPlugin/ExamplePlugin/Android.bp b/systemUIPlugin/ExamplePlugin/Android.bp deleted file mode 100644 index 3f0fdedb57..0000000000 --- a/systemUIPlugin/ExamplePlugin/Android.bp +++ /dev/null @@ -1,24 +0,0 @@ -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_app { - - name: "ExamplePlugin", - - libs: ["SystemUIPluginLib"], - - certificate: "platform", - optimize: { - enabled: false, - }, - - srcs: ["src/**/*.java"], - - platform_apis: true, -} diff --git a/systemUIPlugin/ExamplePlugin/AndroidManifest.xml b/systemUIPlugin/ExamplePlugin/AndroidManifest.xml deleted file mode 100644 index e9e844124e..0000000000 --- a/systemUIPlugin/ExamplePlugin/AndroidManifest.xml +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/systemUIPlugin/ExamplePlugin/res/layout/colored_overlay.xml b/systemUIPlugin/ExamplePlugin/res/layout/colored_overlay.xml deleted file mode 100644 index b2910cb19b..0000000000 --- a/systemUIPlugin/ExamplePlugin/res/layout/colored_overlay.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - diff --git a/systemUIPlugin/ExamplePlugin/res/layout/plugin_settings.xml b/systemUIPlugin/ExamplePlugin/res/layout/plugin_settings.xml deleted file mode 100644 index eb90283f08..0000000000 --- a/systemUIPlugin/ExamplePlugin/res/layout/plugin_settings.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - diff --git a/systemUIPlugin/ExamplePlugin/res/values/strings.xml b/systemUIPlugin/ExamplePlugin/res/values/strings.xml deleted file mode 100644 index a0bfe849e5..0000000000 --- a/systemUIPlugin/ExamplePlugin/res/values/strings.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - Plugin settings go here - Overlay Plugin - - diff --git a/systemUIPlugin/ExamplePlugin/src/com/android/systemui/plugin/testoverlayplugin/CustomView.java b/systemUIPlugin/ExamplePlugin/src/com/android/systemui/plugin/testoverlayplugin/CustomView.java deleted file mode 100644 index 5fdbbf989b..0000000000 --- a/systemUIPlugin/ExamplePlugin/src/com/android/systemui/plugin/testoverlayplugin/CustomView.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2016 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.plugin.testoverlayplugin; - -import android.annotation.Nullable; -import android.content.Context; -import android.util.AttributeSet; -import android.util.Log; -import android.view.View; - -/** - * View with some logging to show that its being run. - */ -public class CustomView extends View { - - private static final String TAG = "CustomView"; - - public CustomView(Context context, @Nullable AttributeSet attrs) { - super(context, attrs); - Log.d(TAG, "new instance"); - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - Log.d(TAG, "onAttachedToWindow"); - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - Log.d(TAG, "onDetachedFromWindow"); - } -} diff --git a/systemUIPlugin/ExamplePlugin/src/com/android/systemui/plugin/testoverlayplugin/PluginSettings.java b/systemUIPlugin/ExamplePlugin/src/com/android/systemui/plugin/testoverlayplugin/PluginSettings.java deleted file mode 100644 index cf39075d95..0000000000 --- a/systemUIPlugin/ExamplePlugin/src/com/android/systemui/plugin/testoverlayplugin/PluginSettings.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2016 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.plugin.testoverlayplugin; - -import android.annotation.Nullable; -import android.app.Activity; -import android.os.Bundle; - -/** - * DO NOT Reference Plugin interfaces here, this runs in the plugin APK's process - * and is only for modifying settings. - */ -public class PluginSettings extends Activity { - - @Override - public void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.plugin_settings); - } -} diff --git a/systemUIPlugin/ExamplePlugin/src/com/android/systemui/plugin/testoverlayplugin/SampleOverlayPlugin.java b/systemUIPlugin/ExamplePlugin/src/com/android/systemui/plugin/testoverlayplugin/SampleOverlayPlugin.java deleted file mode 100644 index 79a0c35990..0000000000 --- a/systemUIPlugin/ExamplePlugin/src/com/android/systemui/plugin/testoverlayplugin/SampleOverlayPlugin.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (C) 2016 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.plugin.testoverlayplugin; - -import android.content.Context; -import android.graphics.Rect; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import android.view.ViewTreeObserver.InternalInsetsInfo; -import android.view.ViewTreeObserver.OnComputeInternalInsetsListener; -import com.android.systemui.plugins.OverlayPlugin; -import com.android.systemui.plugins.annotations.Requires; - -@Requires(target = OverlayPlugin.class, version = OverlayPlugin.VERSION) -public class SampleOverlayPlugin implements OverlayPlugin { - private static final String TAG = "SampleOverlayPlugin"; - private Context mPluginContext; - - private View mStatusBarView; - private View mNavBarView; - private boolean mInputSetup; - private boolean mCollapseDesired; - private float mStatusBarHeight; - - @Override - public void onCreate(Context sysuiContext, Context pluginContext) { - Log.d(TAG, "onCreate"); - mPluginContext = pluginContext; - } - - @Override - public void onDestroy() { - if (mInputSetup) { - mStatusBarView.getViewTreeObserver().removeOnComputeInternalInsetsListener( - onComputeInternalInsetsListener); - } - Log.d(TAG, "onDestroy"); - if (mStatusBarView != null) { - mStatusBarView.post( - () -> ((ViewGroup) mStatusBarView.getParent()).removeView(mStatusBarView)); - } - if (mNavBarView != null) { - mNavBarView.post(() -> ((ViewGroup) mNavBarView.getParent()).removeView(mNavBarView)); - } - } - - @Override - public void setup(View statusBar, View navBar) { - Log.d(TAG, "Setup"); - - int id = mPluginContext.getResources().getIdentifier("status_bar_height", "dimen", - "android"); - mStatusBarHeight = mPluginContext.getResources().getDimension(id); - if (statusBar instanceof ViewGroup) { - mStatusBarView = LayoutInflater.from(mPluginContext) - .inflate(R.layout.colored_overlay, (ViewGroup) statusBar, false); - ((ViewGroup) statusBar).addView(mStatusBarView); - } - if (navBar instanceof ViewGroup) { - mNavBarView = LayoutInflater.from(mPluginContext) - .inflate(R.layout.colored_overlay, (ViewGroup) navBar, false); - ((ViewGroup) navBar).addView(mNavBarView); - } - } - - @Override - public void setCollapseDesired(boolean collapseDesired) { - mCollapseDesired = collapseDesired; - } - - @Override - public boolean holdStatusBarOpen() { - if (!mInputSetup) { - mInputSetup = true; - mStatusBarView.getViewTreeObserver().addOnComputeInternalInsetsListener( - onComputeInternalInsetsListener); - } - return true; - } - - final OnComputeInternalInsetsListener onComputeInternalInsetsListener = inoutInfo -> { - inoutInfo.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_REGION); - if (mCollapseDesired) { - inoutInfo.touchableRegion.set(new Rect(0, 0, 50000, (int) mStatusBarHeight)); - } else { - inoutInfo.touchableRegion.set(new Rect(0, 0, 50000, 50000)); - } - }; -} diff --git a/systemUIPlugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceConfigPlugin.kt b/systemUIPlugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceConfigPlugin.kt deleted file mode 100644 index 509f022310..0000000000 --- a/systemUIPlugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceConfigPlugin.kt +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.plugins - -// TODO(b/265360975): Evaluate this plugin approach. -/** Plugin to provide BC smartspace configuration */ -interface BcSmartspaceConfigPlugin { - /** Gets default date/weather disabled status. */ - val isDefaultDateWeatherDisabled: Boolean -} diff --git a/systemUIPlugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java b/systemUIPlugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java deleted file mode 100644 index 64c0f99f4b..0000000000 --- a/systemUIPlugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java +++ /dev/null @@ -1,225 +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.plugins; - -import android.app.PendingIntent; -import android.app.smartspace.SmartspaceAction; -import android.app.smartspace.SmartspaceTarget; -import android.app.smartspace.SmartspaceTargetEvent; -import android.app.smartspace.uitemplatedata.TapAction; -import android.content.ActivityNotFoundException; -import android.content.Intent; -import android.graphics.drawable.Drawable; -import android.os.Parcelable; -import android.util.Log; -import android.view.View; -import android.view.ViewGroup; - -import androidx.annotation.Nullable; - -import com.android.systemui.plugins.annotations.ProvidesInterface; - -import java.util.List; - -/** - * Interface to provide SmartspaceTargets to BcSmartspace. - */ -@ProvidesInterface(action = BcSmartspaceDataPlugin.ACTION, version = BcSmartspaceDataPlugin.VERSION) -public interface BcSmartspaceDataPlugin extends Plugin { - String UI_SURFACE_LOCK_SCREEN_AOD = "lockscreen"; - String UI_SURFACE_HOME_SCREEN = "home"; - String UI_SURFACE_MEDIA = "media_data_manager"; - String UI_SURFACE_DREAM = "dream"; - - String ACTION = "com.android.systemui.action.PLUGIN_BC_SMARTSPACE_DATA"; - int VERSION = 1; - String TAG = "BcSmartspaceDataPlugin"; - - /** Register a listener to get Smartspace data. */ - default void registerListener(SmartspaceTargetListener listener) { - throw new UnsupportedOperationException("Not implemented by " + getClass()); - } - - /** Unregister a listener. */ - default void unregisterListener(SmartspaceTargetListener listener) { - throw new UnsupportedOperationException("Not implemented by " + getClass()); - } - - /** Register a SmartspaceEventNotifier. */ - default void registerSmartspaceEventNotifier(SmartspaceEventNotifier notifier) { - throw new UnsupportedOperationException("Not implemented by " + getClass()); - } - - /** Push a SmartspaceTargetEvent to the SmartspaceEventNotifier. */ - default void notifySmartspaceEvent(SmartspaceTargetEvent event) { - throw new UnsupportedOperationException("Not implemented by " + getClass()); - } - - /** Allows for notifying the SmartspaceSession of SmartspaceTargetEvents. */ - interface SmartspaceEventNotifier { - /** Pushes a given SmartspaceTargetEvent to the SmartspaceSession. */ - void notifySmartspaceEvent(SmartspaceTargetEvent event); - } - - /** - * Create a view to be shown within the parent. Do not add the view, as the parent - * will be responsible for correctly setting the LayoutParams - */ - default SmartspaceView getView(ViewGroup parent) { - throw new UnsupportedOperationException("Not implemented by " + getClass()); - } - - /** - * As the smartspace view becomes available, allow listeners to receive an event. - */ - default void addOnAttachStateChangeListener(View.OnAttachStateChangeListener listener) { - throw new UnsupportedOperationException("Not implemented by " + getClass()); - } - - /** Updates Smartspace data and propagates it to any listeners. */ - default void onTargetsAvailable(List targets) { - throw new UnsupportedOperationException("Not implemented by " + getClass()); - } - - /** Provides Smartspace data to registered listeners. */ - interface SmartspaceTargetListener { - /** Each Parcelable is a SmartspaceTarget that represents a card. */ - void onSmartspaceTargetsUpdated(List targets); - } - - /** View to which this plugin can be registered, in order to get updates. */ - interface SmartspaceView { - void registerDataProvider(BcSmartspaceDataPlugin plugin); - - /** - * Sets {@link BcSmartspaceConfigPlugin}. - */ - default void registerConfigProvider(BcSmartspaceConfigPlugin configProvider) { - throw new UnsupportedOperationException("Not implemented by " + getClass()); - } - - /** - * Primary color for unprotected text - */ - void setPrimaryTextColor(int color); - - /** - * Set the UI surface for the cards. Should be called immediately after the view is created. - */ - void setUiSurface(String uiSurface); - - /** - * Range [0.0 - 1.0] when transitioning from Lockscreen to/from AOD - */ - void setDozeAmount(float amount); - - /** - * Set if dozing is true or false - */ - default void setDozing(boolean dozing) {} - - /** - * Set if split shade enabled - */ - default void setSplitShadeEnabled(boolean enabled) {} - - /** - * Set the current keyguard bypass enabled status. - */ - default void setKeyguardBypassEnabled(boolean enabled) {} - - /** - * Overrides how Intents/PendingIntents gets launched. Mostly to support auth from - * the lockscreen. - */ - void setIntentStarter(IntentStarter intentStarter); - - /** - * When on the lockscreen, use the FalsingManager to help detect errant touches - */ - void setFalsingManager(com.android.systemui.plugins.FalsingManager falsingManager); - - /** - * Set or clear Do Not Disturb information. - */ - default void setDnd(@Nullable Drawable image, @Nullable String description) { - throw new UnsupportedOperationException("Not implemented by " + getClass()); - } - - /** - * Set or clear next alarm information - */ - default void setNextAlarm(@Nullable Drawable image, @Nullable String description) { - throw new UnsupportedOperationException("Not implemented by " + getClass()); - } - - /** - * Set or clear device media playing - */ - default void setMediaTarget(@Nullable SmartspaceTarget target) { - throw new UnsupportedOperationException("Not implemented by " + getClass()); - } - - /** - * Get the index of the currently selected page. - */ - default int getSelectedPage() { - throw new UnsupportedOperationException("Not implemented by " + getClass()); - } - - /** - * Return the top padding value from the currently visible card, or 0 if there is no current - * card. - */ - default int getCurrentCardTopPadding() { - throw new UnsupportedOperationException("Not implemented by " + getClass()); - } - } - - /** Interface for launching Intents, which can differ on the lockscreen */ - interface IntentStarter { - default void startFromAction(SmartspaceAction action, View v, boolean showOnLockscreen) { - try { - if (action.getIntent() != null) { - startIntent(v, action.getIntent(), showOnLockscreen); - } else if (action.getPendingIntent() != null) { - startPendingIntent(v, action.getPendingIntent(), showOnLockscreen); - } - } catch (ActivityNotFoundException e) { - Log.w(TAG, "Could not launch intent for action: " + action, e); - } - } - - default void startFromAction(TapAction action, View v, boolean showOnLockscreen) { - try { - if (action.getIntent() != null) { - startIntent(v, action.getIntent(), showOnLockscreen); - } else if (action.getPendingIntent() != null) { - startPendingIntent(v, action.getPendingIntent(), showOnLockscreen); - } - } catch (ActivityNotFoundException e) { - Log.w(TAG, "Could not launch intent for action: " + action, e); - } - } - - /** Start the intent */ - void startIntent(View v, Intent i, boolean showOnLockscreen); - - /** Start the PendingIntent */ - void startPendingIntent(View v, PendingIntent pi, boolean showOnLockscreen); - } -} diff --git a/systemUIPlugin/build.gradle b/systemUIPlugin/build.gradle deleted file mode 100644 index 5b6b034dee..0000000000 --- a/systemUIPlugin/build.gradle +++ /dev/null @@ -1,28 +0,0 @@ -plugins { - id 'com.android.library' - id 'org.jetbrains.kotlin.android' -} - -android { - namespace "com.android.systemui.plugins" - buildFeatures { - aidl true - } - sourceSets { - main { - java.srcDirs = ['src'] - aidl.srcDirs = ['src'] - manifest.srcFile 'AndroidManifest.xml' - res.srcDirs = ['res'] - } - } -} - -addFrameworkJar('framework-15.jar') -compileOnlyCommonJars() - -dependencies { - compileOnly projects.systemUIPluginCore - compileOnly projects.systemUILog - compileOnly projects.systemUIAnim -} diff --git a/systemUIPlugin/src/com/android/systemui/plugins/ActivityStarter.java b/systemUIPlugin/src/com/android/systemui/plugins/ActivityStarter.java deleted file mode 100644 index 9cc87fde12..0000000000 --- a/systemUIPlugin/src/com/android/systemui/plugins/ActivityStarter.java +++ /dev/null @@ -1,158 +0,0 @@ -/* - * 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. - */ - -package com.android.systemui.plugins; - -import android.annotation.Nullable; -import android.app.PendingIntent; -import android.content.Intent; -import android.os.UserHandle; -import android.view.View; - -import com.android.systemui.animation.ActivityLaunchAnimator; -import com.android.systemui.plugins.annotations.ProvidesInterface; - -/** - * An interface to start activities. This is used as a callback from the views to - * {@link PhoneStatusBar} to allow custom handling for starting the activity, i.e. dismissing the - * Keyguard. - */ -@ProvidesInterface(version = ActivityStarter.VERSION) -public interface ActivityStarter { - int VERSION = 2; - - void startPendingIntentDismissingKeyguard(PendingIntent intent); - - /** - * Similar to {@link #startPendingIntentDismissingKeyguard(PendingIntent)}, but allows - * you to specify the callback that is executed on the UI thread after the intent is sent. - */ - void startPendingIntentDismissingKeyguard(PendingIntent intent, - Runnable intentSentUiThreadCallback); - - /** - * Similar to {@link #startPendingIntentDismissingKeyguard(PendingIntent, Runnable)}, but also - * specifies an associated view that should be used for the activity launch animation. - */ - void startPendingIntentDismissingKeyguard(PendingIntent intent, - Runnable intentSentUiThreadCallback, @Nullable View associatedView); - - /** - * Similar to {@link #startPendingIntentDismissingKeyguard(PendingIntent, Runnable)}, but also - * specifies an animation controller that should be used for the activity launch animation. - */ - void startPendingIntentDismissingKeyguard(PendingIntent intent, - Runnable intentSentUiThreadCallback, - @Nullable ActivityLaunchAnimator.Controller animationController); - - /** - * The intent flag can be specified in startActivity(). - */ - void startActivity(Intent intent, boolean onlyProvisioned, boolean dismissShade, int flags); - void startActivity(Intent intent, boolean dismissShade); - default void startActivity(Intent intent, boolean dismissShade, - @Nullable ActivityLaunchAnimator.Controller animationController) { - startActivity(intent, dismissShade, animationController, - false /* showOverLockscreenWhenLocked */); - } - - void startActivity(Intent intent, boolean dismissShade, - @Nullable ActivityLaunchAnimator.Controller animationController, - boolean showOverLockscreenWhenLocked); - void startActivity(Intent intent, boolean dismissShade, - @Nullable ActivityLaunchAnimator.Controller animationController, - boolean showOverLockscreenWhenLocked, UserHandle userHandle); - void startActivity(Intent intent, boolean onlyProvisioned, boolean dismissShade); - void startActivity(Intent intent, boolean dismissShade, Callback callback); - void postStartActivityDismissingKeyguard(Intent intent, int delay); - void postStartActivityDismissingKeyguard(Intent intent, int delay, - @Nullable ActivityLaunchAnimator.Controller animationController); - - /** Posts a start activity intent that dismisses keyguard. */ - void postStartActivityDismissingKeyguard(Intent intent, int delay, - @Nullable ActivityLaunchAnimator.Controller animationController, - @Nullable String customMessage); - void postStartActivityDismissingKeyguard(PendingIntent intent); - - /** - * Similar to {@link #postStartActivityDismissingKeyguard(PendingIntent)}, but also specifies an - * animation controller that should be used for the activity launch animation. - */ - void postStartActivityDismissingKeyguard(PendingIntent intent, - @Nullable ActivityLaunchAnimator.Controller animationController); - - void postQSRunnableDismissingKeyguard(Runnable runnable); - - void dismissKeyguardThenExecute(OnDismissAction action, @Nullable Runnable cancel, - boolean afterKeyguardGone); - - /** Authenticates if needed and dismisses keyguard to execute an action. */ - void dismissKeyguardThenExecute(OnDismissAction action, @Nullable Runnable cancel, - boolean afterKeyguardGone, @Nullable String customMessage); - - /** Starts an activity and dismisses keyguard. */ - void startActivityDismissingKeyguard(Intent intent, - boolean onlyProvisioned, - boolean dismissShade); - - /** Starts an activity and dismisses keyguard. */ - void startActivityDismissingKeyguard(Intent intent, - boolean onlyProvisioned, - boolean dismissShade, - boolean disallowEnterPictureInPictureWhileLaunching, - Callback callback, - int flags, - @Nullable ActivityLaunchAnimator.Controller animationController, - UserHandle userHandle); - - /** Execute a runnable after dismissing keyguard. */ - void executeRunnableDismissingKeyguard(Runnable runnable, - Runnable cancelAction, - boolean dismissShade, - boolean afterKeyguardGone, - boolean deferred); - - /** Execute a runnable after dismissing keyguard. */ - void executeRunnableDismissingKeyguard( - Runnable runnable, - Runnable cancelAction, - boolean dismissShade, - boolean afterKeyguardGone, - boolean deferred, - boolean willAnimateOnKeyguard, - @Nullable String customMessage); - - /** Whether we should animate an activity launch. */ - boolean shouldAnimateLaunch(boolean isActivityIntent); - - interface Callback { - void onActivityStarted(int resultCode); - } - - interface OnDismissAction { - /** - * @return {@code true} if the dismiss should be deferred. When returning true, make sure to - * call {@link com.android.keyguard.ViewMediatorCallback#readyForKeyguardDone()} - * *after* returning to start hiding the keyguard. - */ - boolean onDismiss(); - - /** - * Whether running this action when we are locked will start an animation on the keyguard. - */ - default boolean willRunAnimationOnKeyguard() { - return false; - } - } -} diff --git a/systemUIPlugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt b/systemUIPlugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt deleted file mode 100644 index e2f4793b8f..0000000000 --- a/systemUIPlugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt +++ /dev/null @@ -1,270 +0,0 @@ -/* - * 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.plugins - -import android.content.res.Resources -import android.graphics.Rect -import android.graphics.drawable.Drawable -import android.view.View -import com.android.internal.annotations.Keep -import com.android.systemui.log.core.MessageBuffer -import com.android.systemui.plugins.annotations.ProvidesInterface -import java.io.PrintWriter -import java.util.Locale -import java.util.TimeZone -import org.json.JSONObject - -/** Identifies a clock design */ -typealias ClockId = String - -/** A Plugin which exposes the ClockProvider interface */ -@ProvidesInterface(action = ClockProviderPlugin.ACTION, version = ClockProviderPlugin.VERSION) -interface ClockProviderPlugin : Plugin, ClockProvider { - companion object { - const val ACTION = "com.android.systemui.action.PLUGIN_CLOCK_PROVIDER" - const val VERSION = 1 - } -} - -/** Interface for building clocks and providing information about those clocks */ -interface ClockProvider { - /** Returns metadata for all clocks this provider knows about */ - fun getClocks(): List - - /** Initializes and returns the target clock design */ - @Deprecated("Use overload with ClockSettings") - fun createClock(id: ClockId): ClockController { - return createClock(ClockSettings(id, null)) - } - - /** Initializes and returns the target clock design */ - fun createClock(settings: ClockSettings): ClockController - - /** A static thumbnail for rendering in some examples */ - fun getClockThumbnail(id: ClockId): Drawable? -} - -/** Interface for controlling an active clock */ -interface ClockController { - /** A small version of the clock, appropriate for smaller viewports */ - val smallClock: ClockFaceController - - /** A large version of the clock, appropriate when a bigger viewport is available */ - val largeClock: ClockFaceController - - /** Determines the way the hosting app should behave when rendering either clock face */ - val config: ClockConfig - - /** Events that clocks may need to respond to */ - val events: ClockEvents - - /** Initializes various rendering parameters. If never called, provides reasonable defaults. */ - fun initialize( - resources: Resources, - dozeFraction: Float, - foldFraction: Float, - ) - - /** Optional method for dumping debug information */ - fun dump(pw: PrintWriter) -} - -/** Interface for a specific clock face version rendered by the clock */ -interface ClockFaceController { - /** View that renders the clock face */ - val view: View - - /** Determines the way the hosting app should behave when rendering this clock face */ - val config: ClockFaceConfig - - /** Events specific to this clock face */ - val events: ClockFaceEvents - - /** Triggers for various animations */ - val animations: ClockAnimations - - /** Some clocks may log debug information */ - var messageBuffer: MessageBuffer? -} - -/** Events that should call when various rendering parameters change */ -interface ClockEvents { - /** Call whenever timezone changes */ - fun onTimeZoneChanged(timeZone: TimeZone) - - /** Call whenever the text time format changes (12hr vs 24hr) */ - fun onTimeFormatChanged(is24Hr: Boolean) - - /** Call whenever the locale changes */ - fun onLocaleChanged(locale: Locale) - - /** Call whenever the color palette should update */ - fun onColorPaletteChanged(resources: Resources) - - /** Call if the seed color has changed and should be updated */ - fun onSeedColorChanged(seedColor: Int?) - - /** Call whenever the weather data should update */ - fun onWeatherDataChanged(data: WeatherData) -} - -/** Methods which trigger various clock animations */ -interface ClockAnimations { - /** Runs an enter animation (if any) */ - fun enter() - - /** Sets how far into AOD the device currently is. */ - fun doze(fraction: Float) - - /** Sets how far into the folding animation the device is. */ - fun fold(fraction: Float) - - /** Runs the battery animation (if any). */ - fun charge() - - /** - * Runs when the clock's position changed during the move animation. - * - * @param fromLeft the [View.getLeft] position of the clock, before it started moving. - * @param direction the direction in which it is moving. A positive number means right, and - * negative means left. - * @param fraction fraction of the clock movement. 0 means it is at the beginning, and 1 means - * it finished moving. - */ - fun onPositionUpdated(fromLeft: Int, direction: Int, fraction: Float) - - /** - * Runs when swiping clock picker, swipingFraction: 1.0 -> clock is scaled up in the preview, - * 0.0 -> clock is scaled down in the shade; previewRatio is previewSize / screenSize - */ - fun onPickerCarouselSwiping(swipingFraction: Float) -} - -/** Events that have specific data about the related face */ -interface ClockFaceEvents { - /** Call every time tick */ - fun onTimeTick() - - /** - * Region Darkness specific to the clock face. - * - isRegionDark = dark theme -> clock should be light - * - !isRegionDark = light theme -> clock should be dark - */ - fun onRegionDarknessChanged(isRegionDark: Boolean) - - /** - * Call whenever font settings change. Pass in a target font size in pixels. The specific clock - * design is allowed to ignore this target size on a case-by-case basis. - */ - fun onFontSettingChanged(fontSizePx: Float) - - /** - * Target region information for the clock face. For small clock, this will match the bounds of - * the parent view mostly, but have a target height based on the height of the default clock. - * For large clocks, the parent view is the entire device size, but most clocks will want to - * render within the centered targetRect to avoid obstructing other elements. The specified - * targetRegion is relative to the parent view. - */ - fun onTargetRegionChanged(targetRegion: Rect?) - - /** Called to notify the clock about its display. */ - fun onSecondaryDisplayChanged(onSecondaryDisplay: Boolean) -} - -/** Tick rates for clocks */ -enum class ClockTickRate(val value: Int) { - PER_MINUTE(2), // Update the clock once per minute. - PER_SECOND(1), // Update the clock once per second. - PER_FRAME(0), // Update the clock every second. -} - -/** Some data about a clock design */ -data class ClockMetadata( - val clockId: ClockId, - val name: String, -) { - constructor(clockId: ClockId) : this(clockId, clockId) {} -} - -/** Render configuration for the full clock. Modifies the way systemUI behaves with this clock. */ -data class ClockConfig( - val id: String, - - /** Transition to AOD should move smartspace like large clock instead of small clock */ - val useAlternateSmartspaceAODTransition: Boolean = false, - - /** True if the clock will react to tone changes in the seed color. */ - val isReactiveToTone: Boolean = true, -) - -/** Render configuration options for a clock face. Modifies the way SystemUI behaves. */ -data class ClockFaceConfig( - /** Expected interval between calls to onTimeTick. Can always reduce to PER_MINUTE in AOD. */ - val tickRate: ClockTickRate = ClockTickRate.PER_MINUTE, - - /** Call to check whether the clock consumes weather data */ - val hasCustomWeatherDataDisplay: Boolean = false, - - /** - * Whether this clock has a custom position update animation. If true, the keyguard will call - * `onPositionUpdated` to notify the clock of a position update animation. If false, a default - * animation will be used (e.g. a simple translation). - */ - val hasCustomPositionUpdatedAnimation: Boolean = false, -) - -/** Structure for keeping clock-specific settings */ -@Keep -data class ClockSettings( - val clockId: ClockId? = null, - val seedColor: Int? = null, -) { - // Exclude metadata from equality checks - var metadata: JSONObject = JSONObject() - - companion object { - private val KEY_CLOCK_ID = "clockId" - private val KEY_SEED_COLOR = "seedColor" - private val KEY_METADATA = "metadata" - - fun serialize(setting: ClockSettings?): String { - if (setting == null) { - return "" - } - - return JSONObject() - .put(KEY_CLOCK_ID, setting.clockId) - .put(KEY_SEED_COLOR, setting.seedColor) - .put(KEY_METADATA, setting.metadata) - .toString() - } - - fun deserialize(jsonStr: String?): ClockSettings? { - if (jsonStr.isNullOrEmpty()) { - return null - } - - val json = JSONObject(jsonStr) - val result = - ClockSettings( - if (!json.isNull(KEY_CLOCK_ID)) json.getString(KEY_CLOCK_ID) else null, - if (!json.isNull(KEY_SEED_COLOR)) json.getInt(KEY_SEED_COLOR) else null - ) - if (!json.isNull(KEY_METADATA)) { - result.metadata = json.getJSONObject(KEY_METADATA) - } - return result - } - } -} diff --git a/systemUIPlugin/src/com/android/systemui/plugins/DarkIconDispatcher.java b/systemUIPlugin/src/com/android/systemui/plugins/DarkIconDispatcher.java deleted file mode 100644 index a5e5aaa499..0000000000 --- a/systemUIPlugin/src/com/android/systemui/plugins/DarkIconDispatcher.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright (C) 2018 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.plugins; - -import android.graphics.Color; -import android.graphics.Rect; -import android.view.View; -import android.widget.ImageView; - -import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver; -import com.android.systemui.plugins.annotations.DependsOn; -import com.android.systemui.plugins.annotations.ProvidesInterface; - -import java.util.ArrayList; -import java.util.Collection; - -/** - * Dispatches events to {@link DarkReceiver}s about changes in darkness, tint area and dark - * intensity. Accessible through {@link PluginDependency} - */ -@ProvidesInterface(version = DarkIconDispatcher.VERSION) -@DependsOn(target = DarkReceiver.class) -public interface DarkIconDispatcher { - int VERSION = 2; - - /** - * Sets the dark area so {@link #applyDark} only affects the icons in the specified area. - * - * @param r the areas in which icons should change its tint, in logical screen - * coordinates - */ - void setIconsDarkArea(ArrayList r); - - /** - * Adds a receiver to receive callbacks onDarkChanged - */ - void addDarkReceiver(DarkReceiver receiver); - - /** - * Adds a receiver to receive callbacks onDarkChanged - */ - void addDarkReceiver(ImageView imageView); - - /** - * Must have been previously been added through one of the addDarkReceive methods above. - */ - void removeDarkReceiver(DarkReceiver object); - - /** - * Must have been previously been added through one of the addDarkReceive methods above. - */ - void removeDarkReceiver(ImageView object); - - /** - * Used to reapply darkness on an object, must have previously been added through - * addDarkReceiver. - */ - void applyDark(DarkReceiver object); - - int DEFAULT_ICON_TINT = Color.WHITE; - Rect sTmpRect = new Rect(); - int[] sTmpInt2 = new int[2]; - - /** - * @return the tint to apply to view depending on the desired tint color and - * the screen tintArea in which to apply that tint - */ - static int getTint(Collection tintAreas, View view, int color) { - if (isInAreas(tintAreas, view)) { - return color; - } else { - return DEFAULT_ICON_TINT; - } - } - - /** - * @return true if more than half of the view area are in any of the given - * areas, false otherwise - */ - static boolean isInAreas(Collection areas, View view) { - if (areas.isEmpty()) { - return true; - } - for (Rect area : areas) { - if (isInArea(area, view)) { - return true; - } - } - return false; - } - - /** - * @return true if more than half of the view area are in area, false - * otherwise - */ - static boolean isInArea(Rect area, View view) { - if (area.isEmpty()) { - return true; - } - sTmpRect.set(area); - view.getLocationOnScreen(sTmpInt2); - int left = sTmpInt2[0]; - - int intersectStart = Math.max(left, area.left); - int intersectEnd = Math.min(left + view.getWidth(), area.right); - int intersectAmount = Math.max(0, intersectEnd - intersectStart); - - boolean coversFullStatusBar = area.top <= 0; - boolean majorityOfWidth = 2 * intersectAmount > view.getWidth(); - return majorityOfWidth && coversFullStatusBar; - } - - /** - * Receives a callback on darkness changes - */ - @ProvidesInterface(version = DarkReceiver.VERSION) - interface DarkReceiver { - int VERSION = 2; - void onDarkChanged(ArrayList areas, float darkIntensity, int tint); - } -} diff --git a/systemUIPlugin/src/com/android/systemui/plugins/DozeServicePlugin.java b/systemUIPlugin/src/com/android/systemui/plugins/DozeServicePlugin.java deleted file mode 100644 index 3ca5690af4..0000000000 --- a/systemUIPlugin/src/com/android/systemui/plugins/DozeServicePlugin.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.android.systemui.plugins; - -import com.android.systemui.plugins.annotations.ProvidesInterface; - -@ProvidesInterface(action = DozeServicePlugin.ACTION, version = DozeServicePlugin.VERSION) -public interface DozeServicePlugin extends Plugin { - String ACTION = "com.android.systemui.action.PLUGIN_DOZE"; - int VERSION = 1; - - public interface RequestDoze { - void onRequestShowDoze(); - - void onRequestHideDoze(); - } - - void onDreamingStarted(); - - void onDreamingStopped(); - - void setDozeRequester(RequestDoze requester); -} diff --git a/systemUIPlugin/src/com/android/systemui/plugins/FalsingManager.java b/systemUIPlugin/src/com/android/systemui/plugins/FalsingManager.java deleted file mode 100644 index e52a57f761..0000000000 --- a/systemUIPlugin/src/com/android/systemui/plugins/FalsingManager.java +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright (C) 2019 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.plugins; - -import android.annotation.IntDef; -import android.net.Uri; -import android.view.MotionEvent; - -import com.android.systemui.plugins.annotations.ProvidesInterface; - -import java.io.PrintWriter; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * Interface that decides whether a touch on the phone was accidental. i.e. Pocket Dialing. - * - * {@see com.android.systemui.classifier.BrightLineFalsingManager} - */ -@ProvidesInterface(version = FalsingManager.VERSION) -public interface FalsingManager { - int VERSION = 6; - - int NO_PENALTY = 0; - int LOW_PENALTY = 1; - int MODERATE_PENALTY = 2; - int HIGH_PENALTY = 3; - - @IntDef({ - NO_PENALTY, - LOW_PENALTY, - MODERATE_PENALTY, - HIGH_PENALTY - }) - @Retention(RetentionPolicy.SOURCE) - public @interface Penalty {} - - void onSuccessfulUnlock(); - - boolean isUnlockingDisabled(); - - /** Returns true if the gesture should be rejected. */ - boolean isFalseTouch(int interactionType); - - /** - * Does basic checking to see if gesture looks like a tap. - * - * Only does the most basic of checks. No penalty is applied if this method returns false. - * - * For more robust analysis, use {@link #isFalseTap(int)}. - */ - boolean isSimpleTap(); - - /** - * Returns true if the FalsingManager thinks the last gesture was not a valid tap. - * - * This method runs a more thorough analysis than the similar {@link #isSimpleTap()}, - * that can include historical interactions and other contextual cues to see - * if the tap looks accidental. - * - * Use this method to validate a tap for launching an action, like opening - * a notification. - * - * The only parameter, penalty, indicates how much this should affect future gesture - * classifications if this tap looks like a false. As single taps are hard to confirm as false - * or otherwise, a low penalty value is encouraged unless context indicates otherwise. - */ - boolean isFalseTap(@Penalty int penalty); - - /** - * Returns true if the FalsingManager thinks the last gesture was not a valid long tap. - * - * Use this method to validate a long tap for launching an action, like long press on a UMO - * - * The only parameter, penalty, indicates how much this should affect future gesture - * classifications if this long tap looks like a false. - * As long taps are hard to confirm as false or otherwise, - * a low penalty value is encouraged unless context indicates otherwise. - */ - boolean isFalseLongTap(@Penalty int penalty); - - /** - * Returns true if the last two gestures do not look like a double tap. - * - * Only works on data that has already been reported to the FalsingManager. Be sure that - * {@link com.android.systemui.classifier.FalsingCollector#onTouchEvent(MotionEvent)} - * has already been called for all of the taps you want considered. - * - * This looks at the last two gestures on the screen, ensuring that they meet the following - * criteria: - * - * a) There are at least two gestures. - * b) The last two gestures look like taps. - * c) The last two gestures look like a double tap taken together. - * - * This method is _not_ context aware. That is to say, if two taps occur on two neighboring - * views, but are otherwise close to one another, this will report a successful double tap. - * It is up to the caller to decide - * @return - */ - boolean isFalseDoubleTap(); - - /** - * Whether the last proximity event reported NEAR. May be used to short circuit motion events - * that require the proximity sensor is not covered. - */ - boolean isProximityNear(); - - boolean isClassifierEnabled(); - - boolean shouldEnforceBouncer(); - - Uri reportRejectedTouch(); - - boolean isReportingEnabled(); - - /** From com.android.systemui.Dumpable. */ - void dump(PrintWriter pw, String[] args); - - /** - * Don't call this. It's meant for internal use to allow switching between implementations. - * - * Tests may also call it. - **/ - void cleanupInternal(); - - /** Call to report a ProximityEvent to the FalsingManager. */ - void onProximityEvent(ProximityEvent proximityEvent); - - /** Adds a {@link FalsingBeliefListener}. */ - void addFalsingBeliefListener(FalsingBeliefListener listener); - - /** Removes a {@link FalsingBeliefListener}. */ - void removeFalsingBeliefListener(FalsingBeliefListener listener); - - /** Adds a {@link FalsingTapListener}. */ - void addTapListener(FalsingTapListener falsingTapListener); - - /** Removes a {@link FalsingTapListener}. */ - void removeTapListener(FalsingTapListener falsingTapListener); - - /** Listener that is alerted when falsing belief level crosses a predfined threshold. */ - interface FalsingBeliefListener { - void onFalse(); - } - - /** - * Listener that is alerted when an additional tap is required to confirm a single tap. - **/ - interface FalsingTapListener { - void onAdditionalTapRequired(); - } - - /** Passed to {@link FalsingManager#onProximityEvent}. */ - interface ProximityEvent { - /** Returns true when the proximity sensor was covered. */ - boolean getCovered(); - - /** Returns when the proximity sensor was covered in nanoseconds. */ - long getTimestampNs(); - } -} diff --git a/systemUIPlugin/src/com/android/systemui/plugins/FalsingPlugin.java b/systemUIPlugin/src/com/android/systemui/plugins/FalsingPlugin.java deleted file mode 100644 index 53d708d3b2..0000000000 --- a/systemUIPlugin/src/com/android/systemui/plugins/FalsingPlugin.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2018 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.plugins; - -import android.content.Context; - -import com.android.systemui.plugins.annotations.DependsOn; -import com.android.systemui.plugins.annotations.ProvidesInterface; - -/** - * Used to capture Falsing data (related to unlocking the screen). - * - * The intent is that the data can later be analyzed to validate the quality of the - * {@link FalsingManager}. - */ -@ProvidesInterface(action = FalsingPlugin.ACTION, version = FalsingPlugin.VERSION) -@DependsOn(target = FalsingManager.class) -public interface FalsingPlugin extends Plugin { - String ACTION = "com.android.systemui.action.FALSING_PLUGIN"; - int VERSION = 2; - - /** - * Called when there is data to be recorded. - * - * @param success Indicates whether the action is considered a success. - * @param data The raw data to be recorded for analysis. - */ - default void dataCollected(boolean success, byte[] data) { } - - /** - * Return a {@link FalsingManager} to be used in place of the system's default. - * - * @param context - */ - default FalsingManager getFalsingManager(Context context) { - return null; - } -} diff --git a/systemUIPlugin/src/com/android/systemui/plugins/FragmentBase.java b/systemUIPlugin/src/com/android/systemui/plugins/FragmentBase.java deleted file mode 100644 index af55e8b830..0000000000 --- a/systemUIPlugin/src/com/android/systemui/plugins/FragmentBase.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2016 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.plugins; - -import android.content.Context; -import android.view.View; - -/** - * Interface to deal with lack of multiple inheritance - * - * This interface is designed to be used as a base class for plugin interfaces - * that need fragment methods. Plugins should not extend Fragment directly, so - * plugins that are fragments should be extending PluginFragment, but in SysUI - * these same versions should extend Fragment directly. - * - * Only methods that are on Fragment should be included here. - */ -public interface FragmentBase { - View getView(); - Context getContext(); -} diff --git a/systemUIPlugin/src/com/android/systemui/plugins/GlobalActions.java b/systemUIPlugin/src/com/android/systemui/plugins/GlobalActions.java deleted file mode 100644 index 95ff13b45f..0000000000 --- a/systemUIPlugin/src/com/android/systemui/plugins/GlobalActions.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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. - */ - -package com.android.systemui.plugins; - -import com.android.systemui.plugins.GlobalActions.GlobalActionsManager; -import com.android.systemui.plugins.annotations.DependsOn; -import com.android.systemui.plugins.annotations.ProvidesInterface; - -@ProvidesInterface(action = GlobalActions.ACTION, version = GlobalActions.VERSION) -@DependsOn(target = GlobalActionsManager.class) -public interface GlobalActions extends Plugin { - - String ACTION = "com.android.systemui.action.PLUGIN_GLOBAL_ACTIONS"; - int VERSION = 1; - - void showGlobalActions(GlobalActionsManager manager); - default void showShutdownUi(boolean isReboot, String reason) { - } - - default void destroy() { - } - - @ProvidesInterface(version = GlobalActionsManager.VERSION) - public interface GlobalActionsManager { - int VERSION = 1; - - void onGlobalActionsShown(); - void onGlobalActionsHidden(); - - void shutdown(); - void reboot(boolean safeMode); - } -} diff --git a/systemUIPlugin/src/com/android/systemui/plugins/GlobalActionsPanelPlugin.java b/systemUIPlugin/src/com/android/systemui/plugins/GlobalActionsPanelPlugin.java deleted file mode 100644 index 429458fe07..0000000000 --- a/systemUIPlugin/src/com/android/systemui/plugins/GlobalActionsPanelPlugin.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (C) 2019 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.plugins; - -import android.annotation.Nullable; -import android.app.BroadcastOptions; -import android.app.PendingIntent; -import android.graphics.drawable.Drawable; -import android.view.View; - -import com.android.systemui.plugins.annotations.DependsOn; -import com.android.systemui.plugins.annotations.ProvidesInterface; - -/** - * Plugin which provides a "Panel" {@link View} to be rendered inside of the GlobalActions menu. - * - * Implementations should construct a new {@link PanelViewController} with the given - * {@link Callbacks} instance inside of {@link #onPanelShown(Callbacks, boolean)}, and should not - * hold onto a reference, instead allowing Global Actions to manage the lifetime of the object. - * - * Under this assumption, {@link PanelViewController} represents the lifetime of a single invocation - * of the Global Actions menu. The {@link View} for the Panel is generated when the - * {@link PanelViewController} is constructed, and {@link PanelViewController#getPanelContent()} - * serves as a simple getter. When Global Actions is dismissed, - * {@link PanelViewController#onDismissed()} can be used to cleanup any resources allocated when - * constructed. Global Actions will then release the reference, and the {@link PanelViewController} - * will be garbage-collected. - */ -@ProvidesInterface( - action = GlobalActionsPanelPlugin.ACTION, version = GlobalActionsPanelPlugin.VERSION) -@DependsOn(target = GlobalActionsPanelPlugin.Callbacks.class) -@DependsOn(target = GlobalActionsPanelPlugin.PanelViewController.class) -public interface GlobalActionsPanelPlugin extends Plugin { - String ACTION = "com.android.systemui.action.PLUGIN_GLOBAL_ACTIONS_PANEL"; - int VERSION = 0; - - /** - * Invoked when the GlobalActions menu is shown. - * - * @param callbacks {@link Callbacks} instance that can be used by the Panel to interact with - * the Global Actions menu. - * @param deviceLocked Indicates whether or not the device is currently locked. - * @return A {@link PanelViewController} instance used to receive Global Actions events. - */ - PanelViewController onPanelShown(Callbacks callbacks, boolean deviceLocked); - - /** - * Provides methods to interact with the Global Actions menu. - */ - @ProvidesInterface(version = Callbacks.VERSION) - interface Callbacks { - int VERSION = 0; - - /** Dismisses the Global Actions menu. */ - void dismissGlobalActionsMenu(); - - /** Starts a PendingIntent, dismissing the keyguard if necessary. */ - default void startPendingIntentDismissingKeyguard(PendingIntent pendingIntent) { - try { - BroadcastOptions options = BroadcastOptions.makeBasic(); - options.setInteractive(true); - pendingIntent.send(options.toBundle()); - } catch (PendingIntent.CanceledException e) { - // no-op - } - } - } - - /** - * Receives Global Actions events, and provides the Panel {@link View}. - */ - @ProvidesInterface(version = PanelViewController.VERSION) - interface PanelViewController { - int VERSION = 0; - - /** - * Returns the {@link View} for the Panel to be rendered in Global Actions. This View can be - * any size, and will be rendered above the Global Actions menu when z-ordered. - */ - View getPanelContent(); - - /** - * Invoked when the Global Actions menu (containing the View returned from - * {@link #getPanelContent()}) is dismissed. - */ - void onDismissed(); - - /** - * Invoked when the device is either locked or unlocked. - */ - void onDeviceLockStateChanged(boolean locked); - - /** - * Optionally returns a drawable to be used as the background for Global Actions. - */ - @Nullable - default Drawable getBackgroundDrawable() { - return null; - } - } -} diff --git a/systemUIPlugin/src/com/android/systemui/plugins/IntentButtonProvider.java b/systemUIPlugin/src/com/android/systemui/plugins/IntentButtonProvider.java deleted file mode 100644 index 97dbafd65d..0000000000 --- a/systemUIPlugin/src/com/android/systemui/plugins/IntentButtonProvider.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2016 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.plugins; - -import com.android.systemui.plugins.annotations.ProvidesInterface; - -import android.content.Intent; -import android.graphics.drawable.Drawable; - -/** - * An Intent Button represents a triggerable element in SysUI that consists of an - * Icon and an intent to trigger when it is activated (clicked, swiped, etc.). - */ -@ProvidesInterface(version = IntentButtonProvider.VERSION) -public interface IntentButtonProvider extends Plugin { - - public static final int VERSION = 1; - - public IntentButton getIntentButton(); - - public interface IntentButton { - public static class IconState { - public boolean isVisible = true; - public CharSequence contentDescription = null; - public Drawable drawable; - public boolean tint = true; - } - - public IconState getIcon(); - - public Intent getIntent(); - } -} diff --git a/systemUIPlugin/src/com/android/systemui/plugins/NavigationEdgeBackPlugin.java b/systemUIPlugin/src/com/android/systemui/plugins/NavigationEdgeBackPlugin.java deleted file mode 100644 index 5f6f11c16d..0000000000 --- a/systemUIPlugin/src/com/android/systemui/plugins/NavigationEdgeBackPlugin.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (C) 2019 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.plugins; - -import android.graphics.Point; -import android.view.MotionEvent; -import android.view.WindowManager; - -import com.android.systemui.plugins.annotations.ProvidesInterface; - -import java.io.PrintWriter; - -/** Plugin to handle navigation edge gestures for Back. */ -@ProvidesInterface( - action = NavigationEdgeBackPlugin.ACTION, - version = NavigationEdgeBackPlugin.VERSION) -public interface NavigationEdgeBackPlugin extends Plugin { - String ACTION = "com.android.systemui.action.PLUGIN_NAVIGATION_EDGE_BACK_ACTION"; - int VERSION = 1; - - - /** Specifies if the UI should be rendered on the left side of the screen. */ - void setIsLeftPanel(boolean isLeftPanel); - - /** Sets the insets for the gesture handling area. */ - void setInsets(int leftInset, int rightInset); - - /** Sets the display size. */ - void setDisplaySize(Point displaySize); - - /** Sets the callback that should be invoked when a Back gesture is detected. */ - void setBackCallback(BackCallback callback); - - /** Sets the base LayoutParams for the UI. */ - void setLayoutParams(WindowManager.LayoutParams layoutParams); - - /** Updates the UI based on the motion events passed in device coordinates. */ - void onMotionEvent(MotionEvent motionEvent); - - /** Dumps info about the back gesture plugin. */ - void dump(PrintWriter pw); - - /** Callback to let the system react to the detected back gestures. */ - interface BackCallback { - /** Indicates that a Back gesture was recognized and the system should go back. */ - void triggerBack(); - - /** Indicates that the gesture was cancelled and the system should not go back. */ - void cancelBack(); - - /** - * Indicates if back will be triggered if committed in current state. - * - * @param triggerBack if back will be triggered in current state. - */ - void setTriggerBack(boolean triggerBack); - } -} diff --git a/systemUIPlugin/src/com/android/systemui/plugins/NotificationListenerController.java b/systemUIPlugin/src/com/android/systemui/plugins/NotificationListenerController.java deleted file mode 100644 index 6799450079..0000000000 --- a/systemUIPlugin/src/com/android/systemui/plugins/NotificationListenerController.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * 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. - */ - -package com.android.systemui.plugins; - -import android.app.NotificationChannel; -import android.os.UserHandle; -import android.service.notification.NotificationListenerService.RankingMap; -import android.service.notification.StatusBarNotification; - -import com.android.systemui.plugins.NotificationListenerController.NotificationProvider; -import com.android.systemui.plugins.annotations.DependsOn; -import com.android.systemui.plugins.annotations.ProvidesInterface; - -@ProvidesInterface(action = NotificationListenerController.ACTION, - version = NotificationListenerController.VERSION) -@DependsOn(target = NotificationProvider.class) -public interface NotificationListenerController extends Plugin { - String ACTION = "com.android.systemui.action.PLUGIN_NOTIFICATION_ASSISTANT"; - int VERSION = 1; - - void onListenerConnected(NotificationProvider provider); - - /** - * @return whether plugin wants to skip the default callbacks. - */ - default boolean onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) { - return false; - } - - /** - * @return whether plugin wants to skip the default callbacks. - */ - default boolean onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap) { - return false; - } - - /** - * Called when a notification channel is modified. - * @param modificationType One of {@link #NOTIFICATION_CHANNEL_OR_GROUP_ADDED}, - * {@link #NOTIFICATION_CHANNEL_OR_GROUP_UPDATED}, - * {@link #NOTIFICATION_CHANNEL_OR_GROUP_DELETED}. - * @return whether a plugin wants to skip the default callbacks. - */ - default boolean onNotificationChannelModified( - String pkgName, UserHandle user, NotificationChannel channel, int modificationType) { - return false; - } - - default StatusBarNotification[] getActiveNotifications( - StatusBarNotification[] activeNotifications) { - return activeNotifications; - } - - default RankingMap getCurrentRanking(RankingMap currentRanking) { - return currentRanking; - } - - @ProvidesInterface(version = NotificationProvider.VERSION) - interface NotificationProvider { - int VERSION = 1; - - // Methods to get info about current notifications - StatusBarNotification[] getActiveNotifications(); - RankingMap getRankingMap(); - - // Methods to notify sysui of changes to notification list. - void addNotification(StatusBarNotification sbn); - void removeNotification(StatusBarNotification sbn); - void updateRanking(); - } -} diff --git a/systemUIPlugin/src/com/android/systemui/plugins/NotificationPersonExtractorPlugin.java b/systemUIPlugin/src/com/android/systemui/plugins/NotificationPersonExtractorPlugin.java deleted file mode 100644 index f79cd8625c..0000000000 --- a/systemUIPlugin/src/com/android/systemui/plugins/NotificationPersonExtractorPlugin.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (C) 2019 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.plugins; - -import android.annotation.Nullable; -import android.app.PendingIntent; -import android.graphics.drawable.Drawable; -import android.service.notification.StatusBarNotification; - -import com.android.systemui.plugins.annotations.DependsOn; -import com.android.systemui.plugins.annotations.ProvidesInterface; - -/** Custom logic that can extract a PeopleHub "person" from a notification. */ -@ProvidesInterface( - action = NotificationPersonExtractorPlugin.ACTION, - version = NotificationPersonExtractorPlugin.VERSION) -@DependsOn(target = NotificationPersonExtractorPlugin.PersonData.class) -public interface NotificationPersonExtractorPlugin extends Plugin { - - String ACTION = "com.android.systemui.action.PEOPLE_HUB_PERSON_EXTRACTOR"; - int VERSION = 1; - - /** - * Attempts to extract a person from a notification. Returns {@code null} if one is not found. - */ - @Nullable - PersonData extractPerson(StatusBarNotification sbn); - - /** - * Attempts to extract a person id from a notification. Returns {@code null} if one is not - * found. - * - * This method can be overridden in order to provide a faster implementation. - */ - @Nullable - default String extractPersonKey(StatusBarNotification sbn) { - return extractPerson(sbn).key; - } - - /** - * Determines whether or not a notification should be treated as having a person. Used for - * appropriate positioning in the notification shade. - */ - default boolean isPersonNotification(StatusBarNotification sbn) { - return extractPersonKey(sbn) != null; - } - - /** A person to be surfaced in PeopleHub. */ - @ProvidesInterface(version = PersonData.VERSION) - final class PersonData { - - public static final int VERSION = 0; - - public final String key; - public final CharSequence name; - public final Drawable avatar; - public final Runnable clickRunnable; - - public PersonData(String key, CharSequence name, Drawable avatar, - Runnable clickRunnable) { - this.key = key; - this.name = name; - this.avatar = avatar; - this.clickRunnable = clickRunnable; - } - } -} diff --git a/systemUIPlugin/src/com/android/systemui/plugins/OverlayPlugin.java b/systemUIPlugin/src/com/android/systemui/plugins/OverlayPlugin.java deleted file mode 100644 index 075df75f93..0000000000 --- a/systemUIPlugin/src/com/android/systemui/plugins/OverlayPlugin.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2016 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.plugins; - -import android.view.View; - -import com.android.systemui.plugins.annotations.ProvidesInterface; -import com.android.systemui.plugins.statusbar.DozeParameters; - -@ProvidesInterface(action = OverlayPlugin.ACTION, version = OverlayPlugin.VERSION) -public interface OverlayPlugin extends Plugin { - - String ACTION = "com.android.systemui.action.PLUGIN_OVERLAY"; - int VERSION = 4; - - /** - * Setup overlay plugin - */ - void setup(View statusBar, View navBar); - - /** - * Setup overlay plugin with callback and DozeParameters - */ - default void setup(View statusBar, View navBar, Callback callback, - DozeParameters dozeParameters) { - setup(statusBar, navBar); - } - - default boolean holdStatusBarOpen() { - return false; - } - - /** - * Only called if the plugin has returned true to holdStatusBarOpen(). - */ - default void setCollapseDesired(boolean collapseDesired) { - } - - /** - * Used to update system ui whether to hold status bar open - */ - interface Callback { - void onHoldStatusBarOpenChange(); - } -} diff --git a/systemUIPlugin/src/com/android/systemui/plugins/PluginDependency.java b/systemUIPlugin/src/com/android/systemui/plugins/PluginDependency.java deleted file mode 100644 index 25ce3ddf81..0000000000 --- a/systemUIPlugin/src/com/android/systemui/plugins/PluginDependency.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * 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. - */ - -package com.android.systemui.plugins; - -import com.android.systemui.plugins.annotations.ProvidesInterface; - -@ProvidesInterface(version = PluginDependency.VERSION) -public class PluginDependency { - public static final int VERSION = 1; - static DependencyProvider sProvider; - - public static T get(Plugin p, Class cls) { - return sProvider.get(p, cls); - } - - static abstract class DependencyProvider { - abstract T get(Plugin p, Class cls); - } -} diff --git a/systemUIPlugin/src/com/android/systemui/plugins/PluginUtils.java b/systemUIPlugin/src/com/android/systemui/plugins/PluginUtils.java deleted file mode 100644 index af49d43c97..0000000000 --- a/systemUIPlugin/src/com/android/systemui/plugins/PluginUtils.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (C) 2016 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.plugins; - -import android.content.Context; -import android.view.LayoutInflater; -import android.view.View; - -public class PluginUtils { - - public static void setId(Context sysuiContext, View view, String id) { - int i = sysuiContext.getResources().getIdentifier(id, "id", sysuiContext.getPackageName()); - view.setId(i); - } -} diff --git a/systemUIPlugin/src/com/android/systemui/plugins/SensorManagerPlugin.java b/systemUIPlugin/src/com/android/systemui/plugins/SensorManagerPlugin.java deleted file mode 100644 index d62c1d411c..0000000000 --- a/systemUIPlugin/src/com/android/systemui/plugins/SensorManagerPlugin.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright (C) 2018 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.plugins; - -import android.hardware.SensorListener; - -import com.android.systemui.plugins.annotations.ProvidesInterface; - -/** - * Allows for additional sensors to be retrieved from - * {@link com.android.systemui.util.sensors.AsyncSensorManager}. - */ -@ProvidesInterface(action = SensorManagerPlugin.ACTION, version = SensorManagerPlugin.VERSION) -public interface SensorManagerPlugin extends Plugin { - String ACTION = "com.android.systemui.action.PLUGIN_SENSOR_MANAGER"; - int VERSION = 1; - - /** - * Registers for sensor events. Events will be sent until the listener is unregistered. - * @param sensor - * @param listener - * @see android.hardware.SensorManager#registerListener(SensorListener, int) - */ - void registerListener(Sensor sensor, SensorEventListener listener); - - /** - * Unregisters events from the sensor. - * @param sensor - * @param listener - */ - void unregisterListener(Sensor sensor, SensorEventListener listener); - - /** - * Listener triggered whenever the Sensor has new data. - */ - interface SensorEventListener { - void onSensorChanged(SensorEvent event); - } - - /** - * Sensor that can be defined in a plugin. - */ - class Sensor { - public static final int TYPE_WAKE_LOCK_SCREEN = 1; - public static final int TYPE_WAKE_DISPLAY = 2; - public static final int TYPE_SWIPE = 3; - public static final int TYPE_SKIP_STATUS = 4; - - private int mType; - - public Sensor(int type) { - mType = type; - } - public int getType() { - return mType; - } - public String toString() { - return "{PluginSensor type=\"" + mType + "\"}"; - } - } - - /** - * Event sent by a {@link Sensor}. - */ - class SensorEvent { - Sensor mSensor; - int mVendorType; - float[] mValues; - - /** - * Creates a sensor event. - * @param sensor The type of sensor, e.g. TYPE_WAKE_LOCK_SCREEN - * @param vendorType The vendor type, which should be unique for each type of sensor, - * e.g. SINGLE_TAP = 1, DOUBLE_TAP = 2, etc. - */ - public SensorEvent(Sensor sensor, int vendorType) { - this(sensor, vendorType, null); - } - - /** - * Creates a sensor event. - * @param sensor The type of sensor, e.g. TYPE_WAKE_LOCK_SCREEN - * @param vendorType The vendor type, which should be unique for each type of sensor, - * e.g. SINGLE_TAP = 1, DOUBLE_TAP = 2, etc. - * @param values Values captured by the sensor. - */ - public SensorEvent(Sensor sensor, int vendorType, float[] values) { - mSensor = sensor; - mVendorType = vendorType; - mValues = values; - } - - public Sensor getSensor() { - return mSensor; - } - - public float[] getValues() { - return mValues; - } - - public int getVendorType() { - return mVendorType; - } - } -} diff --git a/systemUIPlugin/src/com/android/systemui/plugins/ToastPlugin.java b/systemUIPlugin/src/com/android/systemui/plugins/ToastPlugin.java deleted file mode 100644 index da079cf044..0000000000 --- a/systemUIPlugin/src/com/android/systemui/plugins/ToastPlugin.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (C) 2020 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.plugins; - -import android.animation.Animator; -import android.annotation.NonNull; -import android.view.View; - -import com.android.systemui.plugins.annotations.ProvidesInterface; - -/** - * Customize toasts displayed by SystemUI (via Toast#makeText) - */ -@ProvidesInterface(action = ToastPlugin.ACTION, version = ToastPlugin.VERSION) -public interface ToastPlugin extends Plugin { - - String ACTION = "com.android.systemui.action.PLUGIN_TOAST"; - int VERSION = 1; - - /** - * Creates a CustomPluginToast. - */ - @NonNull Toast createToast(CharSequence text, String packageName, int userId); - - /** - * Custom Toast with the ability to change toast positioning, styling and animations. - */ - interface Toast { - /** - * Retrieve the Toast view's gravity. - * If no changes, returns null. - */ - default Integer getGravity() { - return null; - } - - /** - * Retrieve the Toast view's X-offset. - * If no changes, returns null. - */ - default Integer getXOffset() { - return null; - } - - /** - * Retrieve the Toast view's Y-offset. - * If no changes, returns null. - */ - default Integer getYOffset() { - return null; - } - - /** - * Retrieve the Toast view's horizontal margin. - * If no changes, returns null. - */ - default Integer getHorizontalMargin() { - return null; - } - - /** - * Retrieve the Toast view's vertical margin. - * If no changes, returns null. - */ - default Integer getVerticalMargin() { - return null; - } - - /** - * Retrieve the Toast view to show. - * If no changes, returns null. - */ - default View getView() { - return null; - } - - /** - * Retrieve the Toast's animate in. - * If no changes, returns null. - */ - default Animator getInAnimation() { - return null; - } - - /** - * Retrieve the Toast's animate out. - * If no changes, returns null. - */ - default Animator getOutAnimation() { - return null; - } - - /** - * Called on orientation changes. - */ - default void onOrientationChange(int orientation) { } - } -} diff --git a/systemUIPlugin/src/com/android/systemui/plugins/ViewProvider.java b/systemUIPlugin/src/com/android/systemui/plugins/ViewProvider.java deleted file mode 100644 index 18ca8e6e26..0000000000 --- a/systemUIPlugin/src/com/android/systemui/plugins/ViewProvider.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright (C) 2016 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.plugins; - -import android.view.View; - -public interface ViewProvider extends Plugin { - View getView(); -} diff --git a/systemUIPlugin/src/com/android/systemui/plugins/VolumeDialog.java b/systemUIPlugin/src/com/android/systemui/plugins/VolumeDialog.java deleted file mode 100644 index 9e5db73cf8..0000000000 --- a/systemUIPlugin/src/com/android/systemui/plugins/VolumeDialog.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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. - */ - -package com.android.systemui.plugins; - -import com.android.systemui.plugins.VolumeDialog.Callback; -import com.android.systemui.plugins.annotations.DependsOn; -import com.android.systemui.plugins.annotations.ProvidesInterface; - -/** - * This interface is really just a stub for initialization/teardown, actual handling of - * when to show will be done through {@link VolumeDialogController} - */ -@ProvidesInterface(action = VolumeDialog.ACTION, version = VolumeDialog.VERSION) -@DependsOn(target = Callback.class) -public interface VolumeDialog extends Plugin { - String ACTION = "com.android.systemui.action.PLUGIN_VOLUME"; - int VERSION = 1; - - void init(int windowType, Callback callback); - void destroy(); - - @ProvidesInterface(version = VERSION) - public interface Callback { - int VERSION = 1; - - void onZenSettingsClicked(); - void onZenPrioritySettingsClicked(); - } -} diff --git a/systemUIPlugin/src/com/android/systemui/plugins/VolumeDialogController.java b/systemUIPlugin/src/com/android/systemui/plugins/VolumeDialogController.java deleted file mode 100644 index 3d9645a3d9..0000000000 --- a/systemUIPlugin/src/com/android/systemui/plugins/VolumeDialogController.java +++ /dev/null @@ -1,231 +0,0 @@ -/* - * 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. - */ - -package com.android.systemui.plugins; - -import android.annotation.IntegerRes; -import android.content.ComponentName; -import android.media.AudioManager; -import android.media.AudioSystem; -import android.os.Handler; -import android.os.VibrationEffect; -import android.util.SparseArray; - -import com.android.systemui.plugins.VolumeDialogController.Callbacks; -import com.android.systemui.plugins.VolumeDialogController.State; -import com.android.systemui.plugins.VolumeDialogController.StreamState; -import com.android.systemui.plugins.annotations.DependsOn; -import com.android.systemui.plugins.annotations.ProvidesInterface; - -/** - * Manages the VolumeDialog. - * - * Accessible through {@link PluginDependency} - */ -@ProvidesInterface(version = VolumeDialogController.VERSION) -@DependsOn(target = StreamState.class) -@DependsOn(target = State.class) -@DependsOn(target = Callbacks.class) -public interface VolumeDialogController { - int VERSION = 1; - - void setActiveStream(int stream); - void setStreamVolume(int stream, int userLevel); - void setRingerMode(int ringerModeNormal, boolean external); - - boolean hasVibrator(); - void vibrate(VibrationEffect effect); - void scheduleTouchFeedback(); - - AudioManager getAudioManager(); - - void notifyVisible(boolean visible); - - void addCallback(Callbacks callbacks, Handler handler); - void removeCallback(Callbacks callbacks); - - void userActivity(); - void getState(); - - /** - * Get Captions enabled state - * - * @param checkForSwitchState set true when we'd like to switch captions enabled state after - * getting the latest captions state. - */ - void getCaptionsEnabledState(boolean checkForSwitchState); - - /** - * Set Captions enabled state - * - * @param enabled the captions enabled state we'd like to update. - */ - void setCaptionsEnabledState(boolean enabled); - - /** - * Get Captions component state - * - * @param fromTooltip if it's triggered from tooltip. - */ - void getCaptionsComponentState(boolean fromTooltip); - - @ProvidesInterface(version = StreamState.VERSION) - public static final class StreamState { - public static final int VERSION = 1; - - public boolean dynamic; - public int level; - public int levelMin; - public int levelMax; - public boolean muted; - public boolean muteSupported; - public @IntegerRes int name; - public String remoteLabel; - public boolean routedToBluetooth; - - public StreamState copy() { - final StreamState rt = new StreamState(); - rt.dynamic = dynamic; - rt.level = level; - rt.levelMin = levelMin; - rt.levelMax = levelMax; - rt.muted = muted; - rt.muteSupported = muteSupported; - rt.name = name; - rt.remoteLabel = remoteLabel; - rt.routedToBluetooth = routedToBluetooth; - return rt; - } - } - - @ProvidesInterface(version = State.VERSION) - public static final class State { - public static final int VERSION = 1; - - public static int NO_ACTIVE_STREAM = -1; - - public final SparseArray states = new SparseArray<>(); - - public int ringerModeInternal; - public int ringerModeExternal; - public int zenMode; - public ComponentName effectsSuppressor; - public String effectsSuppressorName; - public int activeStream = NO_ACTIVE_STREAM; - public boolean disallowAlarms; - public boolean disallowMedia; - public boolean disallowSystem; - public boolean disallowRinger; - - public State copy() { - final State rt = new State(); - for (int i = 0; i < states.size(); i++) { - rt.states.put(states.keyAt(i), states.valueAt(i).copy()); - } - rt.ringerModeExternal = ringerModeExternal; - rt.ringerModeInternal = ringerModeInternal; - rt.zenMode = zenMode; - if (effectsSuppressor != null) { - rt.effectsSuppressor = effectsSuppressor.clone(); - } - rt.effectsSuppressorName = effectsSuppressorName; - rt.activeStream = activeStream; - rt.disallowAlarms = disallowAlarms; - rt.disallowMedia = disallowMedia; - rt.disallowSystem = disallowSystem; - rt.disallowRinger = disallowRinger; - return rt; - } - - @Override - public String toString() { - return toString(0); - } - - public String toString(int indent) { - final StringBuilder sb = new StringBuilder("{"); - if (indent > 0) sep(sb, indent); - for (int i = 0; i < states.size(); i++) { - if (i > 0) { - sep(sb, indent); - } - final int stream = states.keyAt(i); - final StreamState ss = states.valueAt(i); - sb.append(AudioSystem.streamToString(stream)).append(":").append(ss.level) - .append('[').append(ss.levelMin).append("..").append(ss.levelMax) - .append(']'); - if (ss.muted) sb.append(" [MUTED]"); - if (ss.dynamic) sb.append(" [DYNAMIC]"); - } - sep(sb, indent); sb.append("ringerModeExternal:").append(ringerModeExternal); - sep(sb, indent); sb.append("ringerModeInternal:").append(ringerModeInternal); - sep(sb, indent); sb.append("zenMode:").append(zenMode); - sep(sb, indent); sb.append("effectsSuppressor:").append(effectsSuppressor); - sep(sb, indent); sb.append("effectsSuppressorName:").append(effectsSuppressorName); - sep(sb, indent); sb.append("activeStream:").append(activeStream); - sep(sb, indent); sb.append("disallowAlarms:").append(disallowAlarms); - sep(sb, indent); sb.append("disallowMedia:").append(disallowMedia); - sep(sb, indent); sb.append("disallowSystem:").append(disallowSystem); - sep(sb, indent); sb.append("disallowRinger:").append(disallowRinger); - if (indent > 0) sep(sb, indent); - return sb.append('}').toString(); - } - - private static void sep(StringBuilder sb, int indent) { - if (indent > 0) { - sb.append('\n'); - for (int i = 0; i < indent; i++) { - sb.append(' '); - } - } else { - sb.append(','); - } - } - } - - @ProvidesInterface(version = Callbacks.VERSION) - public interface Callbacks { - int VERSION = 2; - - // requires version 1 - void onShowRequested(int reason, boolean keyguardLocked, int lockTaskModeState); - void onDismissRequested(int reason); - void onStateChanged(State state); - void onLayoutDirectionChanged(int layoutDirection); - void onConfigurationChanged(); - void onShowVibrateHint(); - void onShowSilentHint(); - void onScreenOff(); - void onShowSafetyWarning(int flags); - void onAccessibilityModeChanged(Boolean showA11yStream); - - /** - * Callback function for captions component state changed event - * - * @param isComponentEnabled the lateset captions component state. - * @param fromTooltip if it's triggered from tooltip. - */ - void onCaptionComponentStateChanged(Boolean isComponentEnabled, Boolean fromTooltip); - - /** - * Callback function for captions enabled state changed event - * - * @param isEnabled the lateset captions enabled state. - * @param checkBeforeSwitch intend to switch captions enabled state after the callback. - */ - void onCaptionEnabledStateChanged(Boolean isEnabled, Boolean checkBeforeSwitch); - // requires version 2 - void onShowCsdWarning(@AudioManager.CsdWarning int csdWarning, int durationMs); - } -} diff --git a/systemUIPlugin/src/com/android/systemui/plugins/WeatherData.kt b/systemUIPlugin/src/com/android/systemui/plugins/WeatherData.kt deleted file mode 100644 index affb76b79d..0000000000 --- a/systemUIPlugin/src/com/android/systemui/plugins/WeatherData.kt +++ /dev/null @@ -1,124 +0,0 @@ -package com.android.systemui.plugins - -import android.os.Bundle -import android.util.Log -import android.view.View -import androidx.annotation.VisibleForTesting - -typealias WeatherTouchAction = (View) -> Unit - -class WeatherData -constructor( - val description: String, - val state: WeatherStateIcon, - val useCelsius: Boolean, - val temperature: Int, - val touchAction: WeatherTouchAction? = null, -) { - companion object { - const val DEBUG = true - private const val TAG = "WeatherData" - @VisibleForTesting const val DESCRIPTION_KEY = "description" - @VisibleForTesting const val STATE_KEY = "state" - @VisibleForTesting const val USE_CELSIUS_KEY = "use_celsius" - @VisibleForTesting const val TEMPERATURE_KEY = "temperature" - private const val INVALID_WEATHER_ICON_STATE = -1 - - fun fromBundle(extras: Bundle, touchAction: WeatherTouchAction? = null): WeatherData? { - val description = extras.getString(DESCRIPTION_KEY) - val state = - WeatherStateIcon.fromInt(extras.getInt(STATE_KEY, INVALID_WEATHER_ICON_STATE)) - val temperature = readIntFromBundle(extras, TEMPERATURE_KEY) - if ( - description == null || - state == null || - !extras.containsKey(USE_CELSIUS_KEY) || - temperature == null - ) { - if (DEBUG) { - Log.w(TAG, "Weather data did not parse from $extras") - } - return null - } else { - val result = - WeatherData( - description = description, - state = state, - useCelsius = extras.getBoolean(USE_CELSIUS_KEY), - temperature = temperature, - touchAction = touchAction - ) - if (DEBUG) { - Log.i(TAG, "Weather data parsed $result from $extras") - } - return result - } - } - - private fun readIntFromBundle(extras: Bundle, key: String): Int? = - try { - extras.getString(key)?.toInt() - } catch (e: Exception) { - null - } - } - - // Values for WeatherStateIcon must stay in sync with go/g3-WeatherStateIcon - enum class WeatherStateIcon(val id: Int) { - UNKNOWN_ICON(0), - - // Clear, day & night. - SUNNY(1), - CLEAR_NIGHT(2), - - // Mostly clear, day & night. - MOSTLY_SUNNY(3), - MOSTLY_CLEAR_NIGHT(4), - - // Partly cloudy, day & night. - PARTLY_CLOUDY(5), - PARTLY_CLOUDY_NIGHT(6), - - // Mostly cloudy, day & night. - MOSTLY_CLOUDY_DAY(7), - MOSTLY_CLOUDY_NIGHT(8), - CLOUDY(9), - HAZE_FOG_DUST_SMOKE(10), - DRIZZLE(11), - HEAVY_RAIN(12), - SHOWERS_RAIN(13), - - // Scattered showers, day & night. - SCATTERED_SHOWERS_DAY(14), - SCATTERED_SHOWERS_NIGHT(15), - - // Isolated scattered thunderstorms, day & night. - ISOLATED_SCATTERED_TSTORMS_DAY(16), - ISOLATED_SCATTERED_TSTORMS_NIGHT(17), - STRONG_TSTORMS(18), - BLIZZARD(19), - BLOWING_SNOW(20), - FLURRIES(21), - HEAVY_SNOW(22), - - // Scattered snow showers, day & night. - SCATTERED_SNOW_SHOWERS_DAY(23), - SCATTERED_SNOW_SHOWERS_NIGHT(24), - SNOW_SHOWERS_SNOW(25), - MIXED_RAIN_HAIL_RAIN_SLEET(26), - SLEET_HAIL(27), - TORNADO(28), - TROPICAL_STORM_HURRICANE(29), - WINDY_BREEZY(30), - WINTRY_MIX_RAIN_SNOW(31); - - companion object { - fun fromInt(value: Int) = values().firstOrNull { it.id == value } - } - } - - override fun toString(): String { - val unit = if (useCelsius) "C" else "F" - return "$state (\"$description\") $temperature°$unit" - } -} diff --git a/systemUIPlugin/src/com/android/systemui/plugins/log/TableLogBufferBase.kt b/systemUIPlugin/src/com/android/systemui/plugins/log/TableLogBufferBase.kt deleted file mode 100644 index 50b3f78a49..0000000000 --- a/systemUIPlugin/src/com/android/systemui/plugins/log/TableLogBufferBase.kt +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file - * except in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the - * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -package com.android.systemui.plugins.log - -/** - * Base interface for a logger that logs changes in table format. - * - * This is a plugin interface for classes outside of SystemUI core. - */ -interface TableLogBufferBase { - /** - * Logs a String? change. - * - * For Java overloading. - */ - fun logChange(prefix: String, columnName: String, value: String?) { - logChange(prefix, columnName, value, isInitial = false) - } - - /** Logs a String? change. */ - fun logChange(prefix: String, columnName: String, value: String?, isInitial: Boolean) - - /** - * Logs a Boolean change. - * - * For Java overloading. - */ - fun logChange(prefix: String, columnName: String, value: Boolean) { - logChange(prefix, columnName, value, isInitial = false) - } - - /** Logs a Boolean change. */ - fun logChange(prefix: String, columnName: String, value: Boolean, isInitial: Boolean) - - /** - * Logs an Int? change. - * - * For Java overloading. - */ - fun logChange(prefix: String, columnName: String, value: Int?) { - logChange(prefix, columnName, value, isInitial = false) - } - - /** Logs an Int? change. */ - fun logChange(prefix: String, columnName: String, value: Int?, isInitial: Boolean) -} diff --git a/systemUIPlugin/src/com/android/systemui/plugins/qs/QS.java b/systemUIPlugin/src/com/android/systemui/plugins/qs/QS.java deleted file mode 100644 index 3244eb43c8..0000000000 --- a/systemUIPlugin/src/com/android/systemui/plugins/qs/QS.java +++ /dev/null @@ -1,180 +0,0 @@ -/* - * Copyright (C) 2016 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.plugins.qs; - -import android.view.View; - -import androidx.annotation.FloatRange; - -import com.android.systemui.plugins.FragmentBase; -import com.android.systemui.plugins.annotations.DependsOn; -import com.android.systemui.plugins.annotations.ProvidesInterface; -import com.android.systemui.plugins.qs.QS.HeightListener; - -import java.util.function.Consumer; - -/** - * Fragment that contains QS in the notification shade. Most of the interface is for - * handling the expand/collapsing of the view interaction. - */ -@ProvidesInterface(action = QS.ACTION, version = QS.VERSION) -@DependsOn(target = HeightListener.class) -public interface QS extends FragmentBase { - - String ACTION = "com.android.systemui.action.PLUGIN_QS"; - - int VERSION = 15; - - String TAG = "QS"; - - void setPanelView(HeightListener notificationPanelView); - - void hideImmediately(); - int getQsMinExpansionHeight(); - int getDesiredHeight(); - void setHeightOverride(int desiredHeight); - void setHeaderClickable(boolean qsExpansionEnabled); - boolean isCustomizing(); - /** Close the QS customizer, if it is open. */ - void closeCustomizer(); - void setOverscrolling(boolean overscrolling); - void setExpanded(boolean qsExpanded); - void setListening(boolean listening); - - /** - * Set whether QQS/QS is visible or not. - * - * This is different from setExpanded, as it will be true when QQS is visible. In particular, - * it should be false when device is locked and only notifications (in lockscreen) are visible. - */ - void setQsVisible(boolean qsVisible); - boolean isShowingDetail(); - void closeDetail(); - void animateHeaderSlidingOut(); - - /** - * Asks QS to update its presentation, according to {@code NotificationPanelViewController}. - * @param qsExpansionFraction How much each UI element in QS should be expanded (QQS to QS.) - * @param panelExpansionFraction Whats the expansion of the whole shade. - * @param headerTranslation How much we should vertically translate QS. - * @param squishinessFraction Fraction that affects tile height. 0 when collapsed, - * 1 when expanded. - */ - void setQsExpansion(float qsExpansionFraction, float panelExpansionFraction, - float headerTranslation, float squishinessFraction); - void setHeaderListening(boolean listening); - void notifyCustomizeChanged(); - void setContainerController(QSContainerController controller); - - /** - * Provide an action to collapse if expanded or expand if collapsed. - * @param action - */ - void setCollapseExpandAction(Runnable action); - - /** - * Returns the height difference between the QSPanel container and the QuickQSPanel container - */ - int getHeightDiff(); - - View getHeader(); - - default void setHasNotifications(boolean hasNotifications) { - } - - /** - * Should touches from the notification panel be disallowed? - * The notification panel might grab any touches rom QS at any time to collapse the shade. - * We should disallow that in case we are showing the detail panel. - */ - default boolean disallowPanelTouches() { - return isShowingDetail(); - } - - /** - * If QS should translate as we pull it down, or if it should be static. - */ - void setInSplitShade(boolean shouldTranslate); - - /** - * Sets the progress of the transition to full shade on the lockscreen. - * - * @param isTransitioningToFullShade - * whether the transition to full shade is in progress. This might be {@code true}, even - * though {@code qsTransitionFraction} is still 0. - * The reason for that is that on some device configurations, the QS transition has a - * start delay compared to the overall transition. - * - * @param qsTransitionFraction - * the fraction of the QS transition progress, from 0 to 1. - * - * @param qsSquishinessFraction - * the fraction of the QS "squish" transition progress, from 0 to 1. - */ - default void setTransitionToFullShadeProgress( - boolean isTransitioningToFullShade, - @FloatRange(from = 0.0, to = 1.0) float qsTransitionFraction, - @FloatRange(from = 0.0, to = 1.0) float qsSquishinessFraction) {} - - /** - * A rounded corner clipping that makes QS feel as if it were behind everything. - */ - void setFancyClipping(int leftInset, int top, int rightInset, int bottom, int cornerRadius, - boolean visible, boolean fullWidth); - - /** - * @return if quick settings is fully collapsed currently - */ - default boolean isFullyCollapsed() { - return true; - } - - /** - * Add a listener for when the collapsed media visibility changes. - */ - void setCollapsedMediaVisibilityChangedListener(Consumer listener); - - /** - * Set a scroll listener for the QSPanel container - */ - default void setScrollListener(ScrollListener scrollListener) {} - - /** - * Sets the amount of vertical over scroll that should be performed on QS. - */ - default void setOverScrollAmount(int overScrollAmount) {} - - /** - * Sets whether the notification panel is using the full width of the screen. Typically true on - * small screens and false on large screens. - */ - void setIsNotificationPanelFullWidth(boolean isFullWidth); - - /** - * Callback for when QSPanel container is scrolled - */ - @ProvidesInterface(version = ScrollListener.VERSION) - interface ScrollListener { - int VERSION = 1; - void onQsPanelScrollChanged(int scrollY); - } - - @ProvidesInterface(version = HeightListener.VERSION) - interface HeightListener { - int VERSION = 1; - void onQsHeightChanged(); - } - -} diff --git a/systemUIPlugin/src/com/android/systemui/plugins/qs/QSContainerController.kt b/systemUIPlugin/src/com/android/systemui/plugins/qs/QSContainerController.kt deleted file mode 100644 index 9c7fbe8842..0000000000 --- a/systemUIPlugin/src/com/android/systemui/plugins/qs/QSContainerController.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.android.systemui.plugins.qs - -interface QSContainerController { - fun setCustomizerAnimating(animating: Boolean) - - fun setCustomizerShowing(showing: Boolean) = setCustomizerShowing(showing, 0L) - - fun setCustomizerShowing(showing: Boolean, animationDuration: Long) - - fun setDetailShowing(showing: Boolean) -} diff --git a/systemUIPlugin/src/com/android/systemui/plugins/qs/QSFactory.java b/systemUIPlugin/src/com/android/systemui/plugins/qs/QSFactory.java deleted file mode 100644 index cfe3be0acc..0000000000 --- a/systemUIPlugin/src/com/android/systemui/plugins/qs/QSFactory.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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. - */ - -package com.android.systemui.plugins.qs; - -import android.content.Context; - -import com.android.systemui.plugins.Plugin; -import com.android.systemui.plugins.annotations.DependsOn; -import com.android.systemui.plugins.annotations.ProvidesInterface; - -/** - * Plugin that has the ability to create or override any part of - * QS tiles. - */ -@ProvidesInterface(action = QSFactory.ACTION, version = QSFactory.VERSION) -@DependsOn(target = QSTile.class) -@DependsOn(target = QSTileView.class) -public interface QSFactory extends Plugin { - - String ACTION = "com.android.systemui.action.PLUGIN_QS_FACTORY"; - int VERSION = 2; - - QSTile createTile(String tileSpec); - - /** - * Create a view for a tile. - * - * @param context a themed context for inflating the view - * @param tile the tile for which the view is created - * @param collapsedView {@code true} if the view will live in QQS and {@code false} otherwise. - * @return a view for the tile - */ - QSTileView createTileView(Context context, QSTile tile, boolean collapsedView); - -} diff --git a/systemUIPlugin/src/com/android/systemui/plugins/qs/QSIconView.java b/systemUIPlugin/src/com/android/systemui/plugins/qs/QSIconView.java deleted file mode 100644 index 0cdb509a52..0000000000 --- a/systemUIPlugin/src/com/android/systemui/plugins/qs/QSIconView.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * 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. - */ - -package com.android.systemui.plugins.qs; - -import android.content.Context; -import android.view.View; -import android.view.ViewGroup; - -import com.android.systemui.plugins.annotations.ProvidesInterface; -import com.android.systemui.plugins.qs.QSTile.State; - -@ProvidesInterface(version = QSIconView.VERSION) -public abstract class QSIconView extends ViewGroup { - public static final int VERSION = 1; - - public QSIconView(Context context) { - super(context); - } - - public abstract void setIcon(State state, boolean allowAnimations); - public abstract void disableAnimation(); - public abstract View getIconView(); -} diff --git a/systemUIPlugin/src/com/android/systemui/plugins/qs/QSTile.java b/systemUIPlugin/src/com/android/systemui/plugins/qs/QSTile.java deleted file mode 100644 index 25f77ea4e6..0000000000 --- a/systemUIPlugin/src/com/android/systemui/plugins/qs/QSTile.java +++ /dev/null @@ -1,374 +0,0 @@ -/* - * 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. - */ - -package com.android.systemui.plugins.qs; - -import android.annotation.NonNull; -import android.content.Context; -import android.content.res.Resources; -import android.graphics.drawable.Drawable; -import android.metrics.LogMaker; -import android.service.quicksettings.Tile; -import android.text.TextUtils; -import android.view.View; - -import androidx.annotation.Nullable; - -import com.android.internal.logging.InstanceId; -import com.android.systemui.plugins.annotations.DependsOn; -import com.android.systemui.plugins.annotations.ProvidesInterface; -import com.android.systemui.plugins.qs.QSTile.Callback; -import com.android.systemui.plugins.qs.QSTile.Icon; -import com.android.systemui.plugins.qs.QSTile.State; - -import java.util.Objects; -import java.util.function.Supplier; - -@ProvidesInterface(version = QSTile.VERSION) -@DependsOn(target = QSIconView.class) -@DependsOn(target = Callback.class) -@DependsOn(target = Icon.class) -@DependsOn(target = State.class) -public interface QSTile { - int VERSION = 4; - - String getTileSpec(); - - boolean isAvailable(); - void setTileSpec(String tileSpec); - - @Deprecated default void clearState() {} - void refreshState(); - - void addCallback(Callback callback); - void removeCallback(Callback callback); - void removeCallbacks(); - - QSIconView createTileView(Context context); - - /** - * The tile was clicked. - * - * @param view The view that was clicked. - */ - void click(@Nullable View view); - - /** - * The tile secondary click was triggered. - * - * @param view The view that was clicked. - */ - void secondaryClick(@Nullable View view); - - /** - * The tile was long clicked. - * - * @param view The view that was clicked. - */ - void longClick(@Nullable View view); - - void userSwitch(int currentUser); - - /** - * @deprecated not needed as {@link com.android.internal.logging.UiEvent} will use - * {@link #getMetricsSpec} - */ - @Deprecated - int getMetricsCategory(); - - void setListening(Object client, boolean listening); - void setDetailListening(boolean show); - - void destroy(); - - CharSequence getTileLabel(); - - State getState(); - - default LogMaker populate(LogMaker logMaker) { - return logMaker; - } - - /** - * Return a string to be used to identify the tile in UiEvents. - */ - default String getMetricsSpec() { - return getClass().getSimpleName(); - } - - /** - * Return an {@link InstanceId} to be used to identify the tile in UiEvents. - */ - InstanceId getInstanceId(); - - default boolean isTileReady() { - return false; - } - - /** - * Return whether the tile is set to its listening state and therefore receiving updates and - * refreshes from controllers - */ - boolean isListening(); - - @ProvidesInterface(version = Callback.VERSION) - interface Callback { - static final int VERSION = 2; - void onStateChanged(State state); - } - - @ProvidesInterface(version = Icon.VERSION) - public static abstract class Icon { - public static final int VERSION = 1; - abstract public Drawable getDrawable(Context context); - - public Drawable getInvisibleDrawable(Context context) { - return getDrawable(context); - } - - @Override - public int hashCode() { - return Icon.class.hashCode(); - } - - public int getPadding() { - return 0; - } - - @Override - @NonNull - public String toString() { - return "Icon"; - } - } - - @ProvidesInterface(version = State.VERSION) - public static class State { - public static final int VERSION = 1; - public static final int DEFAULT_STATE = Tile.STATE_ACTIVE; - - public Icon icon; - public Supplier iconSupplier; - public int state = DEFAULT_STATE; - public CharSequence label; - @Nullable public CharSequence secondaryLabel; - public CharSequence contentDescription; - @Nullable public CharSequence stateDescription; - public CharSequence dualLabelContentDescription; - public boolean disabledByPolicy; - public boolean dualTarget = false; - public boolean isTransient = false; - public String expandedAccessibilityClassName; - public SlashState slash; - public boolean handlesLongClick = true; - @Nullable - public Drawable sideViewCustomDrawable; - public String spec; - - /** Get the state text. */ - public String getStateText(int arrayResId, Resources resources) { - if (state == Tile.STATE_UNAVAILABLE || this instanceof QSTile.BooleanState) { - String[] array = resources.getStringArray(arrayResId); - return array[state]; - } else { - return ""; - } - } - - /** Get the text for secondaryLabel. */ - public String getSecondaryLabel(String stateText) { - // Use a local reference as the value might change from other threads - CharSequence localSecondaryLabel = secondaryLabel; - if (TextUtils.isEmpty(localSecondaryLabel)) { - return stateText; - } - return localSecondaryLabel.toString(); - } - - public boolean copyTo(State other) { - if (other == null) throw new IllegalArgumentException(); - if (!other.getClass().equals(getClass())) throw new IllegalArgumentException(); - final boolean changed = !Objects.equals(other.spec, spec) - || !Objects.equals(other.icon, icon) - || !Objects.equals(other.iconSupplier, iconSupplier) - || !Objects.equals(other.label, label) - || !Objects.equals(other.secondaryLabel, secondaryLabel) - || !Objects.equals(other.contentDescription, contentDescription) - || !Objects.equals(other.stateDescription, stateDescription) - || !Objects.equals(other.dualLabelContentDescription, - dualLabelContentDescription) - || !Objects.equals(other.expandedAccessibilityClassName, - expandedAccessibilityClassName) - || !Objects.equals(other.disabledByPolicy, disabledByPolicy) - || !Objects.equals(other.state, state) - || !Objects.equals(other.isTransient, isTransient) - || !Objects.equals(other.dualTarget, dualTarget) - || !Objects.equals(other.slash, slash) - || !Objects.equals(other.handlesLongClick, handlesLongClick) - || !Objects.equals(other.sideViewCustomDrawable, sideViewCustomDrawable); - other.spec = spec; - other.icon = icon; - other.iconSupplier = iconSupplier; - other.label = label; - other.secondaryLabel = secondaryLabel; - other.contentDescription = contentDescription; - other.stateDescription = stateDescription; - other.dualLabelContentDescription = dualLabelContentDescription; - other.expandedAccessibilityClassName = expandedAccessibilityClassName; - other.disabledByPolicy = disabledByPolicy; - other.state = state; - other.dualTarget = dualTarget; - other.isTransient = isTransient; - other.slash = slash != null ? slash.copy() : null; - other.handlesLongClick = handlesLongClick; - other.sideViewCustomDrawable = sideViewCustomDrawable; - return changed; - } - - @Override - public String toString() { - return toStringBuilder().toString(); - } - - // Used in dumps to determine current state of a tile. - // This string may be used for CTS testing of tiles, so removing elements is discouraged. - protected StringBuilder toStringBuilder() { - final StringBuilder sb = new StringBuilder(getClass().getSimpleName()).append('['); - sb.append("spec=").append(spec); - sb.append(",icon=").append(icon); - sb.append(",iconSupplier=").append(iconSupplier); - sb.append(",label=").append(label); - sb.append(",secondaryLabel=").append(secondaryLabel); - sb.append(",contentDescription=").append(contentDescription); - sb.append(",stateDescription=").append(stateDescription); - sb.append(",dualLabelContentDescription=").append(dualLabelContentDescription); - sb.append(",expandedAccessibilityClassName=").append(expandedAccessibilityClassName); - sb.append(",disabledByPolicy=").append(disabledByPolicy); - sb.append(",dualTarget=").append(dualTarget); - sb.append(",isTransient=").append(isTransient); - sb.append(",state=").append(state); - sb.append(",slash=\"").append(slash).append("\""); - sb.append(",sideViewCustomDrawable=").append(sideViewCustomDrawable); - return sb.append(']'); - } - - public State copy() { - State state = new State(); - copyTo(state); - return state; - } - } - - @ProvidesInterface(version = BooleanState.VERSION) - public static class BooleanState extends State { - public static final int VERSION = 1; - public boolean value; - public boolean forceExpandIcon; - - @Override - public boolean copyTo(State other) { - final BooleanState o = (BooleanState) other; - final boolean changed = super.copyTo(other) - || o.value != value - || o.forceExpandIcon != forceExpandIcon; - o.value = value; - o.forceExpandIcon = forceExpandIcon; - return changed; - } - - @Override - protected StringBuilder toStringBuilder() { - final StringBuilder rt = super.toStringBuilder(); - rt.insert(rt.length() - 1, ",value=" + value); - rt.insert(rt.length() - 1, ",forceExpandIcon=" + forceExpandIcon); - return rt; - } - - @Override - public State copy() { - BooleanState state = new BooleanState(); - copyTo(state); - return state; - } - } - - @ProvidesInterface(version = SignalState.VERSION) - public static final class SignalState extends BooleanState { - public static final int VERSION = 1; - public boolean activityIn; - public boolean activityOut; - public boolean isOverlayIconWide; - public int overlayIconId; - - @Override - public boolean copyTo(State other) { - final SignalState o = (SignalState) other; - final boolean changed = o.activityIn != activityIn - || o.activityOut != activityOut - || o.isOverlayIconWide != isOverlayIconWide - || o.overlayIconId != overlayIconId; - o.activityIn = activityIn; - o.activityOut = activityOut; - o.isOverlayIconWide = isOverlayIconWide; - o.overlayIconId = overlayIconId; - return super.copyTo(other) || changed; - } - - @Override - protected StringBuilder toStringBuilder() { - final StringBuilder rt = super.toStringBuilder(); - rt.insert(rt.length() - 1, ",activityIn=" + activityIn); - rt.insert(rt.length() - 1, ",activityOut=" + activityOut); - return rt; - } - - @Override - public State copy() { - SignalState state = new SignalState(); - copyTo(state); - return state; - } - } - - @ProvidesInterface(version = SlashState.VERSION) - public static class SlashState { - public static final int VERSION = 2; - - public boolean isSlashed; - public float rotation; - - @Override - public String toString() { - return "isSlashed=" + isSlashed + ",rotation=" + rotation; - } - - @Override - public boolean equals(Object o) { - if (o == null) return false; - try { - return (((SlashState) o).rotation == rotation) - && (((SlashState) o).isSlashed == isSlashed); - } catch (ClassCastException e) { - return false; - } - } - - public SlashState copy() { - SlashState state = new SlashState(); - state.rotation = rotation; - state.isSlashed = isSlashed; - return state; - } - } -} diff --git a/systemUIPlugin/src/com/android/systemui/plugins/qs/QSTileView.java b/systemUIPlugin/src/com/android/systemui/plugins/qs/QSTileView.java deleted file mode 100644 index a8999ff31f..0000000000 --- a/systemUIPlugin/src/com/android/systemui/plugins/qs/QSTileView.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * 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. - */ - -package com.android.systemui.plugins.qs; - -import android.content.Context; -import android.view.View; -import android.widget.LinearLayout; - -import com.android.systemui.plugins.annotations.DependsOn; -import com.android.systemui.plugins.annotations.ProvidesInterface; -import com.android.systemui.plugins.qs.QSTile.State; - -@ProvidesInterface(version = QSTileView.VERSION) -@DependsOn(target = QSIconView.class) -@DependsOn(target = QSTile.class) -public abstract class QSTileView extends LinearLayout { - public static final int VERSION = 3; - - public QSTileView(Context context) { - super(context); - } - - public abstract View updateAccessibilityOrder(View previousView); - - /** - * Returns a {@link QSIconView} containing only the icon for this tile. Use - * {@link #getIconWithBackground()} to retrieve the entire tile (background & peripherals - * included). - */ - public abstract QSIconView getIcon(); - - /** - * Returns a {@link View} containing the icon for this tile along with the accompanying - * background circle/peripherals. To retrieve only the inner icon, use {@link #getIcon()}. - */ - public abstract View getIconWithBackground(); - - /** - * Returns the {@link View} containing the icon on the right - * - * @see com.android.systemui.qs.tileimpl.QSTileViewHorizontal#sideView - */ - public View getSecondaryIcon() { - return null; - } - public abstract void init(QSTile tile); - public abstract void onStateChanged(State state); - - public abstract int getDetailY(); - - public View getLabel() { - return null; - } - - public View getLabelContainer() { - return null; - } - - public View getSecondaryLabel() { - return null; - } - - /** Sets the index of this tile in its layout */ - public abstract void setPosition(int position); -} diff --git a/systemUIPlugin/src/com/android/systemui/plugins/statusbar/DozeParameters.java b/systemUIPlugin/src/com/android/systemui/plugins/statusbar/DozeParameters.java deleted file mode 100644 index 678eb31304..0000000000 --- a/systemUIPlugin/src/com/android/systemui/plugins/statusbar/DozeParameters.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2019 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.plugins.statusbar; - -import com.android.systemui.plugins.annotations.ProvidesInterface; - -/** - * Retrieve doze information - */ -@ProvidesInterface(version = DozeParameters.VERSION) -public interface DozeParameters { - int VERSION = 1; - - /** - * Whether to doze when the screen turns off - */ - boolean shouldControlScreenOff(); -} diff --git a/systemUIPlugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java b/systemUIPlugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java deleted file mode 100644 index 94fdbae832..0000000000 --- a/systemUIPlugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java +++ /dev/null @@ -1,255 +0,0 @@ -/* - * 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. - */ - -package com.android.systemui.plugins.statusbar; - -import android.annotation.Nullable; -import android.content.Context; -import android.graphics.Point; -import android.service.notification.StatusBarNotification; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; - -import com.android.systemui.plugins.Plugin; -import com.android.systemui.plugins.annotations.DependsOn; -import com.android.systemui.plugins.annotations.ProvidesInterface; -import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem; -import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.OnMenuEventListener; -import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption; - -import java.util.ArrayList; - -@ProvidesInterface(action = NotificationMenuRowPlugin.ACTION, - version = NotificationMenuRowPlugin.VERSION) -@DependsOn(target = OnMenuEventListener.class) -@DependsOn(target = MenuItem.class) -@DependsOn(target = NotificationSwipeActionHelper.class) -@DependsOn(target = SnoozeOption.class) -public interface NotificationMenuRowPlugin extends Plugin { - - public static final String ACTION = "com.android.systemui.action.PLUGIN_NOTIFICATION_MENU_ROW"; - public static final int VERSION = 5; - - @ProvidesInterface(version = OnMenuEventListener.VERSION) - public interface OnMenuEventListener { - public static final int VERSION = 1; - - public void onMenuClicked(View row, int x, int y, MenuItem menu); - - public void onMenuReset(View row); - - public void onMenuShown(View row); - } - - @ProvidesInterface(version = MenuItem.VERSION) - public interface MenuItem { - public static final int VERSION = 1; - - public View getMenuView(); - - public View getGutsView(); - - public String getContentDescription(); - } - - /** - * @return a list of items to populate the menu 'behind' a notification. - */ - public ArrayList getMenuItems(Context context); - - /** - * @return the {@link MenuItem} to display when a notification is long pressed. - */ - public MenuItem getLongpressMenuItem(Context context); - - /** - * @return the {@link MenuItem} to display when feedback icon is pressed. - */ - public MenuItem getFeedbackMenuItem(Context context); - - /** - * @return the {@link MenuItem} to display when snooze item is pressed. - */ - public MenuItem getSnoozeMenuItem(Context context); - - public void setMenuItems(ArrayList items); - - /** - * If this returns {@code true}, then the menu row will bind and fade in the notification guts - * view for the menu item it holds. - * - * @see #menuItemToExposeOnSnap() - * @return whether or not to immediately expose the notification guts - */ - default boolean shouldShowGutsOnSnapOpen() { - return false; - } - - /** - * When #shouldShowGutsOnExpose is true, this method must return the menu item to expose on - * #onSnapOpen. Otherwise we will fall back to the default behavior of fading in the menu row - * - * @return the {@link MenuItem} containing the NotificationGuts that should be exposed - */ - @Nullable - default MenuItem menuItemToExposeOnSnap() { - return null; - } - - /** - * Get the origin for the circular reveal animation when expanding the notification guts. Only - * used when #shouldShowGutsOnSnapOpen is true - * @return the x,y coordinates for the start of the animation - */ - @Nullable - default Point getRevealAnimationOrigin() { - return new Point(0, 0); - } - - public void setMenuClickListener(OnMenuEventListener listener); - - public void setAppName(String appName); - - public void createMenu(ViewGroup parent, StatusBarNotification sbn); - - public void resetMenu(); - - public View getMenuView(); - - /** - * Get the target position that a notification row should be snapped open to in order to reveal - * the menu. This is generally determined by the number of icons in the notification menu and the - * size of each icon. This method accounts for whether the menu appears on the left or ride side - * of the parent notification row. - * - - * @return an int representing the x-offset in pixels that the notification should snap open to. - * Positive values imply that the notification should be offset to the right to reveal the menu, - * and negative alues imply that the notification should be offset to the right. - */ - public int getMenuSnapTarget(); - - /** - * Determines whether or not the menu should be shown in response to user input. - * @return true if the menu should be shown, false otherwise. - */ - public boolean shouldShowMenu(); - - /** - * Determines whether the menu is currently visible. - * @return true if the menu is visible, false otherwise. - */ - public boolean isMenuVisible(); - - /** - * Determines whether a given movement is towards or away from the current location of the menu. - * @param movement - * @return true if the movement is towards the menu, false otherwise. - */ - public boolean isTowardsMenu(float movement); - - /** - * Determines whether the menu should snap closed instead of dismissing the - * parent notification, as a function of its current state. - * - * @return true if the menu should snap closed, false otherwise. - */ - public boolean shouldSnapBack(); - - /** - * Determines whether the menu was previously snapped open to the same side that it is currently - * being shown on. - * @return true if the menu is snapped open to the same side on which it currently appears, - * false otherwise. - */ - public boolean isSnappedAndOnSameSide(); - - /** - * Determines whether the notification the menu is attached to is able to be dismissed. - * @return true if the menu's parent notification is dismissable, false otherwise. - */ - public boolean canBeDismissed(); - - /** - * Determines whether the menu should remain open given its current state, or snap closed. - * @return true if the menu should remain open, false otherwise. - */ - public boolean isWithinSnapMenuThreshold(); - - /** - * Determines whether the menu has been swiped far enough to snap open. - * @return true if the menu has been swiped far enough to open, false otherwise. - */ - public boolean isSwipedEnoughToShowMenu(); - - public default boolean onInterceptTouchEvent(View view, MotionEvent ev) { - return false; - } - - public default boolean shouldUseDefaultMenuItems() { - return false; - } - - /** - * Callback used to signal the menu that its parent's translation has changed. - * @param translation The new x-translation of the menu as a position (not an offset). - */ - public void onParentTranslationUpdate(float translation); - - /** - * Callback used to signal the menu that its parent's height has changed. - */ - public void onParentHeightUpdate(); - - /** - * Callback used to signal the menu that its parent notification has been updated. - * @param sbn - */ - public void onNotificationUpdated(StatusBarNotification sbn); - - /** - * Callback used to signal the menu that a user is moving the parent notification. - * @param delta The change in the parent notification's position. - */ - public void onTouchMove(float delta); - - /** - * Callback used to signal the menu that a user has begun touching its parent notification. - */ - public void onTouchStart(); - - /** - * Callback used to signal the menu that a user has finished touching its parent notification. - */ - public void onTouchEnd(); - - /** - * Callback used to signal the menu that it has been snapped closed. - */ - public void onSnapClosed(); - - /** - * Callback used to signal the menu that it has been snapped open. - */ - public void onSnapOpen(); - - /** - * Callback used to signal the menu that its parent notification has been dismissed. - */ - public void onDismiss(); - - public default void onConfigurationChanged() { } - -} diff --git a/systemUIPlugin/src/com/android/systemui/plugins/statusbar/NotificationSwipeActionHelper.java b/systemUIPlugin/src/com/android/systemui/plugins/statusbar/NotificationSwipeActionHelper.java deleted file mode 100644 index 59911b233e..0000000000 --- a/systemUIPlugin/src/com/android/systemui/plugins/statusbar/NotificationSwipeActionHelper.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * 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. - */ - -package com.android.systemui.plugins.statusbar; - -import android.service.notification.SnoozeCriterion; -import android.service.notification.StatusBarNotification; -import android.view.MotionEvent; -import android.view.View; -import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; - -import com.android.systemui.plugins.annotations.DependsOn; -import com.android.systemui.plugins.annotations.ProvidesInterface; -import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption; - -@ProvidesInterface(version = NotificationSwipeActionHelper.VERSION) -@DependsOn(target = SnoozeOption.class) -public interface NotificationSwipeActionHelper { - public static final String ACTION = "com.android.systemui.action.PLUGIN_NOTIFICATION_SWIPE_ACTION"; - - public static final int VERSION = 1; - - /** - * Call this to dismiss a notification. - */ - public void dismiss(View animView, float velocity); - - /** - * Call this to snap a notification to provided {@code targetLeft}. - */ - public void snapOpen(View animView, int targetLeft, float velocity); - - /** - * Call this to snooze a notification based on the provided {@link SnoozeOption}. - */ - public void snooze(StatusBarNotification sbn, SnoozeOption snoozeOption); - - public float getMinDismissVelocity(); - - public boolean isDismissGesture(MotionEvent ev); - - /** Returns true if the gesture should be rejected. */ - boolean isFalseGesture(); - - @ProvidesInterface(version = SnoozeOption.VERSION) - public interface SnoozeOption { - public static final int VERSION = 2; - - public SnoozeCriterion getSnoozeCriterion(); - - public CharSequence getDescription(); - - public CharSequence getConfirmation(); - - public int getMinutesToSnoozeFor(); - - public AccessibilityAction getAccessibilityAction(); - } -} diff --git a/systemUIPlugin/src/com/android/systemui/plugins/statusbar/StatusBarStateController.java b/systemUIPlugin/src/com/android/systemui/plugins/statusbar/StatusBarStateController.java deleted file mode 100644 index b7088d5d3d..0000000000 --- a/systemUIPlugin/src/com/android/systemui/plugins/statusbar/StatusBarStateController.java +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright (C) 2019 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.plugins.statusbar; - -import com.android.systemui.plugins.annotations.DependsOn; -import com.android.systemui.plugins.annotations.ProvidesInterface; - - -/** - * Sends updates to {@link StateListener}s about changes to the status bar state and dozing state - */ -@ProvidesInterface(version = StatusBarStateController.VERSION) -@DependsOn(target = StatusBarStateController.StateListener.class) -public interface StatusBarStateController { - int VERSION = 1; - - /** - * Current status bar state - */ - int getState(); - - /** - * Is device dozing. Dozing is when the screen is in AOD or asleep given that - * {@link com.android.systemui.doze.DozeService} is configured. - */ - boolean isDozing(); - - /** - * Is the status bar panel expanded. - */ - boolean isExpanded(); - - /** - * Is device pulsing. - */ - boolean isPulsing(); - - /** - * Is device dreaming. This method is more inclusive than - * {@link android.service.dreams.IDreamManager.isDreaming}, as it will return true during the - * dream's wake-up phase. - */ - boolean isDreaming(); - - /** - * Adds a state listener - */ - void addCallback(StateListener listener); - - /** - * Removes callback from listeners - */ - void removeCallback(StateListener listener); - - /** - * Get amount of doze - */ - float getDozeAmount(); - - /** - * Listener for StatusBarState updates - */ - @ProvidesInterface(version = StateListener.VERSION) - public interface StateListener { - int VERSION = 1; - - /** - * Callback before the new state is applied, for those who need to preempt the change. - */ - default void onStatePreChange(int oldState, int newState) { - } - - /** - * Callback after all listeners have had a chance to update based on the state change - */ - default void onStatePostChange() { - } - - /** - * Required callback. Get the new state and do what you will with it. Keep in mind that - * other listeners are typically unordered and don't rely on your work being done before - * other peers. - * - * Only called if the state is actually different. - */ - default void onStateChanged(int newState) { - } - - /** - * Callback to be notified about upcoming state changes. Typically, is immediately followed - * by #onStateChanged, unless there was an intentional delay in updating the state changed. - */ - default void onUpcomingStateChanged(int upcomingState) {} - - /** - * Callback to be notified when Dozing changes. Dozing is stored separately from state. - */ - default void onDozingChanged(boolean isDozing) {} - - /** - * Callback to be notified when Dreaming changes. Dreaming is stored separately from state. - */ - default void onDreamingChanged(boolean isDreaming) {} - - /** - * Callback to be notified when the doze amount changes. Useful for animations. - * Note: this will be called for each animation frame. Please be careful to avoid - * performance regressions. - */ - default void onDozeAmountChanged(float linear, float eased) {} - - /** - * Callback to be notified when the fullscreen or immersive state changes. - * - * @param isFullscreen if any of the system bar is hidden by the focused window. - */ - default void onFullscreenStateChanged(boolean isFullscreen) {} - - /** - * Callback to be notified when the pulsing state changes - */ - default void onPulsingChanged(boolean pulsing) {} - - /** - * Callback to be notified when the expanded state of the status bar changes - */ - default void onExpandedChanged(boolean isExpanded) {} - } -} diff --git a/systemUIPlugin/update_plugin_lib.sh b/systemUIPlugin/update_plugin_lib.sh deleted file mode 100644 index 34f4895de3..0000000000 --- a/systemUIPlugin/update_plugin_lib.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash -cd $ANDROID_BUILD_TOP/frameworks/base/packages/SystemUI/plugin -# Clear out anything old. -rm -rf /tmp/plugin_classes/ -mkdir /tmp/plugin_classes - -# Compile the jar -javac -cp $ANDROID_BUILD_TOP/out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/classes.jar:$ANDROID_BUILD_TOP/out/target/common/obj/JAVA_LIBRARIES/core-all_intermediates/classes.jar `find ../plugin*/src -name *.java` -d /tmp/plugin_classes/ -echo "" >> /tmp/plugin_classes/manifest.txt -jar cvfm SystemUIPluginLib.jar /tmp/plugin_classes/manifest.txt -C /tmp/plugin_classes . - -# Place the jar and update the latest -mv SystemUIPluginLib.jar ./SystemUIPluginLib-`date +%m-%d-%Y`.jar -rm SystemUIPluginLib-latest.jar -ln -s SystemUIPluginLib-`date +%m-%d-%Y`.jar SystemUIPluginLib-latest.jar diff --git a/systemUIPluginCore/Android.bp b/systemUIPluginCore/Android.bp deleted file mode 100644 index 4e39f1ac95..0000000000 --- a/systemUIPluginCore/Android.bp +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (C) 2018 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"], -} - -java_library { - sdk_version: "current", - name: "PluginCoreLib", - srcs: ["src/**/*.java"], - optimize: { - proguard_flags_files: ["proguard.flags"], - }, - - // Enforce that the library is built against java 8 so that there are - // no compatibility issues with launcher - java_version: "1.8", -} diff --git a/systemUIPluginCore/AndroidManifest.xml b/systemUIPluginCore/AndroidManifest.xml deleted file mode 100644 index df835fd8e3..0000000000 --- a/systemUIPluginCore/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - diff --git a/systemUIPluginCore/build.gradle b/systemUIPluginCore/build.gradle deleted file mode 100644 index 06ca335af0..0000000000 --- a/systemUIPluginCore/build.gradle +++ /dev/null @@ -1,21 +0,0 @@ -plugins { - id 'com.android.library' - id 'org.jetbrains.kotlin.android' -} - -android { - namespace "com.android.systemui.plugin_core" - buildFeatures { - aidl true - } - sourceSets { - main { - java.srcDirs = ['src'] - aidl.srcDirs = ['src'] - manifest.srcFile 'AndroidManifest.xml' - } - } -} - -addFrameworkJar('framework-15.jar') -compileOnlyCommonJars() diff --git a/systemUIPluginCore/proguard.flags b/systemUIPluginCore/proguard.flags deleted file mode 100644 index 6240898b3b..0000000000 --- a/systemUIPluginCore/proguard.flags +++ /dev/null @@ -1,11 +0,0 @@ -# R8's full mode is a bit more aggressive in stripping annotations, but the -# SystemUI plugin architecture requires these annotations at runtime. The -# following rules are the minimal set necessary to ensure compatibility. -# For more details, see: -# https://r8.googlesource.com/r8/+/refs/heads/master/compatibility-faq.md#r8-full-mode --keepattributes RuntimeVisible*Annotation*,AnnotationDefault - --keep interface com.android.systemui.plugins.annotations.** { - *; -} --keep,allowshrinking,allowoptimization,allowobfuscation,allowaccessmodification @com.android.systemui.plugins.annotations.** class * diff --git a/systemUIPluginCore/src/com/android/systemui/plugins/Plugin.java b/systemUIPluginCore/src/com/android/systemui/plugins/Plugin.java deleted file mode 100644 index bb93367c37..0000000000 --- a/systemUIPluginCore/src/com/android/systemui/plugins/Plugin.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright (C) 2016 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.plugins; - -import com.android.systemui.plugins.annotations.Requires; - -import android.content.Context; - -/** - * Plugins are separate APKs that - * are expected to implement interfaces provided by SystemUI. Their - * code is dynamically loaded into the SysUI process which can allow - * for multiple prototypes to be created and run on a single android - * build. - * - * PluginLifecycle: - *

- *
- * plugin.onCreate(Context sysuiContext, Context pluginContext);
- * --- This is always called before any other calls
- *
- * pluginListener.onPluginConnected(Plugin p);
- * --- This lets the plugin hook know that a plugin is now connected.
- *
- * ** Any other calls back and forth between sysui/plugin **
- *
- * pluginListener.onPluginDisconnected(Plugin p);
- * --- Lets the plugin hook know that it should stop interacting with
- *     this plugin and drop all references to it.
- *
- * plugin.onDestroy();
- * --- Finally the plugin can perform any cleanup to ensure that its not
- *     leaking into the SysUI process.
- *
- * Any time a plugin APK is updated the plugin is destroyed and recreated
- * to load the new code/resources.
- *
- * 
- * - * Creating plugin hooks: - * - * To create a plugin hook, first create an interface in - * frameworks/base/packages/SystemUI/plugin that extends Plugin. - * Include in it any hooks you want to be able to call into from - * sysui and create callback interfaces for anything you need to - * pass through into the plugin. - * - * Then to attach to any plugins simply add a plugin listener and - * onPluginConnected will get called whenever new plugins are installed, - * updated, or enabled. Like this example from SystemUIApplication: - * - *
- * {@literal
- * PluginManager.getInstance(this).addPluginListener(OverlayPlugin.COMPONENT,
- *        new PluginListener() {
- *        @Override
- *        public void onPluginConnected(OverlayPlugin plugin) {
- *            StatusBar phoneStatusBar = getComponent(StatusBar.class);
- *            if (phoneStatusBar != null) {
- *                plugin.setup(phoneStatusBar.getStatusBarWindow(),
- *                phoneStatusBar.getNavigationBarView());
- *            }
- *        }
- * }, OverlayPlugin.VERSION, true /* Allow multiple plugins *\/);
- * }
- * 
- * Note the VERSION included here. Any time incompatible changes in the - * interface are made, this version should be changed to ensure old plugins - * aren't accidentally loaded. Since the plugin library is provided by - * SystemUI, default implementations can be added for new methods to avoid - * version changes when possible. - * - * Implementing a Plugin: - * - * See the ExamplePlugin for an example Android.mk on how to compile - * a plugin. Note that SystemUILib is not static for plugins, its classes - * are provided by SystemUI. - * - * Plugin security is based around a signature permission, so plugins must - * hold the following permission in their manifest. - * - *
- * {@literal
- * 
- * }
- * 
- * - * A plugin is found through a querying for services, so to let SysUI know - * about it, create a service with a name that points at your implementation - * of the plugin interface with the action accompanying it: - * - *
- * {@literal
- * 
- *    
- *        
- *    
- * 
- * }
- * 
- */ -public interface Plugin { - - /** - * @deprecated - * @see Requires - */ - default int getVersion() { - // Default of -1 indicates the plugin supports the new Requires model. - return -1; - } - - default void onCreate(Context sysuiContext, Context pluginContext) { - } - - default void onDestroy() { - } -} diff --git a/systemUIPluginCore/src/com/android/systemui/plugins/PluginFragment.java b/systemUIPluginCore/src/com/android/systemui/plugins/PluginFragment.java deleted file mode 100644 index 1bfa567b66..0000000000 --- a/systemUIPluginCore/src/com/android/systemui/plugins/PluginFragment.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2016 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.plugins; - -import android.app.Fragment; -import android.content.Context; -import android.os.Bundle; -import android.view.LayoutInflater; - -public abstract class PluginFragment extends Fragment implements Plugin { - - private Context mPluginContext; - - @Override - public void onCreate(Context sysuiContext, Context pluginContext) { - mPluginContext = pluginContext; - } - - @Override - public LayoutInflater onGetLayoutInflater(Bundle savedInstanceState) { - return super.onGetLayoutInflater(savedInstanceState).cloneInContext(getContext()); - } - - @Override - public void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - } - - @Override - public Context getContext() { - return mPluginContext; - } -} diff --git a/systemUIPluginCore/src/com/android/systemui/plugins/PluginLifecycleManager.java b/systemUIPluginCore/src/com/android/systemui/plugins/PluginLifecycleManager.java deleted file mode 100644 index 3e5e8a0d46..0000000000 --- a/systemUIPluginCore/src/com/android/systemui/plugins/PluginLifecycleManager.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.plugins; - -import android.content.ComponentName; - -/** - * Provides the ability for consumers to control plugin lifecycle. - * - * @param is the target plugin type - */ -public interface PluginLifecycleManager { - /** Returns the ComponentName of the target plugin. Maybe be called when not loaded. */ - ComponentName getComponentName(); - - /** Returns the package name of the target plugin. May be called when not loaded. */ - String getPackage(); - - /** Returns the currently loaded plugin instance (if plugin is loaded) */ - T getPlugin(); - - /** Returns true if the lifecycle manager should log debug messages */ - boolean getIsDebug(); - - /** Sets whether or not hte lifecycle manager should log debug messages */ - void setIsDebug(boolean debug); - - /** returns true if the plugin is currently loaded */ - default boolean isLoaded() { - return getPlugin() != null; - } - - /** - * Loads and creates the plugin instance if it does not exist. - * - * This will trigger {@link PluginListener#onPluginLoaded} with the new instance if it did not - * already exist. - */ - void loadPlugin(); - - /** - * Unloads and destroys the plugin instance if it exists. - * - * This will trigger {@link PluginListener#onPluginUnloaded} if a concrete plugin instance - * existed when this call was made. - */ - void unloadPlugin(); -} diff --git a/systemUIPluginCore/src/com/android/systemui/plugins/PluginListener.java b/systemUIPluginCore/src/com/android/systemui/plugins/PluginListener.java deleted file mode 100644 index bd0bd8942d..0000000000 --- a/systemUIPluginCore/src/com/android/systemui/plugins/PluginListener.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright (C) 2016 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.plugins; - -import android.content.Context; - -/** - * Interface for listening to plugins being connected and disconnected. - * - * The call order for a plugin is - * 1) {@link #onPluginAttached} - * Called when a new plugin is added to the device, or an existing plugin was replaced by - * the package manager. Will only be called once per package manager event. If multiple - * non-conflicting packages which have the same plugin interface are installed on the - * device, then this method can be called multiple times with different instances of - * {@link PluginLifecycleManager} (as long as `allowMultiple` was set to true when the - * listener was registered with {@link PluginManager#addPluginListener}). - * 2) {@link #onPluginLoaded} - * Called whenever a new instance of the plugin object is created and ready for use. Can be - * called multiple times per {@link PluginLifecycleManager}, but will always pass a newly - * created plugin object. {@link #onPluginUnloaded} with the previous plugin object will - * be called before another call to {@link #onPluginLoaded} is made. This method will be - * called once automatically after {@link #onPluginAttached}. Besides the initial call, - * {@link #onPluginLoaded} will occur due to {@link PluginLifecycleManager#loadPlugin}. - * 3) {@link #onPluginUnloaded} - * Called when a request to unload the plugin has been received. This can be triggered from - * a related call to {@link PluginLifecycleManager#unloadPlugin} or for any reason that - * {@link #onPluginDetached} would be triggered. - * 4) {@link #onPluginDetached} - * Called when the package is removed from the device, disabled, or replaced due to an - * external trigger. These are events from the android package manager. - * - * @param is the target plugin type - */ -public interface PluginListener { - /** - * Called when the plugin has been loaded and is ready to be used. - * This may be called multiple times if multiple plugins are allowed. - * It may also be called in the future if the plugin package changes - * and needs to be reloaded. - * - * @deprecated Migrate to {@link #onPluginLoaded} or {@link #onPluginAttached} - */ - @Deprecated - default void onPluginConnected(T plugin, Context pluginContext) { - // Optional - } - - /** - * Called when the plugin is first attached to the host application. {@link #onPluginLoaded} - * will be automatically called as well when first attached if true is returned. This may be - * called multiple times if multiple plugins are allowed. It may also be called in the future - * if the plugin package changes and needs to be reloaded. Each call to - * {@link #onPluginAttached} will provide a new or different {@link PluginLifecycleManager}. - * - * @return returning true will immediately load the plugin and call onPluginLoaded with the - * created object. false will skip loading, but the listener can load it at any time using the - * provided PluginLifecycleManager. Loading plugins immediately is the default behavior. - */ - default boolean onPluginAttached(PluginLifecycleManager manager) { - // Optional - return true; - } - - /** - * Called when a plugin has been uninstalled/updated and should be removed - * from use. - * - * @deprecated Migrate to {@link #onPluginDetached} or {@link #onPluginUnloaded} - */ - @Deprecated - default void onPluginDisconnected(T plugin) { - // Optional. - } - - /** - * Called when the plugin has been detached from the host application. Implementers should no - * longer attempt to reload it via this {@link PluginLifecycleManager}. If the package was - * updated and not removed, then {@link #onPluginAttached} will be called again when the updated - * package is available. - */ - default void onPluginDetached(PluginLifecycleManager manager) { - // Optional. - } - - /** - * Called when the plugin is loaded into the host's process and is available for use. This can - * happen several times if clients are using {@link PluginLifecycleManager} to manipulate a - * plugin's load state. Each call to {@link #onPluginLoaded} will have a matched call to - * {@link #onPluginUnloaded} when that plugin object should no longer be used. - */ - default void onPluginLoaded( - T plugin, - Context pluginContext, - PluginLifecycleManager manager - ) { - // Optional, default to deprecated version - onPluginConnected(plugin, pluginContext); - } - - /** - * Called when the plugin should no longer be used. Listeners should clean up all references to - * the relevant plugin so that it can be garbage collected. If the plugin object is required in - * the future a call can be made to {@link PluginLifecycleManager#loadPlugin} to create a new - * plugin object and trigger {@link #onPluginLoaded}. - */ - default void onPluginUnloaded(T plugin, PluginLifecycleManager manager) { - // Optional, default to deprecated version - onPluginDisconnected(plugin); - } -} \ No newline at end of file diff --git a/systemUIPluginCore/src/com/android/systemui/plugins/PluginManager.java b/systemUIPluginCore/src/com/android/systemui/plugins/PluginManager.java deleted file mode 100644 index 80c64cd416..0000000000 --- a/systemUIPluginCore/src/com/android/systemui/plugins/PluginManager.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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. - */ - -package com.android.systemui.plugins; - -import android.text.TextUtils; - -import com.android.systemui.plugins.annotations.ProvidesInterface; - -public interface PluginManager { - - String PLUGIN_CHANGED = "com.android.systemui.action.PLUGIN_CHANGED"; - - // must be one of the channels created in NotificationChannels.java - String NOTIFICATION_CHANNEL_ID = "ALR"; - - /** Returns plugins that don't get disabled when an exceptoin occurs. */ - String[] getPrivilegedPlugins(); - - /** */ - void addPluginListener(PluginListener listener, Class cls); - /** */ - void addPluginListener(PluginListener listener, Class cls, - boolean allowMultiple); - void addPluginListener(String action, PluginListener listener, - Class cls); - void addPluginListener(String action, PluginListener listener, - Class cls, boolean allowMultiple); - - void removePluginListener(PluginListener listener); - - boolean dependsOn(Plugin p, Class cls); - - class Helper { - public static

String getAction(Class

cls) { - ProvidesInterface info = cls.getDeclaredAnnotation(ProvidesInterface.class); - if (info == null) { - throw new RuntimeException(cls + " doesn't provide an interface"); - } - if (TextUtils.isEmpty(info.action())) { - throw new RuntimeException(cls + " doesn't provide an action"); - } - return info.action(); - } - } - -} diff --git a/systemUIPluginCore/src/com/android/systemui/plugins/annotations/Dependencies.java b/systemUIPluginCore/src/com/android/systemui/plugins/annotations/Dependencies.java deleted file mode 100644 index dbbf047519..0000000000 --- a/systemUIPluginCore/src/com/android/systemui/plugins/annotations/Dependencies.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * 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. - */ - -package com.android.systemui.plugins.annotations; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * Used for repeated @DependsOn internally, not for plugin - * use. - */ -@Retention(RetentionPolicy.RUNTIME) -public @interface Dependencies { - DependsOn[] value(); -} diff --git a/systemUIPluginCore/src/com/android/systemui/plugins/annotations/DependsOn.java b/systemUIPluginCore/src/com/android/systemui/plugins/annotations/DependsOn.java deleted file mode 100644 index b81d673063..0000000000 --- a/systemUIPluginCore/src/com/android/systemui/plugins/annotations/DependsOn.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * 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. - */ - -package com.android.systemui.plugins.annotations; - -import java.lang.annotation.Repeatable; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * Used to indicate that an interface in the plugin library needs another - * interface to function properly. When this is added, it will be enforced - * that all plugins that @Requires the annotated interface also @Requires - * the specified class as well. - */ -@Retention(RetentionPolicy.RUNTIME) -@Repeatable(value = Dependencies.class) -public @interface DependsOn { - Class target(); - -} diff --git a/systemUIPluginCore/src/com/android/systemui/plugins/annotations/ProvidesInterface.java b/systemUIPluginCore/src/com/android/systemui/plugins/annotations/ProvidesInterface.java deleted file mode 100644 index d0e14b8657..0000000000 --- a/systemUIPluginCore/src/com/android/systemui/plugins/annotations/ProvidesInterface.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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. - */ - -package com.android.systemui.plugins.annotations; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * Should be added to all interfaces in plugin lib to specify their - * current version and optionally their action to implement the plugin. - */ -@Retention(RetentionPolicy.RUNTIME) -public @interface ProvidesInterface { - int version(); - - String action() default ""; - -} diff --git a/systemUIPluginCore/src/com/android/systemui/plugins/annotations/Requirements.java b/systemUIPluginCore/src/com/android/systemui/plugins/annotations/Requirements.java deleted file mode 100644 index 9cfa279b9c..0000000000 --- a/systemUIPluginCore/src/com/android/systemui/plugins/annotations/Requirements.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * 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. - */ - -package com.android.systemui.plugins.annotations; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * Used for repeated @Requires internally, not for plugin - * use. - */ -@Retention(RetentionPolicy.RUNTIME) -public @interface Requirements { - Requires[] value(); -} diff --git a/systemUIPluginCore/src/com/android/systemui/plugins/annotations/Requires.java b/systemUIPluginCore/src/com/android/systemui/plugins/annotations/Requires.java deleted file mode 100644 index e1b1303b8c..0000000000 --- a/systemUIPluginCore/src/com/android/systemui/plugins/annotations/Requires.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * 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. - */ - -package com.android.systemui.plugins.annotations; - -import java.lang.annotation.Repeatable; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * Used to annotate which interfaces a given plugin depends on. - * - * At minimum all plugins should have at least one @Requires annotation - * for the plugin interface that they are implementing. They will also - * need an @Requires for each class that the plugin interface @DependsOn. - */ -@Retention(RetentionPolicy.RUNTIME) -@Repeatable(value = Requirements.class) -public @interface Requires { - Class target(); - int version(); -} diff --git a/systemUIShared/build.gradle b/systemUIShared/build.gradle index 8796f9bb52..81b397c40e 100644 --- a/systemUIShared/build.gradle +++ b/systemUIShared/build.gradle @@ -24,8 +24,6 @@ compileOnlyCommonJars() dependencies { compileOnly projects.hiddenApi compileOnly projects.systemUnFold - compileOnly projects.systemUIPlugin - compileOnly projects.systemUIPluginCore implementation "com.google.dagger:hilt-android:$daggerVersion" annotationProcessor "com.google.dagger:hilt-compiler:$daggerVersion" diff --git a/systemUIShared/src/com/android/systemui/shared/system/UncaughtExceptionPreHandlerManager.kt b/systemUIShared/src/com/android/systemui/shared/system/UncaughtExceptionPreHandlerManager.kt index 621778b557..44f90f4de5 100644 --- a/systemUIShared/src/com/android/systemui/shared/system/UncaughtExceptionPreHandlerManager.kt +++ b/systemUIShared/src/com/android/systemui/shared/system/UncaughtExceptionPreHandlerManager.kt @@ -28,13 +28,13 @@ class UncaughtExceptionPreHandlerManager @Inject constructor() { * Verifies that the global handler is set in Thread. If not, sets is up. */ private fun checkGlobalHandlerSetup() { - val currentHandler = Thread.getUncaughtExceptionPreHandler() + val currentHandler = Thread.getDefaultUncaughtExceptionHandler() if (currentHandler != globalUncaughtExceptionPreHandler) { if (currentHandler is GlobalUncaughtExceptionHandler) { throw IllegalStateException("Two UncaughtExceptionPreHandlerManagers created") } currentHandler?.let { addHandler(it) } - Thread.setUncaughtExceptionPreHandler(globalUncaughtExceptionPreHandler) + Thread.setDefaultUncaughtExceptionHandler(globalUncaughtExceptionPreHandler) } } diff --git a/systemUIViewCapture/.gitignore b/systemUIViewCapture/.gitignore deleted file mode 100644 index 6213826ab5..0000000000 --- a/systemUIViewCapture/.gitignore +++ /dev/null @@ -1,13 +0,0 @@ -*.iml -.project -.classpath -.project.properties -gen/ -bin/ -.idea/ -.gradle/ -local.properties -gradle/ -build/ -gradlew* -.DS_Store diff --git a/systemUIViewCapture/Android.bp b/systemUIViewCapture/Android.bp deleted file mode 100644 index 33da2dd36d..0000000000 --- a/systemUIViewCapture/Android.bp +++ /dev/null @@ -1,73 +0,0 @@ -// 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 { - default_applicable_licenses: ["Android-Apache-2.0"], -} - -java_library { - name: "view_capture_proto", - srcs: ["src/com/android/app/viewcapture/proto/*.proto"], - proto: { - type: "lite", - local_include_dirs:[ - "src/com/android/app/viewcapture/proto" - ], - }, - static_libs: ["libprotobuf-java-lite"], - java_version: "1.8", -} - -android_library { - name: "view_capture", - manifest: "AndroidManifest.xml", - platform_apis: true, - min_sdk_version: "26", - - static_libs: [ - "androidx.core_core", - "view_capture_proto", - ], - - srcs: [ - "src/**/*.java", - "src/**/*.kt" - ], -} - -android_test { - name: "view_capture_tests", - manifest: "tests/AndroidManifest.xml", - platform_apis: true, - min_sdk_version: "26", - - static_libs: [ - "androidx.core_core", - "view_capture", - "androidx.test.ext.junit", - "androidx.test.rules", - "testables", - "mockito-target-extended-minus-junit4", - ], - srcs: [ - "**/*.java", - "**/*.kt" - ], - libs: [ - "android.test.runner", - "android.test.base", - "android.test.mock", - ], - test_suites: ["device-tests"], -} diff --git a/systemUIViewCapture/AndroidManifest.xml b/systemUIViewCapture/AndroidManifest.xml deleted file mode 100644 index 1da812903b..0000000000 --- a/systemUIViewCapture/AndroidManifest.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - diff --git a/systemUIViewCapture/OWNERS b/systemUIViewCapture/OWNERS deleted file mode 100644 index 30bdc84a4d..0000000000 --- a/systemUIViewCapture/OWNERS +++ /dev/null @@ -1,2 +0,0 @@ -sunnygoyal@google.com -andonian@google.com diff --git a/systemUIViewCapture/README.md b/systemUIViewCapture/README.md deleted file mode 100644 index e2834cb22b..0000000000 --- a/systemUIViewCapture/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# ViewCapture Library - -> [!CAUTION] -> `ViewCapture.java` is **extremely performance sensitive**. -> **Any changes should be carried out with great caution** not to hurt performance. - -The following measurements should serve as a performance baseline (as of 02.10.2022): - -The onDraw() function invocation time in WindowListener within ViewCapture is measured with System.nanoTime(). The following scenario was measured: - -1. Capturing the notification shade window root view on a freshly rebooted bluejay device (2 notifications present) -> avg. time = 204237ns (0.2ms) diff --git a/systemUIViewCapture/TEST_MAPPING b/systemUIViewCapture/TEST_MAPPING deleted file mode 100644 index ecd3e96c5f..0000000000 --- a/systemUIViewCapture/TEST_MAPPING +++ /dev/null @@ -1,15 +0,0 @@ -{ - "presubmit": [ - { - "name": "view_capture_tests", - "options": [ - { - "exclude-annotation": "org.junit.Ignore" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] - } - ] -} diff --git a/systemUIViewCapture/build.gradle b/systemUIViewCapture/build.gradle deleted file mode 100644 index 860aaa5f18..0000000000 --- a/systemUIViewCapture/build.gradle +++ /dev/null @@ -1,23 +0,0 @@ -plugins { - id 'com.android.library' - id 'org.jetbrains.kotlin.android' - id 'com.google.protobuf' -} - -android { - namespace "com.android.app.viewcapture" - sourceSets { - main { - java.srcDirs = ['src'] - manifest.srcFile 'AndroidManifest.xml' - proto.srcDirs = ['src'] - } - androidTest { - java.srcDirs = ["tests"] - manifest.srcFile "tests/AndroidManifest.xml" - } - } -} - -addFrameworkJar('framework-15.jar') -compileOnlyCommonJars() diff --git a/systemUIViewCapture/src/com/android/app/viewcapture/LooperExecutor.java b/systemUIViewCapture/src/com/android/app/viewcapture/LooperExecutor.java deleted file mode 100644 index e3450f6bcc..0000000000 --- a/systemUIViewCapture/src/com/android/app/viewcapture/LooperExecutor.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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.app.viewcapture; - -import android.os.Handler; -import android.os.Looper; - -import java.util.concurrent.Callable; -import java.util.concurrent.Executor; -import java.util.concurrent.Future; -import java.util.concurrent.FutureTask; -import java.util.concurrent.RejectedExecutionException; -import java.util.concurrent.RunnableFuture; - -/** - * Implementation of {@link Executor} which executes on a provided looper. - */ -public class LooperExecutor implements Executor { - - private final Handler mHandler; - - public LooperExecutor(Looper looper) { - mHandler = new Handler(looper); - } - - @Override - public void execute(Runnable runnable) { - if (mHandler.getLooper() == Looper.myLooper()) { - runnable.run(); - } else { - mHandler.post(runnable); - } - } - - /** - * @throws RejectedExecutionException {@inheritDoc} - * @throws NullPointerException {@inheritDoc} - */ - public Future submit(Callable task) { - if (task == null) throw new NullPointerException(); - RunnableFuture ftask = new FutureTask(task); - execute(ftask); - return ftask; - } - -} diff --git a/systemUIViewCapture/src/com/android/app/viewcapture/SettingsAwareViewCapture.kt b/systemUIViewCapture/src/com/android/app/viewcapture/SettingsAwareViewCapture.kt deleted file mode 100644 index f75166b73e..0000000000 --- a/systemUIViewCapture/src/com/android/app/viewcapture/SettingsAwareViewCapture.kt +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.app.viewcapture - -import android.content.Context -import android.content.pm.LauncherApps -import android.database.ContentObserver -import android.os.Build -import android.os.Handler -import android.os.Looper -import android.os.ParcelFileDescriptor -import android.os.Process -import android.provider.Settings -import android.util.Log -import android.window.IDumpCallback -import androidx.annotation.AnyThread -import androidx.annotation.VisibleForTesting -import java.util.concurrent.Executor - -private val TAG = SettingsAwareViewCapture::class.java.simpleName - -/** - * ViewCapture that listens to system updates and enables / disables attached ViewCapture - * WindowListeners accordingly. The Settings toggle is currently controlled by the Winscope - * developer tile in the System developer options. - */ -class SettingsAwareViewCapture -@VisibleForTesting -internal constructor(private val context: Context, executor: Executor) - : ViewCapture(DEFAULT_MEMORY_SIZE, DEFAULT_INIT_POOL_SIZE, executor) { - /** Dumps all the active view captures to the wm trace directory via LauncherAppService */ - private val mDumpCallback: IDumpCallback.Stub? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) object : IDumpCallback.Stub() { - override fun onDump(out: ParcelFileDescriptor) { - try { - ParcelFileDescriptor.AutoCloseOutputStream(out).use { os -> dumpTo(os, context) } - } catch (e: Exception) { - Log.e(TAG, "failed to dump data to wm trace", e) - } - } - } else null - - init { - enableOrDisableWindowListeners() - context.contentResolver.registerContentObserver( - Settings.Global.getUriFor(VIEW_CAPTURE_ENABLED), - false, - object : ContentObserver(Handler()) { - override fun onChange(selfChange: Boolean) { - enableOrDisableWindowListeners() - } - }) - } - - @AnyThread - private fun enableOrDisableWindowListeners() { - mBgExecutor.execute { - val isEnabled = Settings.Global.getInt(context.contentResolver, VIEW_CAPTURE_ENABLED, - 0) != 0 - MAIN_EXECUTOR.execute { - enableOrDisableWindowListeners(isEnabled) - } - val launcherApps = context.getSystemService(LauncherApps::class.java) - if (mDumpCallback != null) { - if (isEnabled) { - launcherApps?.registerDumpCallback(mDumpCallback) - } else { - launcherApps?.unRegisterDumpCallback(mDumpCallback) - } - } - } - } - - companion object { - @VisibleForTesting internal const val VIEW_CAPTURE_ENABLED = "view_capture_enabled" - - private var INSTANCE: ViewCapture? = null - - @JvmStatic - fun getInstance(context: Context): ViewCapture = when { - INSTANCE != null -> INSTANCE!! - Looper.myLooper() == Looper.getMainLooper() -> SettingsAwareViewCapture( - context.applicationContext, - createAndStartNewLooperExecutor("SAViewCapture", - Process.THREAD_PRIORITY_FOREGROUND)).also { INSTANCE = it } - else -> try { - MAIN_EXECUTOR.submit { getInstance(context) }.get() - } catch (e: Exception) { - throw e - } - } - - } -} \ No newline at end of file diff --git a/systemUIViewCapture/src/com/android/app/viewcapture/SimpleViewCapture.kt b/systemUIViewCapture/src/com/android/app/viewcapture/SimpleViewCapture.kt deleted file mode 100644 index 420faca9fe..0000000000 --- a/systemUIViewCapture/src/com/android/app/viewcapture/SimpleViewCapture.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.android.app.viewcapture - -import android.os.Process - -open class SimpleViewCapture(threadName: String) : ViewCapture(DEFAULT_MEMORY_SIZE, DEFAULT_INIT_POOL_SIZE, - createAndStartNewLooperExecutor(threadName, Process.THREAD_PRIORITY_FOREGROUND)) \ No newline at end of file diff --git a/systemUIViewCapture/src/com/android/app/viewcapture/ViewCapture.java b/systemUIViewCapture/src/com/android/app/viewcapture/ViewCapture.java deleted file mode 100644 index bbd797efbf..0000000000 --- a/systemUIViewCapture/src/com/android/app/viewcapture/ViewCapture.java +++ /dev/null @@ -1,623 +0,0 @@ -/* - * 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.app.viewcapture; - -import static com.android.app.viewcapture.data.ExportedData.MagicNumber.MAGIC_NUMBER_H; -import static com.android.app.viewcapture.data.ExportedData.MagicNumber.MAGIC_NUMBER_L; - -import android.content.Context; -import android.content.res.Resources; -import android.media.permission.SafeCloseable; -import android.os.HandlerThread; -import android.os.Looper; -import android.os.SystemClock; -import android.os.Trace; -import android.text.TextUtils; -import android.util.SparseArray; -import android.view.View; -import android.view.ViewGroup; -import android.view.ViewTreeObserver; -import android.view.Window; - -import androidx.annotation.AnyThread; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.UiThread; -import androidx.annotation.VisibleForTesting; -import androidx.annotation.WorkerThread; - -import com.android.app.viewcapture.data.ExportedData; -import com.android.app.viewcapture.data.FrameData; -import com.android.app.viewcapture.data.MotionWindowData; -import com.android.app.viewcapture.data.ViewNode; -import com.android.app.viewcapture.data.WindowData; - -import java.io.IOException; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Executor; -import java.util.concurrent.TimeUnit; -import java.util.function.Consumer; -import java.util.function.Predicate; - -/** - * Utility class for capturing view data every frame - */ -public abstract class ViewCapture { - - private static final String TAG = "ViewCapture"; - - // These flags are copies of two private flags in the View class. - private static final int PFLAG_INVALIDATED = 0x80000000; - private static final int PFLAG_DIRTY_MASK = 0x00200000; - - private static final long MAGIC_NUMBER_FOR_WINSCOPE = - ((long) MAGIC_NUMBER_H.getNumber() << 32) | MAGIC_NUMBER_L.getNumber(); - - // Number of frames to keep in memory - private final int mMemorySize; - protected static final int DEFAULT_MEMORY_SIZE = 2000; - // Initial size of the reference pool. This is at least be 5 * total number of views in - // Launcher. This allows the first free frames avoid object allocation during view capture. - protected static final int DEFAULT_INIT_POOL_SIZE = 300; - - public static final LooperExecutor MAIN_EXECUTOR = new LooperExecutor(Looper.getMainLooper()); - - private final List mListeners = new ArrayList<>(); - - protected final Executor mBgExecutor; - - // Pool used for capturing view tree on the UI thread. - private ViewRef mPool = new ViewRef(); - private boolean mIsEnabled = true; - - protected ViewCapture(int memorySize, int initPoolSize, Executor bgExecutor) { - mMemorySize = memorySize; - mBgExecutor = bgExecutor; - mBgExecutor.execute(() -> initPool(initPoolSize)); - } - - public static LooperExecutor createAndStartNewLooperExecutor(String name, int priority) { - HandlerThread thread = new HandlerThread(name, priority); - thread.start(); - return new LooperExecutor(thread.getLooper()); - } - - @UiThread - private void addToPool(ViewRef start, ViewRef end) { - end.next = mPool; - mPool = start; - } - - @WorkerThread - private void initPool(int initPoolSize) { - ViewRef start = new ViewRef(); - ViewRef current = start; - - for (int i = 0; i < initPoolSize; i++) { - current.next = new ViewRef(); - current = current.next; - } - - ViewRef finalCurrent = current; - MAIN_EXECUTOR.execute(() -> addToPool(start, finalCurrent)); - } - - /** - * Attaches the ViewCapture to the provided window and returns a handle to detach the listener - */ - @NonNull - public SafeCloseable startCapture(Window window) { - String title = window.getAttributes().getTitle().toString(); - String name = TextUtils.isEmpty(title) ? window.toString() : title; - return startCapture(window.getDecorView(), name); - } - - /** - * Attaches the ViewCapture to the provided window and returns a handle to detach the listener. - * Verifies that ViewCapture is enabled before actually attaching an onDrawListener. - */ - @NonNull - public SafeCloseable startCapture(View view, String name) { - WindowListener listener = new WindowListener(view, name); - if (mIsEnabled) MAIN_EXECUTOR.execute(listener::attachToRoot); - mListeners.add(listener); - return () -> { - mListeners.remove(listener); - listener.detachFromRoot(); - }; - } - - /** - * Launcher checks for leaks in many spots during its instrumented tests. The WindowListeners - * appear to have leaks because they store mRoot views. In reality, attached views close their - * respective window listeners when they are destroyed. - *

- * This method deletes detaches and deletes mRoot views from windowListeners. This makes the - * WindowListeners unusable for anything except dumping previously captured information. They - * are still technically enabled to allow for dumping. - */ - @VisibleForTesting - public void stopCapture(@NonNull View rootView) { - mListeners.forEach(it -> { - if (rootView == it.mRoot) { - it.mRoot.getViewTreeObserver().removeOnDrawListener(it); - it.mRoot = null; - } - }); - } - - @UiThread - protected void enableOrDisableWindowListeners(boolean isEnabled) { - mIsEnabled = isEnabled; - mListeners.forEach(WindowListener::detachFromRoot); - if (mIsEnabled) mListeners.forEach(WindowListener::attachToRoot); - } - - @AnyThread - public void dumpTo(OutputStream os, Context context) - throws InterruptedException, ExecutionException, IOException { - if (mIsEnabled) getExportedData(context).writeTo(os); - } - - @VisibleForTesting - public ExportedData getExportedData(Context context) - throws InterruptedException, ExecutionException { - ArrayList classList = new ArrayList<>(); - return ExportedData.newBuilder() - .setMagicNumber(MAGIC_NUMBER_FOR_WINSCOPE) - .setPackage(context.getPackageName()) - .addAllWindowData(getWindowData(context, classList, l -> l.mIsActive).get()) - .addAllClassname(toStringList(classList)) - .setRealToElapsedTimeOffsetNanos(TimeUnit.MILLISECONDS - .toNanos(System.currentTimeMillis()) - SystemClock.elapsedRealtimeNanos()) - .build(); - } - - private static List toStringList(List classList) { - return classList.stream().map(Class::getName).toList(); - } - - public CompletableFuture> getDumpTask(View view) { - ArrayList classList = new ArrayList<>(); - return getWindowData(view.getContext().getApplicationContext(), classList, - l -> l.mRoot.equals(view)).thenApply(list -> list.stream().findFirst().map(w -> - MotionWindowData.newBuilder() - .addAllFrameData(w.getFrameDataList()) - .addAllClassname(toStringList(classList)) - .build())); - } - - @AnyThread - private CompletableFuture> getWindowData(Context context, - ArrayList outClassList, Predicate filter) { - ViewIdProvider idProvider = new ViewIdProvider(context.getResources()); - return CompletableFuture.supplyAsync(() -> - mListeners.stream().filter(filter).toList(), MAIN_EXECUTOR).thenApplyAsync(it -> - it.stream().map(l -> l.dumpToProto(idProvider, outClassList)).toList(), - mBgExecutor); - } - - - /** - * Once this window listener is attached to a window's root view, it traverses the entire - * view tree on the main thread every time onDraw is called. It then saves the state of the view - * tree traversed in a local list of nodes, so that this list of nodes can be processed on a - * background thread, and prepared for being dumped into a bugreport. - * - * Since some of the work needs to be done on the main thread after every draw, this piece of - * code needs to be hyper optimized. That is why we are recycling ViewRef and ViewPropertyRef - * objects and storing the list of nodes as a flat LinkedList, rather than as a tree. This data - * structure allows recycling to happen in O(1) time via pointer assignment. Without this - * optimization, a lot of time is wasted creating ViewRef objects, or finding ViewRef objects to - * recycle. - * - * Another optimization is to only traverse view nodes on the main thread that have potentially - * changed since the last frame was drawn. This can be determined via a combination of private - * flags inside the View class. - * - * Another optimization is to not store or manipulate any string objects on the main thread. - * While this might seem trivial, using Strings in any form causes the ViewCapture to hog the - * main thread for up to an additional 6-7ms. It must be avoided at all costs. - * - * Another optimization is to only store the class names of the Views in the view hierarchy one - * time. They are then referenced via a classNameIndex value stored in each ViewPropertyRef. - * - * TODO: b/262585897: If further memory optimization is required, an effective one would be to - * only store the changes between frames, rather than the entire node tree for each frame. - * The go/web-hv UX already does this, and has reaped significant memory improves because of it. - * - * TODO: b/262585897: Another memory optimization could be to store all integer, float, and - * boolean information via single integer values via the Chinese remainder theorem, or a similar - * algorithm, which enables multiple numerical values to be stored inside 1 number. Doing this - * would allow each ViewProperty / ViewRef to slim down its memory footprint significantly. - * - * One important thing to remember is that bugs related to recycling will usually only appear - * after at least 2000 frames have been rendered. If that code is changed, the tester can - * use hard-coded logs to verify that recycling is happening, and test view capturing at least - * ~8000 frames or so to verify the recycling functionality is working properly. - */ - private class WindowListener implements ViewTreeObserver.OnDrawListener { - - @Nullable // Nullable in tests only - public View mRoot; - public final String name; - - private final ViewRef mViewRef = new ViewRef(); - - private int mFrameIndexBg = -1; - private boolean mIsFirstFrame = true; - private final long[] mFrameTimesNanosBg = new long[mMemorySize]; - private final ViewPropertyRef[] mNodesBg = new ViewPropertyRef[mMemorySize]; - - private boolean mIsActive = true; - private final Consumer mCaptureCallback = this::captureViewPropertiesBg; - - WindowListener(View view, String name) { - mRoot = view; - this.name = name; - } - - /** - * Every time onDraw is called, it does the minimal set of work required on the main thread, - * i.e. capturing potentially dirty / invalidated views, and then immediately offloads the - * rest of the processing work (extracting the captured view properties) to a background - * thread via mExecutor. - */ - @Override - public void onDraw() { - Trace.beginSection("view_capture"); - captureViewTree(mRoot, mViewRef); - ViewRef captured = mViewRef.next; - if (captured != null) { - captured.callback = mCaptureCallback; - captured.elapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos(); - mBgExecutor.execute(captured); - } - mIsFirstFrame = false; - Trace.endSection(); - } - - /** - * Captures the View property on the background thread, and transfer all the ViewRef objects - * back to the pool - */ - @WorkerThread - private void captureViewPropertiesBg(ViewRef viewRefStart) { - long elapsedRealtimeNanos = viewRefStart.elapsedRealtimeNanos; - mFrameIndexBg++; - if (mFrameIndexBg >= mMemorySize) { - mFrameIndexBg = 0; - } - mFrameTimesNanosBg[mFrameIndexBg] = elapsedRealtimeNanos; - - ViewPropertyRef recycle = mNodesBg[mFrameIndexBg]; - - ViewPropertyRef resultStart = null; - ViewPropertyRef resultEnd = null; - - ViewRef viewRefEnd = viewRefStart; - while (viewRefEnd != null) { - ViewPropertyRef propertyRef = recycle; - if (propertyRef == null) { - propertyRef = new ViewPropertyRef(); - } else { - recycle = recycle.next; - propertyRef.next = null; - } - - ViewPropertyRef copy = null; - if (viewRefEnd.childCount < 0) { - copy = findInLastFrame(viewRefEnd.view.hashCode()); - viewRefEnd.childCount = (copy != null) ? copy.childCount : 0; - } - viewRefEnd.transferTo(propertyRef); - - if (resultStart == null) { - resultStart = propertyRef; - resultEnd = resultStart; - } else { - resultEnd.next = propertyRef; - resultEnd = resultEnd.next; - } - - if (copy != null) { - int pending = copy.childCount; - while (pending > 0) { - copy = copy.next; - pending = pending - 1 + copy.childCount; - - propertyRef = recycle; - if (propertyRef == null) { - propertyRef = new ViewPropertyRef(); - } else { - recycle = recycle.next; - propertyRef.next = null; - } - - copy.transferTo(propertyRef); - - resultEnd.next = propertyRef; - resultEnd = resultEnd.next; - } - } - - if (viewRefEnd.next == null) { - // The compiler will complain about using a non-final variable from - // an outer class in a lambda if we pass in viewRefEnd directly. - final ViewRef finalViewRefEnd = viewRefEnd; - MAIN_EXECUTOR.execute(() -> addToPool(viewRefStart, finalViewRefEnd)); - break; - } - viewRefEnd = viewRefEnd.next; - } - mNodesBg[mFrameIndexBg] = resultStart; - } - - private @Nullable ViewPropertyRef findInLastFrame(int hashCode) { - int lastFrameIndex = (mFrameIndexBg == 0) ? mMemorySize - 1 : mFrameIndexBg - 1; - ViewPropertyRef viewPropertyRef = mNodesBg[lastFrameIndex]; - while (viewPropertyRef != null && viewPropertyRef.hashCode != hashCode) { - viewPropertyRef = viewPropertyRef.next; - } - return viewPropertyRef; - } - - void attachToRoot() { - mIsActive = true; - if (mRoot.isAttachedToWindow()) { - safelyEnableOnDrawListener(); - } else { - mRoot.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { - @Override - public void onViewAttachedToWindow(View v) { - if (mIsActive) { - safelyEnableOnDrawListener(); - } - mRoot.removeOnAttachStateChangeListener(this); - } - - @Override - public void onViewDetachedFromWindow(View v) { - } - }); - } - } - - void detachFromRoot() { - mIsActive = false; - if (mRoot != null) { - mRoot.getViewTreeObserver().removeOnDrawListener(this); - } - } - - private void safelyEnableOnDrawListener() { - mRoot.getViewTreeObserver().removeOnDrawListener(this); - mRoot.getViewTreeObserver().addOnDrawListener(this); - } - - @WorkerThread - private WindowData dumpToProto(ViewIdProvider idProvider, ArrayList classList) { - WindowData.Builder builder = WindowData.newBuilder().setTitle(name); - int size = (mNodesBg[mMemorySize - 1] == null) ? mFrameIndexBg + 1 : mMemorySize; - for (int i = size - 1; i >= 0; i--) { - int index = (mMemorySize + mFrameIndexBg - i) % mMemorySize; - ViewNode.Builder nodeBuilder = ViewNode.newBuilder(); - mNodesBg[index].toProto(idProvider, classList, nodeBuilder); - FrameData.Builder frameDataBuilder = FrameData.newBuilder() - .setNode(nodeBuilder) - .setTimestamp(mFrameTimesNanosBg[index]); - builder.addFrameData(frameDataBuilder); - } - return builder.build(); - } - - private ViewRef captureViewTree(View view, ViewRef start) { - ViewRef ref; - if (mPool != null) { - ref = mPool; - mPool = mPool.next; - ref.next = null; - } else { - ref = new ViewRef(); - } - ref.view = view; - start.next = ref; - if (view instanceof ViewGroup) { - ViewGroup parent = (ViewGroup) view; - // If a view has not changed since the last frame, we will copy - // its children from the last processed frame's data. - if ((view.mPrivateFlags & (PFLAG_INVALIDATED | PFLAG_DIRTY_MASK)) == 0 - && !mIsFirstFrame) { - // A negative child count is the signal to copy this view from the last frame. - ref.childCount = -parent.getChildCount(); - return ref; - } - ViewRef result = ref; - int childCount = ref.childCount = parent.getChildCount(); - for (int i = 0; i < childCount; i++) { - result = captureViewTree(parent.getChildAt(i), result); - } - return result; - } else { - ref.childCount = 0; - return ref; - } - } - } - - private static class ViewPropertyRef { - // We store reference in memory to avoid generating and storing too many strings - public Class clazz; - public int hashCode; - public int childCount = 0; - - public int id; - public int left, top, right, bottom; - public int scrollX, scrollY; - - public float translateX, translateY; - public float scaleX, scaleY; - public float alpha; - public float elevation; - - public int visibility; - public boolean willNotDraw; - public boolean clipChildren; - - public ViewPropertyRef next; - - public void transferTo(ViewPropertyRef out) { - out.clazz = this.clazz; - out.hashCode = this.hashCode; - out.childCount = this.childCount; - out.id = this.id; - out.left = this.left; - out.top = this.top; - out.right = this.right; - out.bottom = this.bottom; - out.scrollX = this.scrollX; - out.scrollY = this.scrollY; - out.scaleX = this.scaleX; - out.scaleY = this.scaleY; - out.translateX = this.translateX; - out.translateY = this.translateY; - out.alpha = this.alpha; - out.visibility = this.visibility; - out.willNotDraw = this.willNotDraw; - out.clipChildren = this.clipChildren; - out.elevation = this.elevation; - } - - /** - * Converts the data to the proto representation and returns the next property ref - * at the end of the iteration. - */ - public ViewPropertyRef toProto(ViewIdProvider idProvider, ArrayList classList, - ViewNode.Builder viewNode) { - int classnameIndex = classList.indexOf(clazz); - if (classnameIndex < 0) { - classnameIndex = classList.size(); - classList.add(clazz); - } - - viewNode.setClassnameIndex(classnameIndex) - .setHashcode(hashCode) - .setId(idProvider.getName(id)) - .setLeft(left) - .setTop(top) - .setWidth(right - left) - .setHeight(bottom - top) - .setTranslationX(translateX) - .setTranslationY(translateY) - .setScrollX(scrollX) - .setScrollY(scrollY) - .setScaleX(scaleX) - .setScaleY(scaleY) - .setAlpha(alpha) - .setVisibility(visibility) - .setWillNotDraw(willNotDraw) - .setElevation(elevation) - .setClipChildren(clipChildren); - - ViewPropertyRef result = next; - for (int i = 0; (i < childCount) && (result != null); i++) { - ViewNode.Builder childViewNode = ViewNode.newBuilder(); - result = result.toProto(idProvider, classList, childViewNode); - viewNode.addChildren(childViewNode); - } - return result; - } - } - - - private static class ViewRef implements Runnable { - public View view; - public int childCount = 0; - public ViewRef next; - - public Consumer callback = null; - public long elapsedRealtimeNanos = 0; - - public void transferTo(ViewPropertyRef out) { - out.childCount = this.childCount; - - View view = this.view; - this.view = null; - - out.clazz = view.getClass(); - out.hashCode = view.hashCode(); - out.id = view.getId(); - out.left = view.getLeft(); - out.top = view.getTop(); - out.right = view.getRight(); - out.bottom = view.getBottom(); - out.scrollX = view.getScrollX(); - out.scrollY = view.getScrollY(); - - out.translateX = view.getTranslationX(); - out.translateY = view.getTranslationY(); - out.scaleX = view.getScaleX(); - out.scaleY = view.getScaleY(); - out.alpha = view.getAlpha(); - out.elevation = view.getElevation(); - - out.visibility = view.getVisibility(); - out.willNotDraw = view.willNotDraw(); - } - - @Override - public void run() { - Consumer oldCallback = callback; - callback = null; - if (oldCallback != null) { - oldCallback.accept(this); - } - } - } - - private static final class ViewIdProvider { - - private final SparseArray mNames = new SparseArray<>(); - private final Resources mRes; - - ViewIdProvider(Resources res) { - mRes = res; - } - - String getName(int id) { - String name = mNames.get(id); - if (name == null) { - if (id >= 0) { - try { - name = mRes.getResourceTypeName(id) + '/' + mRes.getResourceEntryName(id); - } catch (Resources.NotFoundException e) { - name = "id/" + "0x" + Integer.toHexString(id).toUpperCase(); - } - } else { - name = "NO_ID"; - } - mNames.put(id, name); - } - return name; - } - } -} diff --git a/systemUIViewCapture/src/com/android/app/viewcapture/proto/view_capture.proto b/systemUIViewCapture/src/com/android/app/viewcapture/proto/view_capture.proto deleted file mode 100644 index c73ce8bcf8..0000000000 --- a/systemUIViewCapture/src/com/android/app/viewcapture/proto/view_capture.proto +++ /dev/null @@ -1,83 +0,0 @@ -/* - * 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. - */ - -syntax = "proto2"; - -package com.android.app.viewcapture.data; - -option java_multiple_files = true; - -message ExportedData { - /* constant; MAGIC_NUMBER = (long) MAGIC_NUMBER_H << 32 | MagicNumber.MAGIC_NUMBER_L - (this is needed because enums have to be 32 bits and there's no nice way to put 64bit - constants into .proto files. */ - enum MagicNumber { - INVALID = 0; - MAGIC_NUMBER_L = 0x65906578; /* AZAN (ASCII) */ - MAGIC_NUMBER_H = 0x68658273; /* DARI (ASCII) */ - } - - optional fixed64 magic_number = 1; /* Must be the first field, set to value in MagicNumber */ - repeated WindowData windowData = 2; - optional string package = 3; - repeated string classname = 4; - - /* offset between real-time clock and elapsed time clock in nanoseconds. - Calculated as: 1000000 * System.currentTimeMillis() - SystemClock.elapsedRealtimeNanos() */ - optional fixed64 real_to_elapsed_time_offset_nanos = 5; -} - -message WindowData { - repeated FrameData frameData = 1; - optional string title = 2; -} - -message MotionWindowData { - repeated FrameData frameData = 1; - repeated string classname = 2; -} - -message FrameData { - optional int64 timestamp = 1; // unit is elapsed realtime nanos - optional ViewNode node = 2; -} - -message ViewNode { - optional int32 classname_index = 1; - optional int32 hashcode = 2; - - repeated ViewNode children = 3; - - optional string id = 4; - optional int32 left = 5; - optional int32 top = 6; - optional int32 width = 7; - optional int32 height = 8; - optional int32 scrollX = 9; - optional int32 scrollY = 10; - - optional float translationX = 11; - optional float translationY = 12; - optional float scaleX = 13 [default = 1]; - optional float scaleY = 14 [default = 1]; - optional float alpha = 15 [default = 1]; - - optional bool willNotDraw = 16; - optional bool clipChildren = 17; - optional int32 visibility = 18; - - optional float elevation = 19; -} diff --git a/systemUIViewCapture/tests/AndroidManifest.xml b/systemUIViewCapture/tests/AndroidManifest.xml deleted file mode 100644 index 8d31c0eb5d..0000000000 --- a/systemUIViewCapture/tests/AndroidManifest.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - - - - - - diff --git a/systemUIViewCapture/tests/com/android/app/viewcapture/SettingsAwareViewCaptureTest.kt b/systemUIViewCapture/tests/com/android/app/viewcapture/SettingsAwareViewCaptureTest.kt deleted file mode 100644 index 15352aa760..0000000000 --- a/systemUIViewCapture/tests/com/android/app/viewcapture/SettingsAwareViewCaptureTest.kt +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.app.viewcapture - -import android.Manifest -import android.content.Context -import android.content.Intent -import android.media.permission.SafeCloseable -import android.provider.Settings -import android.testing.AndroidTestingRunner -import android.view.Choreographer -import android.view.View -import androidx.test.ext.junit.rules.ActivityScenarioRule -import androidx.test.filters.SmallTest -import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.rule.GrantPermissionRule -import com.android.app.viewcapture.SettingsAwareViewCapture.Companion.VIEW_CAPTURE_ENABLED -import com.android.app.viewcapture.ViewCapture.MAIN_EXECUTOR -import junit.framework.Assert.assertEquals -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith - -@SmallTest -@RunWith(AndroidTestingRunner::class) -class SettingsAwareViewCaptureTest { - private val context: Context = InstrumentationRegistry.getInstrumentation().context - private val activityIntent = Intent(context, TestActivity::class.java) - - @get:Rule val activityScenarioRule = ActivityScenarioRule(activityIntent) - @get:Rule val grantPermissionRule = - GrantPermissionRule.grant(Manifest.permission.WRITE_SECURE_SETTINGS) - - @Test - fun do_not_capture_view_hierarchies_if_setting_is_disabled() { - Settings.Global.putInt(context.contentResolver, VIEW_CAPTURE_ENABLED, 0) - - activityScenarioRule.scenario.onActivity { activity -> - val viewCapture: ViewCapture = SettingsAwareViewCapture(context, MAIN_EXECUTOR) - val rootView: View = activity.requireViewById(android.R.id.content) - - val closeable: SafeCloseable = viewCapture.startCapture(rootView, "rootViewId") - Choreographer.getInstance().postFrameCallback { - rootView.viewTreeObserver.dispatchOnDraw() - - assertEquals( - 0, - viewCapture - .getDumpTask(activity.requireViewById(android.R.id.content)) - .get() - .get() - .frameDataList - .size - ) - closeable.close() - } - } - } - - @Test - fun capture_view_hierarchies_if_setting_is_enabled() { - Settings.Global.putInt(context.contentResolver, VIEW_CAPTURE_ENABLED, 1) - - activityScenarioRule.scenario.onActivity { activity -> - val viewCapture: ViewCapture = SettingsAwareViewCapture(context, MAIN_EXECUTOR) - val rootView: View = activity.requireViewById(android.R.id.content) - - val closeable: SafeCloseable = viewCapture.startCapture(rootView, "rootViewId") - Choreographer.getInstance().postFrameCallback { - rootView.viewTreeObserver.dispatchOnDraw() - - assertEquals( - 1, - viewCapture - .getDumpTask(activity.requireViewById(android.R.id.content)) - .get() - .get() - .frameDataList - .size - ) - - closeable.close() - } - } - } - - @Test - fun getInstance_calledTwiceInARow_returnsSameObject() { - assertEquals( - SettingsAwareViewCapture.getInstance(context).hashCode(), - SettingsAwareViewCapture.getInstance(context).hashCode() - ) - } -} diff --git a/systemUIViewCapture/tests/com/android/app/viewcapture/TestActivity.kt b/systemUIViewCapture/tests/com/android/app/viewcapture/TestActivity.kt deleted file mode 100644 index 749327ebe3..0000000000 --- a/systemUIViewCapture/tests/com/android/app/viewcapture/TestActivity.kt +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.app.viewcapture - -import android.app.Activity -import android.os.Bundle -import android.widget.LinearLayout -import android.widget.TextView - -/** - * Activity with the content set to a [LinearLayout] with [TextView] children. - */ -class TestActivity : Activity() { - - companion object { - const val TEXT_VIEW_COUNT = 1000 - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(createContentView()) - } - - private fun createContentView(): LinearLayout { - val root = LinearLayout(this) - for (i in 0 until TEXT_VIEW_COUNT) { - root.addView(TextView(this)) - } - return root - } -} \ No newline at end of file diff --git a/systemUIViewCapture/tests/com/android/app/viewcapture/ViewCaptureTest.kt b/systemUIViewCapture/tests/com/android/app/viewcapture/ViewCaptureTest.kt deleted file mode 100644 index e3272c422e..0000000000 --- a/systemUIViewCapture/tests/com/android/app/viewcapture/ViewCaptureTest.kt +++ /dev/null @@ -1,111 +0,0 @@ -/* - * 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.app.viewcapture - -import android.content.Intent -import android.media.permission.SafeCloseable -import android.testing.AndroidTestingRunner -import android.view.View -import android.widget.LinearLayout -import android.widget.TextView -import androidx.test.ext.junit.rules.ActivityScenarioRule -import androidx.test.filters.SmallTest -import androidx.test.platform.app.InstrumentationRegistry -import com.android.app.viewcapture.TestActivity.Companion.TEXT_VIEW_COUNT -import com.android.app.viewcapture.data.MotionWindowData -import junit.framework.Assert.assertEquals -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith - -@SmallTest -@RunWith(AndroidTestingRunner::class) -class ViewCaptureTest { - - private val memorySize = 100 - private val initPoolSize = 15 - private val viewCapture by lazy { - object : - ViewCapture(memorySize, initPoolSize, MAIN_EXECUTOR) {} - } - - private val activityIntent = - Intent(InstrumentationRegistry.getInstrumentation().context, TestActivity::class.java) - - @get:Rule val activityScenarioRule = ActivityScenarioRule(activityIntent) - - @Test - fun testWindowListenerDumpsOneFrameAfterInvalidate() { - activityScenarioRule.scenario.onActivity { activity -> - val closeable = startViewCaptureAndInvalidateNTimes(1, activity) - val rootView = activity.requireViewById(android.R.id.content) - val data = viewCapture.getDumpTask(rootView).get().get() - - assertEquals(1, data.frameDataList.size) - verifyTestActivityViewHierarchy(data) - closeable.close() - } - } - - @Test - fun testWindowListenerDumpsCorrectlyAfterRecyclingStarted() { - activityScenarioRule.scenario.onActivity { activity -> - val closeable = startViewCaptureAndInvalidateNTimes(memorySize + 5, activity) - val rootView = activity.requireViewById(android.R.id.content) - val data = viewCapture.getDumpTask(rootView).get().get() - - // since ViewCapture MEMORY_SIZE is [viewCaptureMemorySize], only - // [viewCaptureMemorySize] frames are exported, although the view is invalidated - // [viewCaptureMemorySize + 5] times - assertEquals(memorySize, data.frameDataList.size) - verifyTestActivityViewHierarchy(data) - closeable.close() - } - } - - private fun startViewCaptureAndInvalidateNTimes(n: Int, activity: TestActivity): SafeCloseable { - val rootView: View = activity.requireViewById(android.R.id.content) - val closeable: SafeCloseable = viewCapture.startCapture(rootView, "rootViewId") - dispatchOnDraw(rootView, times = n) - return closeable - } - - private fun dispatchOnDraw(view: View, times: Int) { - if (times > 0) { - view.viewTreeObserver.dispatchOnDraw() - dispatchOnDraw(view, times - 1) - } - } - - private fun verifyTestActivityViewHierarchy(exportedData: MotionWindowData) { - for (frame in exportedData.frameDataList) { - val testActivityRoot = - frame.node // FrameLayout (android.R.id.content) - .childrenList - .first() // LinearLayout (set by setContentView()) - assertEquals(TEXT_VIEW_COUNT, testActivityRoot.childrenList.size) - assertEquals( - LinearLayout::class.qualifiedName, - exportedData.getClassname(testActivityRoot.classnameIndex) - ) - assertEquals( - TextView::class.qualifiedName, - exportedData.getClassname(testActivityRoot.childrenList.first().classnameIndex) - ) - } - } -} diff --git a/systemUnFold/build.gradle b/systemUnFold/build.gradle index c1def6b0a3..5ab933ae02 100644 --- a/systemUnFold/build.gradle +++ b/systemUnFold/build.gradle @@ -1,7 +1,6 @@ plugins { id 'com.android.library' id 'org.jetbrains.kotlin.android' - id 'org.jetbrains.kotlin.kapt' } android { @@ -15,11 +14,6 @@ android { aidl.srcDirs = ['src'] } } - kapt { - arguments { - arg("dagger.hilt.disableModulesHaveInstallInCheck", "true") - } - } } addFrameworkJar('framework-15.jar') @@ -27,7 +21,7 @@ compileOnlyCommonJars() dependencies { implementation "com.google.dagger:hilt-android:$daggerVersion" - kapt "com.google.dagger:hilt-compiler:$daggerVersion" + annotationProcessor "com.google.dagger:hilt-compiler:$daggerVersion" implementation "androidx.concurrent:concurrent-futures:1.2.0" implementation "androidx.lifecycle:lifecycle-common:2.8.6"