summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Yein Jo <yeinj@google.com> 2022-08-10 16:38:51 +0000
committer Yein Jo <yeinj@google.com> 2022-08-26 20:24:16 +0000
commit77b4efa8a787a113517d2d6123e0c7965b5725c7 (patch)
tree4bb1fb30fa6c1dc29dbb373b27953154da54248f
parent791a2c7909aa65c9912b6d8ecfb02e42929f3e87 (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
-rw-r--r--packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java254
-rw-r--r--packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java46
-rw-r--r--packages/SystemUI/src/com/android/systemui/charging/WirelessChargingRippleController.kt116
-rw-r--r--packages/SystemUI/src/com/android/systemui/charging/WirelessChargingView.kt91
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/ChargingLog.java35
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java20
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/charging/WirelessChargingRippleControllerTest.kt135
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java6
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),