Merge "Revert "Refactor WirelessChargingAnimation.""
This commit is contained in:
committed by
Android (Google) Code Review
commit
a230afa84a
@@ -0,0 +1,254 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,9 +16,6 @@
|
||||
|
||||
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;
|
||||
@@ -44,43 +41,39 @@ import com.android.systemui.ripple.RippleView;
|
||||
import java.text.NumberFormat;
|
||||
|
||||
/**
|
||||
* Layout that is used for wireless charging.
|
||||
*
|
||||
* <p>Wireless charging layout has {@link RippleView} and text view to show the battery level.
|
||||
* @hide
|
||||
*/
|
||||
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, long duration) {
|
||||
boolean isDozing, RippleShape rippleShape) {
|
||||
super(context);
|
||||
init(context, null, transmittingBatteryLevel, batteryLevel, isDozing, rippleShape,
|
||||
duration);
|
||||
init(context, null, transmittingBatteryLevel, batteryLevel, isDozing, rippleShape);
|
||||
}
|
||||
|
||||
private WirelessChargingLayout(Context context) {
|
||||
super(context);
|
||||
init(context, null, /* isDozing= */ false, RippleShape.CIRCLE,
|
||||
DEFAULT_DURATION);
|
||||
init(context, null, /* isDozing= */ false, RippleShape.CIRCLE);
|
||||
}
|
||||
|
||||
private WirelessChargingLayout(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init(context, attrs, /* isDozing= */false, RippleShape.CIRCLE,
|
||||
DEFAULT_DURATION);
|
||||
init(context, attrs, /* isDozing= */false, RippleShape.CIRCLE);
|
||||
}
|
||||
|
||||
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 c, AttributeSet attrs, boolean isDozing, RippleShape rippleShape) {
|
||||
init(c, attrs, -1, -1, isDozing, rippleShape);
|
||||
}
|
||||
|
||||
private void init(Context context, AttributeSet attrs, int transmittingBatteryLevel,
|
||||
int batteryLevel, boolean isDozing, RippleShape rippleShape, long duration) {
|
||||
int batteryLevel, boolean isDozing, RippleShape rippleShape) {
|
||||
final boolean showTransmittingBatteryLevel =
|
||||
(transmittingBatteryLevel != UNKNOWN_BATTERY_LEVEL);
|
||||
(transmittingBatteryLevel != WirelessChargingAnimation.UNKNOWN_BATTERY_LEVEL);
|
||||
|
||||
// set style based on background
|
||||
int style = R.style.ChargingAnim_WallpaperBackground;
|
||||
@@ -93,7 +86,7 @@ final class WirelessChargingLayout extends FrameLayout {
|
||||
// amount of battery:
|
||||
final TextView percentage = findViewById(R.id.wireless_charging_percentage);
|
||||
|
||||
if (batteryLevel != UNKNOWN_BATTERY_LEVEL) {
|
||||
if (batteryLevel != WirelessChargingAnimation.UNKNOWN_BATTERY_LEVEL) {
|
||||
percentage.setText(NumberFormat.getPercentInstance().format(batteryLevel / 100f));
|
||||
percentage.setAlpha(0);
|
||||
}
|
||||
@@ -132,6 +125,7 @@ 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);
|
||||
@@ -140,21 +134,25 @@ final class WirelessChargingLayout extends FrameLayout {
|
||||
"backgroundColor", SCRIM_COLOR, Color.TRANSPARENT);
|
||||
scrimFadeOutAnimator.setDuration(SCRIM_FADE_DURATION);
|
||||
scrimFadeOutAnimator.setInterpolator(Interpolators.LINEAR);
|
||||
scrimFadeOutAnimator.setStartDelay(duration - SCRIM_FADE_DURATION);
|
||||
scrimFadeOutAnimator.setStartDelay((rippleShape == RippleShape.CIRCLE
|
||||
? CIRCLE_RIPPLE_ANIMATION_DURATION : ROUNDED_BOX_RIPPLE_ANIMATION_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.setColor(ColorUtils.setAlphaComponent(color, 45));
|
||||
mRippleView.setDuration(CIRCLE_RIPPLE_ANIMATION_DURATION);
|
||||
mRippleView.setColor(Utils.getColorAttr(mRippleView.getContext(),
|
||||
android.R.attr.colorAccent).getDefaultColor());
|
||||
}
|
||||
|
||||
OnAttachStateChangeListener listener = new OnAttachStateChangeListener() {
|
||||
|
||||
@@ -1,116 +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.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()
|
||||
}
|
||||
}
|
||||
@@ -1,91 +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.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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,35 +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.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 {
|
||||
}
|
||||
@@ -316,14 +316,4 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 = vec4(in_color.rgb, 1.0) * rippleAlpha;
|
||||
vec4 ripple = in_color * 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 = vec4(in_color.rgb, 1.0) * rippleAlpha;
|
||||
vec4 ripple = in_color * 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 = vec4(in_color.rgb, 1.0) * rippleAlpha;
|
||||
vec4 ripple = in_color * rippleAlpha;
|
||||
return mix(ripple, vec4(sparkle), sparkle * in_sparkle_strength);
|
||||
}
|
||||
"""
|
||||
|
||||
@@ -31,8 +31,7 @@ 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.WirelessChargingRippleControllerKt.DEFAULT_DURATION;
|
||||
import static com.android.systemui.charging.WirelessChargingRippleControllerKt.UNKNOWN_BATTERY_LEVEL;
|
||||
import static com.android.systemui.charging.WirelessChargingAnimation.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;
|
||||
@@ -139,8 +138,7 @@ 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.WirelessChargingRippleController;
|
||||
import com.android.systemui.charging.WirelessChargingView;
|
||||
import com.android.systemui.charging.WirelessChargingAnimation;
|
||||
import com.android.systemui.classifier.FalsingCollector;
|
||||
import com.android.systemui.colorextraction.SysuiColorExtractor;
|
||||
import com.android.systemui.dagger.SysUISingleton;
|
||||
@@ -509,7 +507,6 @@ 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.
|
||||
@@ -735,7 +732,6 @@ public class CentralSurfacesImpl extends CoreStartable implements
|
||||
InteractionJankMonitor jankMonitor,
|
||||
DeviceStateManager deviceStateManager,
|
||||
WiredChargingRippleController wiredChargingRippleController,
|
||||
WirelessChargingRippleController wirelessChargingRippleController,
|
||||
IDreamManager dreamManager) {
|
||||
super(context);
|
||||
mNotificationsController = notificationsController;
|
||||
@@ -853,7 +849,6 @@ public class CentralSurfacesImpl extends CoreStartable implements
|
||||
deviceStateManager.registerCallback(mMainExecutor,
|
||||
new FoldStateListener(mContext, this::onFoldedStateChanged));
|
||||
wiredChargingRippleController.registerCallbacks();
|
||||
mWirelessChargingRippleController = wirelessChargingRippleController;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -2167,11 +2162,9 @@ public class CentralSurfacesImpl extends CoreStartable implements
|
||||
|
||||
protected void showChargingAnimation(int batteryLevel, int transmittingBatteryLevel,
|
||||
long animationDelay) {
|
||||
WirelessChargingView wirelessChargingView = WirelessChargingView.create(mContext,
|
||||
transmittingBatteryLevel, batteryLevel, /* isDozing= */ false,
|
||||
RippleShape.CIRCLE, DEFAULT_DURATION);
|
||||
mWirelessChargingRippleController.show(wirelessChargingView, animationDelay,
|
||||
new WirelessChargingRippleController.Callback() {
|
||||
WirelessChargingAnimation.makeWirelessChargingAnimation(mContext, null,
|
||||
transmittingBatteryLevel, batteryLevel,
|
||||
new WirelessChargingAnimation.Callback() {
|
||||
@Override
|
||||
public void onAnimationStarting() {
|
||||
mNotificationShadeWindowController.setRequestTopUi(true, TAG);
|
||||
@@ -2181,7 +2174,8 @@ public class CentralSurfacesImpl extends CoreStartable implements
|
||||
public void onAnimationEnded() {
|
||||
mNotificationShadeWindowController.setRequestTopUi(false, TAG);
|
||||
}
|
||||
});
|
||||
}, /* isDozing= */ false, RippleShape.CIRCLE,
|
||||
sUiEventLogger).show(animationDelay);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,135 +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.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)
|
||||
}
|
||||
}
|
||||
@@ -88,7 +88,6 @@ 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;
|
||||
@@ -284,7 +283,6 @@ 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);
|
||||
@@ -467,9 +465,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
|
||||
mActivityLaunchAnimator,
|
||||
mJankMonitor,
|
||||
mDeviceStateManager,
|
||||
mWiredChargingRippleController,
|
||||
mWirelessChargingRippleController,
|
||||
mDreamManager);
|
||||
mWiredChargingRippleController, mDreamManager);
|
||||
when(mKeyguardViewMediator.registerCentralSurfaces(
|
||||
any(CentralSurfacesImpl.class),
|
||||
any(NotificationPanelViewController.class),
|
||||
|
||||
Reference in New Issue
Block a user