diff options
| author | 2022-08-10 16:38:51 +0000 | |
|---|---|---|
| committer | 2022-08-26 20:24:16 +0000 | |
| commit | 77b4efa8a787a113517d2d6123e0c7965b5725c7 (patch) | |
| tree | 4bb1fb30fa6c1dc29dbb373b27953154da54248f | |
| parent | 791a2c7909aa65c9912b6d8ecfb02e42929f3e87 (diff) | |
Refactor WirelessChargingAnimation.
TL;DR separate WirelessChargingAnimation into a view and controller class to make it testable & reduce complexity.
Design doc: go/wireless-ripple-refactor
Bug: b/243064496
Test: Manual, WirelssChargingRippleController
Change-Id: I2fbb8f0c6b476c9476cd98761643f6213d3de11c
10 files changed, 432 insertions, 287 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java deleted file mode 100644 index e82d0ea85490..000000000000 --- a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java +++ /dev/null @@ -1,254 +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.charging; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.content.Context; -import android.graphics.PixelFormat; -import android.os.Handler; -import android.os.Looper; -import android.os.Message; -import android.util.Log; -import android.util.Slog; -import android.view.Gravity; -import android.view.WindowManager; - -import com.android.internal.logging.UiEvent; -import com.android.internal.logging.UiEventLogger; -import com.android.systemui.ripple.RippleShader.RippleShape; - -/** - * A WirelessChargingAnimation is a view containing view + animation for wireless charging. - * @hide - */ -public class WirelessChargingAnimation { - public static final int UNKNOWN_BATTERY_LEVEL = -1; - public static final long DURATION = 1500; - private static final String TAG = "WirelessChargingView"; - private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); - - private final WirelessChargingView mCurrentWirelessChargingView; - private static WirelessChargingView mPreviousWirelessChargingView; - - public interface Callback { - void onAnimationStarting(); - void onAnimationEnded(); - } - - /** - * Constructs an empty WirelessChargingAnimation object. If looper is null, - * Looper.myLooper() is used. Must set - * {@link WirelessChargingAnimation#mCurrentWirelessChargingView} - * before calling {@link #show} - can be done through {@link #makeWirelessChargingAnimation}. - * @hide - */ - private WirelessChargingAnimation(@NonNull Context context, @Nullable Looper looper, - int transmittingBatteryLevel, int batteryLevel, Callback callback, boolean isDozing, - RippleShape rippleShape, UiEventLogger uiEventLogger) { - mCurrentWirelessChargingView = new WirelessChargingView(context, looper, - transmittingBatteryLevel, batteryLevel, callback, isDozing, - rippleShape, uiEventLogger); - } - - /** - * Creates a wireless charging animation object populated with next view. - * - * @hide - */ - public static WirelessChargingAnimation makeWirelessChargingAnimation(@NonNull Context context, - @Nullable Looper looper, int transmittingBatteryLevel, int batteryLevel, - Callback callback, boolean isDozing, RippleShape rippleShape, - UiEventLogger uiEventLogger) { - return new WirelessChargingAnimation(context, looper, transmittingBatteryLevel, - batteryLevel, callback, isDozing, rippleShape, uiEventLogger); - } - - /** - * Creates a charging animation object using mostly default values for non-dozing and unknown - * battery level without charging number shown. - */ - public static WirelessChargingAnimation makeChargingAnimationWithNoBatteryLevel( - @NonNull Context context, RippleShape rippleShape, UiEventLogger uiEventLogger) { - return makeWirelessChargingAnimation(context, null, - UNKNOWN_BATTERY_LEVEL, UNKNOWN_BATTERY_LEVEL, null, false, - rippleShape, uiEventLogger); - } - - /** - * Show the view for the specified duration. - */ - public void show(long delay) { - if (mCurrentWirelessChargingView == null || - mCurrentWirelessChargingView.mNextView == null) { - throw new RuntimeException("setView must have been called"); - } - - if (mPreviousWirelessChargingView != null) { - mPreviousWirelessChargingView.hide(0); - } - - mPreviousWirelessChargingView = mCurrentWirelessChargingView; - mCurrentWirelessChargingView.show(delay); - mCurrentWirelessChargingView.hide(delay + DURATION); - } - - private static class WirelessChargingView { - private static final int SHOW = 0; - private static final int HIDE = 1; - - private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams(); - private final Handler mHandler; - private final UiEventLogger mUiEventLogger; - - private int mGravity; - private WirelessChargingLayout mView; - private WirelessChargingLayout mNextView; - private WindowManager mWM; - private Callback mCallback; - - public WirelessChargingView(Context context, @Nullable Looper looper, - int transmittingBatteryLevel, int batteryLevel, Callback callback, - boolean isDozing, RippleShape rippleShape, UiEventLogger uiEventLogger) { - mCallback = callback; - mNextView = new WirelessChargingLayout(context, transmittingBatteryLevel, batteryLevel, - isDozing, rippleShape); - mGravity = Gravity.CENTER_HORIZONTAL | Gravity.CENTER; - mUiEventLogger = uiEventLogger; - - final WindowManager.LayoutParams params = mParams; - params.height = WindowManager.LayoutParams.MATCH_PARENT; - params.width = WindowManager.LayoutParams.MATCH_PARENT; - params.format = PixelFormat.TRANSLUCENT; - params.type = WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG; - params.setTitle("Charging Animation"); - params.layoutInDisplayCutoutMode = - WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; - params.setFitInsetsTypes(0 /* ignore all system bar insets */); - params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE - | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; - params.setTrustedOverlay(); - - if (looper == null) { - // Use Looper.myLooper() if looper is not specified. - looper = Looper.myLooper(); - if (looper == null) { - throw new RuntimeException( - "Can't display wireless animation on a thread that has not called " - + "Looper.prepare()"); - } - } - - mHandler = new Handler(looper, null) { - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case SHOW: { - handleShow(); - break; - } - case HIDE: { - handleHide(); - // Don't do this in handleHide() because it is also invoked by - // handleShow() - mNextView = null; - break; - } - } - } - }; - } - - public void show(long delay) { - if (DEBUG) Slog.d(TAG, "SHOW: " + this); - mHandler.sendMessageDelayed(Message.obtain(mHandler, SHOW), delay); - } - - public void hide(long duration) { - mHandler.removeMessages(HIDE); - - if (DEBUG) Slog.d(TAG, "HIDE: " + this); - mHandler.sendMessageDelayed(Message.obtain(mHandler, HIDE), duration); - } - - private void handleShow() { - if (DEBUG) { - Slog.d(TAG, "HANDLE SHOW: " + this + " mView=" + mView + " mNextView=" - + mNextView); - } - - if (mView != mNextView) { - // remove the old view if necessary - handleHide(); - mView = mNextView; - Context context = mView.getContext().getApplicationContext(); - String packageName = mView.getContext().getOpPackageName(); - if (context == null) { - context = mView.getContext(); - } - mWM = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); - mParams.packageName = packageName; - mParams.hideTimeoutMilliseconds = DURATION; - - if (mView.getParent() != null) { - if (DEBUG) Slog.d(TAG, "REMOVE! " + mView + " in " + this); - mWM.removeView(mView); - } - if (DEBUG) Slog.d(TAG, "ADD! " + mView + " in " + this); - - try { - if (mCallback != null) { - mCallback.onAnimationStarting(); - } - mWM.addView(mView, mParams); - mUiEventLogger.log(WirelessChargingRippleEvent.WIRELESS_RIPPLE_PLAYED); - } catch (WindowManager.BadTokenException e) { - Slog.d(TAG, "Unable to add wireless charging view. " + e); - } - } - } - - private void handleHide() { - if (DEBUG) Slog.d(TAG, "HANDLE HIDE: " + this + " mView=" + mView); - if (mView != null) { - if (mView.getParent() != null) { - if (DEBUG) Slog.d(TAG, "REMOVE! " + mView + " in " + this); - if (mCallback != null) { - mCallback.onAnimationEnded(); - } - mWM.removeViewImmediate(mView); - } - - mView = null; - } - } - - enum WirelessChargingRippleEvent implements UiEventLogger.UiEventEnum { - @UiEvent(doc = "Wireless charging ripple effect played") - WIRELESS_RIPPLE_PLAYED(830); - - private final int mInt; - WirelessChargingRippleEvent(int id) { - mInt = id; - } - - @Override public int getId() { - return mInt; - } - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java index 08393386c27f..6da65d2811d0 100644 --- a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java +++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java @@ -16,6 +16,9 @@ package com.android.systemui.charging; +import static com.android.systemui.charging.WirelessChargingRippleControllerKt.DEFAULT_DURATION; +import static com.android.systemui.charging.WirelessChargingRippleControllerKt.UNKNOWN_BATTERY_LEVEL; + import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; @@ -41,39 +44,43 @@ import com.android.systemui.ripple.RippleView; import java.text.NumberFormat; /** - * @hide + * Layout that is used for wireless charging. + * + * <p>Wireless charging layout has {@link RippleView} and text view to show the battery level. */ final class WirelessChargingLayout extends FrameLayout { - private static final long CIRCLE_RIPPLE_ANIMATION_DURATION = 1500; - private static final long ROUNDED_BOX_RIPPLE_ANIMATION_DURATION = 1750; private static final int SCRIM_COLOR = 0x4C000000; private static final int SCRIM_FADE_DURATION = 300; private RippleView mRippleView; WirelessChargingLayout(Context context, int transmittingBatteryLevel, int batteryLevel, - boolean isDozing, RippleShape rippleShape) { + boolean isDozing, RippleShape rippleShape, long duration) { super(context); - init(context, null, transmittingBatteryLevel, batteryLevel, isDozing, rippleShape); + init(context, null, transmittingBatteryLevel, batteryLevel, isDozing, rippleShape, + duration); } private WirelessChargingLayout(Context context) { super(context); - init(context, null, /* isDozing= */ false, RippleShape.CIRCLE); + init(context, null, /* isDozing= */ false, RippleShape.CIRCLE, + DEFAULT_DURATION); } private WirelessChargingLayout(Context context, AttributeSet attrs) { super(context, attrs); - init(context, attrs, /* isDozing= */false, RippleShape.CIRCLE); + init(context, attrs, /* isDozing= */false, RippleShape.CIRCLE, + DEFAULT_DURATION); } - private void init(Context c, AttributeSet attrs, boolean isDozing, RippleShape rippleShape) { - init(c, attrs, -1, -1, isDozing, rippleShape); + private void init(Context c, AttributeSet attrs, boolean isDozing, RippleShape rippleShape, + long duration) { + init(c, attrs, -1, -1, isDozing, rippleShape, duration); } private void init(Context context, AttributeSet attrs, int transmittingBatteryLevel, - int batteryLevel, boolean isDozing, RippleShape rippleShape) { + int batteryLevel, boolean isDozing, RippleShape rippleShape, long duration) { final boolean showTransmittingBatteryLevel = - (transmittingBatteryLevel != WirelessChargingAnimation.UNKNOWN_BATTERY_LEVEL); + (transmittingBatteryLevel != UNKNOWN_BATTERY_LEVEL); // set style based on background int style = R.style.ChargingAnim_WallpaperBackground; @@ -86,7 +93,7 @@ final class WirelessChargingLayout extends FrameLayout { // amount of battery: final TextView percentage = findViewById(R.id.wireless_charging_percentage); - if (batteryLevel != WirelessChargingAnimation.UNKNOWN_BATTERY_LEVEL) { + if (batteryLevel != UNKNOWN_BATTERY_LEVEL) { percentage.setText(NumberFormat.getPercentInstance().format(batteryLevel / 100f)); percentage.setAlpha(0); } @@ -125,7 +132,6 @@ final class WirelessChargingLayout extends FrameLayout { // play all animations together AnimatorSet animatorSet = new AnimatorSet(); animatorSet.playTogether(textSizeAnimator, textOpacityAnimator, textFadeAnimator); - ValueAnimator scrimFadeInAnimator = ObjectAnimator.ofArgb(this, "backgroundColor", Color.TRANSPARENT, SCRIM_COLOR); scrimFadeInAnimator.setDuration(SCRIM_FADE_DURATION); @@ -134,25 +140,21 @@ final class WirelessChargingLayout extends FrameLayout { "backgroundColor", SCRIM_COLOR, Color.TRANSPARENT); scrimFadeOutAnimator.setDuration(SCRIM_FADE_DURATION); scrimFadeOutAnimator.setInterpolator(Interpolators.LINEAR); - scrimFadeOutAnimator.setStartDelay((rippleShape == RippleShape.CIRCLE - ? CIRCLE_RIPPLE_ANIMATION_DURATION : ROUNDED_BOX_RIPPLE_ANIMATION_DURATION) - - SCRIM_FADE_DURATION); + scrimFadeOutAnimator.setStartDelay(duration - SCRIM_FADE_DURATION); AnimatorSet animatorSetScrim = new AnimatorSet(); animatorSetScrim.playTogether(scrimFadeInAnimator, scrimFadeOutAnimator); animatorSetScrim.start(); mRippleView = findViewById(R.id.wireless_charging_ripple); mRippleView.setupShader(rippleShape); + mRippleView.setDuration(duration); + int color = Utils.getColorAttr(mRippleView.getContext(), + android.R.attr.colorAccent).getDefaultColor(); if (mRippleView.getRippleShape() == RippleShape.ROUNDED_BOX) { - mRippleView.setDuration(ROUNDED_BOX_RIPPLE_ANIMATION_DURATION); mRippleView.setSparkleStrength(0.22f); - int color = Utils.getColorAttr(mRippleView.getContext(), - android.R.attr.colorAccent).getDefaultColor(); mRippleView.setColor(ColorUtils.setAlphaComponent(color, 28)); } else { - mRippleView.setDuration(CIRCLE_RIPPLE_ANIMATION_DURATION); - mRippleView.setColor(Utils.getColorAttr(mRippleView.getContext(), - android.R.attr.colorAccent).getDefaultColor()); + mRippleView.setColor(ColorUtils.setAlphaComponent(color, 45)); } OnAttachStateChangeListener listener = new OnAttachStateChangeListener() { diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingRippleController.kt b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingRippleController.kt new file mode 100644 index 000000000000..8b5c7eb2742a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingRippleController.kt @@ -0,0 +1,116 @@ +/* + * 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.charging + +import android.content.Context +import android.view.WindowManager +import androidx.annotation.VisibleForTesting +import com.android.internal.logging.UiEventLogger +import com.android.systemui.charging.WirelessChargingView.WirelessChargingRippleEvent +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.LogLevel +import com.android.systemui.log.dagger.ChargingLog +import com.android.systemui.util.concurrency.DelayableExecutor +import javax.inject.Inject + +const val UNKNOWN_BATTERY_LEVEL = -1 +const val DEFAULT_DURATION: Long = 1500 + +/** + * Controls the wireless charging animation. + */ +@SysUISingleton +class WirelessChargingRippleController @Inject constructor( + context: Context, + private val uiEventLogger: UiEventLogger, + @Main private val delayableExecutor: DelayableExecutor, + @ChargingLog private val logBuffer: LogBuffer +) { + private val windowManager: WindowManager = context.getSystemService(Context.WINDOW_SERVICE) + as WindowManager + + @VisibleForTesting + var wirelessChargingView: WirelessChargingView? = null + private var callback: Callback? = null + + companion object { + private const val TAG = "WirelessChargingRippleController" + } + + /** + * Shows the wireless charging view with the given delay. + * + * If there's already the animation is playing, the new request will be disregarded. + * @param wirelessChargingView WirelessChargingView to display. + * @param delay the start delay of the WirelessChargingView. + * @param callback optional callback that is triggered on animations start and end. + */ + fun show(wirelessChargingView: WirelessChargingView, delay: Long, callback: Callback? = null) { + // Avoid multiple animation getting triggered. + if (this.wirelessChargingView != null) { + logBuffer.log(TAG, LogLevel.INFO, "Already playing animation, disregard " + + "$wirelessChargingView") + return + } + + this.wirelessChargingView = wirelessChargingView + this.callback = callback + + logBuffer.log(TAG, LogLevel.DEBUG, "SHOW: $wirelessChargingView") + delayableExecutor.executeDelayed({ showInternal() }, delay) + + logBuffer.log(TAG, LogLevel.DEBUG, "HIDE: $wirelessChargingView") + delayableExecutor.executeDelayed({ hideInternal() }, delay + wirelessChargingView.duration) + } + + private fun showInternal() { + if (wirelessChargingView == null) { + return + } + + val chargingLayout = wirelessChargingView!!.getWirelessChargingLayout() + try { + callback?.onAnimationStarting() + windowManager.addView(chargingLayout, wirelessChargingView!!.wmLayoutParams) + uiEventLogger.log(WirelessChargingRippleEvent.WIRELESS_RIPPLE_PLAYED) + } catch (e: WindowManager.BadTokenException) { + logBuffer.log(TAG, LogLevel.ERROR, "Unable to add wireless charging view. $e") + } + } + + private fun hideInternal() { + wirelessChargingView?.getWirelessChargingLayout().let { + callback?.onAnimationEnded() + if (it?.parent != null) { + windowManager.removeViewImmediate(it) + } + } + wirelessChargingView = null + callback = null + } + + /** + * Callbacks that are triggered on animation events. + */ + interface Callback { + /** Triggered when the animation starts playing. */ + fun onAnimationStarting() + /** Triggered when the animation ends playing. */ + fun onAnimationEnded() + } +} diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingView.kt b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingView.kt new file mode 100644 index 000000000000..d510cf614083 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingView.kt @@ -0,0 +1,91 @@ +/* + * 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.charging + +import android.content.Context +import android.graphics.PixelFormat +import android.view.View +import android.view.WindowManager +import com.android.internal.logging.UiEvent +import com.android.internal.logging.UiEventLogger.UiEventEnum +import com.android.systemui.ripple.RippleShader.RippleShape + +/** + * WirelessChargingView that encapsulates the current and next [WirelessChargingLayout]s. + */ +class WirelessChargingView ( + context: Context, + transmittingBatteryLevel: Int, + batteryLevel: Int, + isDozing: Boolean, + rippleShape: RippleShape, + val duration: Long, +) { + companion object { + @JvmStatic + fun create( + context: Context, + transmittingBatteryLevel: Int, + batteryLevel: Int, + isDozing: Boolean, + rippleShape: RippleShape, + duration: Long = DEFAULT_DURATION + ): WirelessChargingView { + return WirelessChargingView(context, transmittingBatteryLevel, batteryLevel, isDozing, + rippleShape, duration) + } + + @JvmStatic + fun createWithNoBatteryLevel( + context: Context, + rippleShape: RippleShape, + duration: Long = DEFAULT_DURATION + ): WirelessChargingView { + return create(context, + UNKNOWN_BATTERY_LEVEL, UNKNOWN_BATTERY_LEVEL, false, rippleShape, + duration) + } + } + + val wmLayoutParams = WindowManager.LayoutParams().apply { + height = WindowManager.LayoutParams.MATCH_PARENT + width = WindowManager.LayoutParams.MATCH_PARENT + format = PixelFormat.TRANSLUCENT + type = WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG + title = "Charging Animation" + layoutInDisplayCutoutMode = + WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS + fitInsetsTypes = 0 + flags = (WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + or WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) + packageName = context.applicationContext.opPackageName + setTrustedOverlay() + } + + private val wirelessChargingLayout: WirelessChargingLayout = + WirelessChargingLayout(context, transmittingBatteryLevel, batteryLevel, isDozing, + rippleShape, duration) + fun getWirelessChargingLayout(): View = wirelessChargingLayout + + internal enum class WirelessChargingRippleEvent(private val mInt: Int) : UiEventEnum { + @UiEvent(doc = "Wireless charging ripple effect played") + WIRELESS_RIPPLE_PLAYED(830); + + override fun getId(): Int { + return mInt + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/ChargingLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/ChargingLog.java new file mode 100644 index 000000000000..3cfc22c5fbac --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/ChargingLog.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.log.dagger; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import com.android.systemui.log.LogBuffer; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import javax.inject.Qualifier; + +/** + * A {@link LogBuffer} for {@link com.android.systemui.charging} + */ +@Qualifier +@Documented +@Retention(RUNTIME) +public @interface ChargingLog { +} diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java index 9af42f825e00..15627207e8ed 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java @@ -316,4 +316,14 @@ public class LogModule { public static LogBuffer provideKeyguardViewMediatorLogBuffer(LogBufferFactory factory) { return factory.create("KeyguardViewMediatorLog", 100); } + + /** + * Provides a {@link LogBuffer} for use by {@link com.android.systemui.charging}. + */ + @Provides + @SysUISingleton + @ChargingLog + public static LogBuffer provideChargingLogBuffer(LogBufferFactory factory) { + return factory.create("ChargingLog", 20); + } } diff --git a/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt b/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt index db7c1fd86be0..d2f3a6a7bee1 100644 --- a/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt +++ b/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt @@ -68,7 +68,7 @@ class RippleShader internal constructor(rippleShape: RippleShape = RippleShape.C float rippleInsideAlpha = (1.-inside) * in_fadeFill; float rippleRingAlpha = (1.-sparkleRing) * in_fadeRing; float rippleAlpha = max(rippleInsideAlpha, rippleRingAlpha) * in_color.a; - vec4 ripple = in_color * rippleAlpha; + vec4 ripple = vec4(in_color.rgb, 1.0) * rippleAlpha; return mix(ripple, vec4(sparkle), sparkle * in_sparkle_strength); } """ @@ -84,7 +84,7 @@ class RippleShader internal constructor(rippleShape: RippleShape = RippleShape.C float rippleInsideAlpha = (1.-inside) * in_fadeFill; float rippleRingAlpha = (1.-sparkleRing) * in_fadeRing; float rippleAlpha = max(rippleInsideAlpha, rippleRingAlpha) * in_color.a; - vec4 ripple = in_color * rippleAlpha; + vec4 ripple = vec4(in_color.rgb, 1.0) * rippleAlpha; return mix(ripple, vec4(sparkle), sparkle * in_sparkle_strength); } """ @@ -100,7 +100,7 @@ class RippleShader internal constructor(rippleShape: RippleShape = RippleShape.C float rippleInsideAlpha = (1.-inside) * in_fadeFill; float rippleRingAlpha = (1.-sparkleRing) * in_fadeRing; float rippleAlpha = max(rippleInsideAlpha, rippleRingAlpha) * in_color.a; - vec4 ripple = in_color * rippleAlpha; + vec4 ripple = vec4(in_color.rgb, 1.0) * rippleAlpha; return mix(ripple, vec4(sparkle), sparkle * in_sparkle_strength); } """ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index a1904859cd4c..cb2ca51d7e16 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -31,7 +31,8 @@ import static androidx.core.view.ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_ import static androidx.lifecycle.Lifecycle.State.RESUMED; import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME; -import static com.android.systemui.charging.WirelessChargingAnimation.UNKNOWN_BATTERY_LEVEL; +import static com.android.systemui.charging.WirelessChargingRippleControllerKt.DEFAULT_DURATION; +import static com.android.systemui.charging.WirelessChargingRippleControllerKt.UNKNOWN_BATTERY_LEVEL; import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP; import static com.android.systemui.statusbar.NotificationLockscreenUserManager.PERMISSION_SELF; import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT; @@ -138,7 +139,8 @@ import com.android.systemui.biometrics.AuthRippleController; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.camera.CameraIntents; import com.android.systemui.charging.WiredChargingRippleController; -import com.android.systemui.charging.WirelessChargingAnimation; +import com.android.systemui.charging.WirelessChargingRippleController; +import com.android.systemui.charging.WirelessChargingView; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.dagger.SysUISingleton; @@ -507,6 +509,7 @@ public class CentralSurfacesImpl extends CoreStartable implements private final WallpaperManager mWallpaperManager; private CentralSurfacesComponent mCentralSurfacesComponent; + private WirelessChargingRippleController mWirelessChargingRippleController; // Flags for disabling the status bar // Two variables becaseu the first one evidently ran out of room for new flags. @@ -732,6 +735,7 @@ public class CentralSurfacesImpl extends CoreStartable implements InteractionJankMonitor jankMonitor, DeviceStateManager deviceStateManager, WiredChargingRippleController wiredChargingRippleController, + WirelessChargingRippleController wirelessChargingRippleController, IDreamManager dreamManager) { super(context); mNotificationsController = notificationsController; @@ -849,6 +853,7 @@ public class CentralSurfacesImpl extends CoreStartable implements deviceStateManager.registerCallback(mMainExecutor, new FoldStateListener(mContext, this::onFoldedStateChanged)); wiredChargingRippleController.registerCallbacks(); + mWirelessChargingRippleController = wirelessChargingRippleController; } @Override @@ -2162,9 +2167,11 @@ public class CentralSurfacesImpl extends CoreStartable implements protected void showChargingAnimation(int batteryLevel, int transmittingBatteryLevel, long animationDelay) { - WirelessChargingAnimation.makeWirelessChargingAnimation(mContext, null, - transmittingBatteryLevel, batteryLevel, - new WirelessChargingAnimation.Callback() { + WirelessChargingView wirelessChargingView = WirelessChargingView.create(mContext, + transmittingBatteryLevel, batteryLevel, /* isDozing= */ false, + RippleShape.CIRCLE, DEFAULT_DURATION); + mWirelessChargingRippleController.show(wirelessChargingView, animationDelay, + new WirelessChargingRippleController.Callback() { @Override public void onAnimationStarting() { mNotificationShadeWindowController.setRequestTopUi(true, TAG); @@ -2174,8 +2181,7 @@ public class CentralSurfacesImpl extends CoreStartable implements public void onAnimationEnded() { mNotificationShadeWindowController.setRequestTopUi(false, TAG); } - }, /* isDozing= */ false, RippleShape.CIRCLE, - sUiEventLogger).show(animationDelay); + }); } @Override diff --git a/packages/SystemUI/tests/src/com/android/systemui/charging/WirelessChargingRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/charging/WirelessChargingRippleControllerTest.kt new file mode 100644 index 000000000000..d967b5944bc5 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/charging/WirelessChargingRippleControllerTest.kt @@ -0,0 +1,135 @@ +/* + * 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.charging + +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper.RunWithLooper +import androidx.test.filters.SmallTest +import com.android.internal.logging.testing.UiEventLoggerFake +import com.android.systemui.SysuiTestCase +import com.android.systemui.dump.logcatLogBuffer +import com.android.systemui.ripple.RippleShader.RippleShape +import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.time.FakeSystemClock +import com.google.common.truth.Truth.assertThat +import junit.framework.Assert.assertEquals +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@RunWithLooper +class WirelessChargingRippleControllerTest : SysuiTestCase() { + + private val duration: Long = 1000L + private val fakeSystemClock: FakeSystemClock = FakeSystemClock() + private lateinit var wirelessChargingRippleController: WirelessChargingRippleController + private val fakeExecutor = FakeExecutor(fakeSystemClock) + + @Before + fun setUp() { + wirelessChargingRippleController = WirelessChargingRippleController( + context, UiEventLoggerFake(), fakeExecutor, logcatLogBuffer()) + fakeSystemClock.setElapsedRealtime(0L) + } + + @Test + fun showWirelessChargingView_hasCorrectDuration() { + val wirelessChargingView = WirelessChargingView.create( + context, UNKNOWN_BATTERY_LEVEL, UNKNOWN_BATTERY_LEVEL, false, + RippleShape.ROUNDED_BOX, duration + ) + + wirelessChargingRippleController.show(wirelessChargingView, 0) + fakeExecutor.runAllReady() + + assertEquals(duration, wirelessChargingView.duration) + } + + @Test + fun showWirelessChargingView_triggerWhilePlayingAnim_doesNotShowRipple() { + val wirelessChargingViewFirst = WirelessChargingView.create( + context, UNKNOWN_BATTERY_LEVEL, UNKNOWN_BATTERY_LEVEL, false, + RippleShape.ROUNDED_BOX, duration + ) + wirelessChargingRippleController.show(wirelessChargingViewFirst, 0) + assertThat(fakeExecutor.numPending()).isEqualTo(2) + fakeExecutor.runNextReady() // run showInternal + + // ensure we haven't run hideInternal, to simulate the first one is still playing. + assertThat(fakeExecutor.numPending()).isEqualTo(1) + + val wirelessChargingViewSecond = WirelessChargingView.create( + context, UNKNOWN_BATTERY_LEVEL, UNKNOWN_BATTERY_LEVEL, false, + RippleShape.ROUNDED_BOX, duration + ) + wirelessChargingRippleController.show(wirelessChargingViewSecond, 0) + + assertEquals(wirelessChargingViewFirst, + wirelessChargingRippleController.wirelessChargingView) + } + + @Test + fun hideWirelessChargingView_afterDuration() { + fakeSystemClock.setElapsedRealtime(0L) + val wirelessChargingView = WirelessChargingView.create( + context, UNKNOWN_BATTERY_LEVEL, UNKNOWN_BATTERY_LEVEL, false, + RippleShape.ROUNDED_BOX, duration + ) + wirelessChargingRippleController.show(wirelessChargingView, 0) + + assertThat(fakeExecutor.numPending()).isEqualTo(2) + with(fakeExecutor) { + runNextReady() // run showInternal + advanceClockToNext() + runNextReady() // run hideInternal + } + + assertEquals(null, wirelessChargingRippleController.wirelessChargingView) + } + + @Test + fun showWirelessChargingView_withCallback_triggersCallback() { + val callback = object : WirelessChargingRippleController.Callback { + var onAnimationStartingCalled = false + var onAnimationEndedCalled = false + + override fun onAnimationStarting() { + onAnimationStartingCalled = true + } + + override fun onAnimationEnded() { + onAnimationEndedCalled = true + } + } + val wirelessChargingView = WirelessChargingView.create( + context, UNKNOWN_BATTERY_LEVEL, UNKNOWN_BATTERY_LEVEL, false, + RippleShape.CIRCLE, duration + ) + wirelessChargingRippleController.show(wirelessChargingView, 0, callback) + assertThat(fakeExecutor.numPending()).isEqualTo(2) + + fakeExecutor.runNextReady() // run showInternal + assertEquals(true, callback.onAnimationStartingCalled) + + fakeExecutor.advanceClockToNext() + + fakeExecutor.runNextReady() // run hideInternal + assertEquals(true, callback.onAnimationEndedCalled) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java index a0f7087ddb9e..3443ae6b2efe 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java @@ -88,6 +88,7 @@ import com.android.systemui.animation.ActivityLaunchAnimator; import com.android.systemui.assist.AssistManager; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.charging.WiredChargingRippleController; +import com.android.systemui.charging.WirelessChargingRippleController; import com.android.systemui.classifier.FalsingCollectorFake; import com.android.systemui.classifier.FalsingManagerFake; import com.android.systemui.colorextraction.SysuiColorExtractor; @@ -283,6 +284,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { @Mock private InteractionJankMonitor mJankMonitor; @Mock private DeviceStateManager mDeviceStateManager; @Mock private WiredChargingRippleController mWiredChargingRippleController; + @Mock private WirelessChargingRippleController mWirelessChargingRippleController; private ShadeController mShadeController; private final FakeSystemClock mFakeSystemClock = new FakeSystemClock(); private FakeExecutor mMainExecutor = new FakeExecutor(mFakeSystemClock); @@ -465,7 +467,9 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mActivityLaunchAnimator, mJankMonitor, mDeviceStateManager, - mWiredChargingRippleController, mDreamManager); + mWiredChargingRippleController, + mWirelessChargingRippleController, + mDreamManager); when(mKeyguardViewMediator.registerCentralSurfaces( any(CentralSurfacesImpl.class), any(NotificationPanelViewController.class), |