Statusbar charging animation chip when plugging in device
Bug: 197638244 Test: Separate CL (ag/20982130) Change-Id: I93b8bb4e0eb277e9491e7667de0142f6b1501fea
This commit is contained in:
23
packages/SystemUI/res/drawable/statusbar_chip_bg.xml
Normal file
23
packages/SystemUI/res/drawable/statusbar_chip_bg.xml
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2023 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<shape
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
|
||||
<solid android:color="?androidprv:attr/colorAccentPrimary" />
|
||||
<corners android:radius="@dimen/ongoing_appops_chip_bg_corner_radius" />
|
||||
</shape>
|
||||
43
packages/SystemUI/res/layout/battery_status_chip.xml
Normal file
43
packages/SystemUI/res/layout/battery_status_chip.xml
Normal file
@@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
Copyright (C) 2023 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center_vertical|end"
|
||||
tools:parentTag="com.android.systemui.statusbar.BatteryStatusChip">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/rounded_container"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="@dimen/ongoing_appops_chip_height"
|
||||
android:layout_gravity="center"
|
||||
android:background="@drawable/statusbar_chip_bg"
|
||||
android:clipToOutline="true"
|
||||
android:gravity="center"
|
||||
android:maxWidth="@dimen/ongoing_appops_chip_max_width"
|
||||
android:minWidth="@dimen/ongoing_appops_chip_min_width">
|
||||
|
||||
<com.android.systemui.battery.BatteryMeterView
|
||||
android:id="@+id/battery_meter_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginHorizontal="10dp" />
|
||||
|
||||
</LinearLayout>
|
||||
</merge>
|
||||
@@ -22,10 +22,8 @@
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_gravity="center_vertical|end"
|
||||
android:focusable="true"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
android:paddingStart="8dp"
|
||||
>
|
||||
|
||||
<LinearLayout
|
||||
@@ -35,6 +33,8 @@
|
||||
android:paddingStart="10dp"
|
||||
android:paddingEnd="10dp"
|
||||
android:gravity="center"
|
||||
android:clipToOutline="true"
|
||||
android:clipToPadding="false"
|
||||
android:layout_gravity="center"
|
||||
android:minWidth="@dimen/ongoing_appops_chip_min_width"
|
||||
android:maxWidth="@dimen/ongoing_appops_chip_max_width"
|
||||
|
||||
@@ -24,6 +24,7 @@ import static java.lang.annotation.RetentionPolicy.SOURCE;
|
||||
import android.animation.LayoutTransition;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.annotation.IntDef;
|
||||
import android.annotation.IntRange;
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
@@ -195,7 +196,13 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver {
|
||||
return false;
|
||||
}
|
||||
|
||||
void onBatteryLevelChanged(int level, boolean pluggedIn) {
|
||||
/**
|
||||
* Update battery level
|
||||
*
|
||||
* @param level int between 0 and 100 (representing percentage value)
|
||||
* @param pluggedIn whether the device is plugged in or not
|
||||
*/
|
||||
public void onBatteryLevelChanged(@IntRange(from = 0, to = 100) int level, boolean pluggedIn) {
|
||||
mDrawable.setCharging(pluggedIn);
|
||||
mDrawable.setBatteryLevel(level);
|
||||
mCharging = pluggedIn;
|
||||
|
||||
@@ -298,6 +298,9 @@ object Flags {
|
||||
val NEW_STATUS_BAR_ICONS_DEBUG_COLORING =
|
||||
unreleasedFlag(611, "new_status_bar_icons_debug_coloring")
|
||||
|
||||
// TODO(b/265892345): Tracking Bug
|
||||
val PLUG_IN_STATUS_BAR_CHIP = unreleasedFlag(265892345, "plug_in_status_bar_chip")
|
||||
|
||||
// 700 - dialer/calls
|
||||
// TODO(b/254512734): Tracking Bug
|
||||
val ONGOING_CALL_STATUS_BAR_CHIP = releasedFlag(700, "ongoing_call_status_bar_chip")
|
||||
|
||||
@@ -107,6 +107,6 @@ class OngoingPrivacyChip @JvmOverloads constructor(
|
||||
val padding = context.resources
|
||||
.getDimensionPixelSize(R.dimen.ongoing_appops_chip_side_padding)
|
||||
iconsContainer.setPaddingRelative(padding, 0, padding, 0)
|
||||
iconsContainer.background = context.getDrawable(R.drawable.privacy_chip_bg)
|
||||
iconsContainer.background = context.getDrawable(R.drawable.statusbar_privacy_chip_bg)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
|
||||
* except in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the
|
||||
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the specific language governing
|
||||
* permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.statusbar
|
||||
|
||||
import android.annotation.IntRange
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.LinearLayout
|
||||
import com.android.settingslib.Utils
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.battery.BatteryMeterView
|
||||
import com.android.systemui.statusbar.events.BackgroundAnimatableView
|
||||
|
||||
class BatteryStatusChip @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
|
||||
FrameLayout(context, attrs), BackgroundAnimatableView {
|
||||
|
||||
private val roundedContainer: LinearLayout
|
||||
private val batteryMeterView: BatteryMeterView
|
||||
override val contentView: View
|
||||
get() = batteryMeterView
|
||||
|
||||
init {
|
||||
inflate(context, R.layout.battery_status_chip, this)
|
||||
roundedContainer = findViewById(R.id.rounded_container)
|
||||
batteryMeterView = findViewById(R.id.battery_meter_view)
|
||||
updateResources()
|
||||
}
|
||||
|
||||
/**
|
||||
* When animating as a chip in the status bar, we want to animate the width for the rounded
|
||||
* container. We have to subtract our own top and left offset because the bounds come to us as
|
||||
* absolute on-screen bounds.
|
||||
*/
|
||||
override fun setBoundsForAnimation(l: Int, t: Int, r: Int, b: Int) {
|
||||
roundedContainer.setLeftTopRightBottom(l - left, t - top, r - left, b - top)
|
||||
}
|
||||
|
||||
fun setBatteryLevel(@IntRange(from = 0, to = 100) batteryLevel: Int) {
|
||||
batteryMeterView.setForceShowPercent(true)
|
||||
batteryMeterView.onBatteryLevelChanged(batteryLevel, true)
|
||||
}
|
||||
|
||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||
super.onConfigurationChanged(newConfig)
|
||||
updateResources()
|
||||
}
|
||||
|
||||
@SuppressLint("UseCompatLoadingForDrawables")
|
||||
private fun updateResources() {
|
||||
val primaryColor =
|
||||
Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.colorPrimary)
|
||||
val textColorSecondary =
|
||||
Utils.getColorAttrDefaultColor(mContext, android.R.attr.textColorSecondary)
|
||||
batteryMeterView.updateColors(primaryColor, textColorSecondary, primaryColor)
|
||||
roundedContainer.background = mContext.getDrawable(R.drawable.statusbar_chip_bg)
|
||||
}
|
||||
}
|
||||
@@ -16,17 +16,16 @@
|
||||
|
||||
package com.android.systemui.statusbar.events
|
||||
|
||||
import android.annotation.IntRange
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import com.android.settingslib.graph.ThemedBatteryDrawable
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.privacy.OngoingPrivacyChip
|
||||
import com.android.systemui.privacy.PrivacyItem
|
||||
import com.android.systemui.statusbar.BatteryStatusChip
|
||||
|
||||
typealias ViewCreator = (context: Context) -> BackgroundAnimatableView
|
||||
|
||||
@@ -73,17 +72,16 @@ class BGImageView(
|
||||
}
|
||||
}
|
||||
|
||||
class BatteryEvent : StatusEvent {
|
||||
class BatteryEvent(@IntRange(from = 0, to = 100) val batteryLevel: Int) : StatusEvent {
|
||||
override val priority = 50
|
||||
override val forceVisible = false
|
||||
override val showAnimation = true
|
||||
override var contentDescription: String? = ""
|
||||
|
||||
override val viewCreator: (context: Context) -> BGImageView = { context ->
|
||||
val iv = BGImageView(context)
|
||||
iv.setImageDrawable(ThemedBatteryDrawable(context, Color.WHITE))
|
||||
iv.setBackgroundDrawable(ColorDrawable(Color.GREEN))
|
||||
iv
|
||||
override val viewCreator: ViewCreator = { context ->
|
||||
BatteryStatusChip(context).apply {
|
||||
setBatteryLevel(batteryLevel)
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
|
||||
@@ -117,16 +117,21 @@ class SystemEventChipAnimationController @Inject constructor(
|
||||
interpolator = null
|
||||
addUpdateListener { currentAnimatedView?.view?.alpha = animatedValue as Float }
|
||||
}
|
||||
currentAnimatedView?.contentView?.alpha = 0f
|
||||
val contentAlphaIn = ValueAnimator.ofFloat(0f, 1f).apply {
|
||||
startDelay = 10.frames
|
||||
duration = 10.frames
|
||||
interpolator = null
|
||||
addUpdateListener { currentAnimatedView?.contentView?.alpha = animatedValue as Float }
|
||||
}
|
||||
val moveIn = ValueAnimator.ofInt(chipMinWidth, chipWidth).apply {
|
||||
startDelay = 7.frames
|
||||
duration = 23.frames
|
||||
interpolator = STATUS_BAR_X_MOVE_IN
|
||||
addUpdateListener {
|
||||
updateAnimatedViewBoundsWidth(animatedValue as Int)
|
||||
}
|
||||
addUpdateListener { updateAnimatedViewBoundsWidth(animatedValue as Int) }
|
||||
}
|
||||
val animSet = AnimatorSet()
|
||||
animSet.playTogether(alphaIn, moveIn)
|
||||
animSet.playTogether(alphaIn, contentAlphaIn, moveIn)
|
||||
return animSet
|
||||
}
|
||||
|
||||
@@ -210,15 +215,32 @@ class SystemEventChipAnimationController @Inject constructor(
|
||||
}
|
||||
|
||||
private fun createMoveOutAnimationDefault(): Animator {
|
||||
val alphaOut = ValueAnimator.ofFloat(1f, 0f).apply {
|
||||
startDelay = 6.frames
|
||||
duration = 6.frames
|
||||
interpolator = null
|
||||
addUpdateListener { currentAnimatedView?.view?.alpha = animatedValue as Float }
|
||||
}
|
||||
|
||||
val contentAlphaOut = ValueAnimator.ofFloat(1f, 0f).apply {
|
||||
duration = 5.frames
|
||||
interpolator = null
|
||||
addUpdateListener { currentAnimatedView?.contentView?.alpha = animatedValue as Float }
|
||||
}
|
||||
|
||||
val moveOut = ValueAnimator.ofInt(chipWidth, chipMinWidth).apply {
|
||||
duration = 23.frames
|
||||
interpolator = STATUS_BAR_X_MOVE_OUT
|
||||
addUpdateListener {
|
||||
currentAnimatedView?.apply {
|
||||
updateAnimatedViewBoundsWidth(it.animatedValue as Int)
|
||||
}
|
||||
}
|
||||
}
|
||||
return moveOut
|
||||
|
||||
val animSet = AnimatorSet()
|
||||
animSet.playTogether(alphaOut, contentAlphaOut, moveOut)
|
||||
return animSet
|
||||
}
|
||||
|
||||
private fun init() {
|
||||
@@ -296,6 +318,8 @@ class SystemEventChipAnimationController @Inject constructor(
|
||||
interface BackgroundAnimatableView {
|
||||
val view: View // Since this can't extend View, add a view prop
|
||||
get() = this as View
|
||||
val contentView: View? // This will be alpha faded during appear and disappear animation
|
||||
get() = null
|
||||
val chipWidth: Int
|
||||
get() = view.measuredWidth
|
||||
fun setBoundsForAnimation(l: Int, t: Int, r: Int, b: Int)
|
||||
|
||||
@@ -16,11 +16,14 @@
|
||||
|
||||
package com.android.systemui.statusbar.events
|
||||
|
||||
import android.annotation.IntRange
|
||||
import android.content.Context
|
||||
import android.provider.DeviceConfig
|
||||
import android.provider.DeviceConfig.NAMESPACE_PRIVACY
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.flags.FeatureFlags
|
||||
import com.android.systemui.flags.Flags
|
||||
import com.android.systemui.privacy.PrivacyChipBuilder
|
||||
import com.android.systemui.privacy.PrivacyItem
|
||||
import com.android.systemui.privacy.PrivacyItemController
|
||||
@@ -37,21 +40,18 @@ class SystemEventCoordinator @Inject constructor(
|
||||
private val systemClock: SystemClock,
|
||||
private val batteryController: BatteryController,
|
||||
private val privacyController: PrivacyItemController,
|
||||
private val context: Context
|
||||
private val context: Context,
|
||||
private val featureFlags: FeatureFlags
|
||||
) {
|
||||
private lateinit var scheduler: SystemStatusAnimationScheduler
|
||||
|
||||
fun startObserving() {
|
||||
/* currently unused
|
||||
batteryController.addCallback(batteryStateListener)
|
||||
*/
|
||||
privacyController.addCallback(privacyStateListener)
|
||||
}
|
||||
|
||||
fun stopObserving() {
|
||||
/* currently unused
|
||||
batteryController.removeCallback(batteryStateListener)
|
||||
*/
|
||||
privacyController.removeCallback(privacyStateListener)
|
||||
}
|
||||
|
||||
@@ -59,8 +59,10 @@ class SystemEventCoordinator @Inject constructor(
|
||||
this.scheduler = s
|
||||
}
|
||||
|
||||
fun notifyPluggedIn() {
|
||||
scheduler.onStatusEvent(BatteryEvent())
|
||||
fun notifyPluggedIn(@IntRange(from = 0, to = 100) batteryLevel: Int) {
|
||||
if (featureFlags.isEnabled(Flags.PLUG_IN_STATUS_BAR_CHIP)) {
|
||||
scheduler.onStatusEvent(BatteryEvent(batteryLevel))
|
||||
}
|
||||
}
|
||||
|
||||
fun notifyPrivacyItemsEmpty() {
|
||||
@@ -79,25 +81,25 @@ class SystemEventCoordinator @Inject constructor(
|
||||
}
|
||||
|
||||
private val batteryStateListener = object : BatteryController.BatteryStateChangeCallback {
|
||||
var plugged = false
|
||||
var stateKnown = false
|
||||
private var plugged = false
|
||||
private var stateKnown = false
|
||||
override fun onBatteryLevelChanged(level: Int, pluggedIn: Boolean, charging: Boolean) {
|
||||
if (!stateKnown) {
|
||||
stateKnown = true
|
||||
plugged = pluggedIn
|
||||
notifyListeners()
|
||||
notifyListeners(level)
|
||||
return
|
||||
}
|
||||
|
||||
if (plugged != pluggedIn) {
|
||||
plugged = pluggedIn
|
||||
notifyListeners()
|
||||
notifyListeners(level)
|
||||
}
|
||||
}
|
||||
|
||||
private fun notifyListeners() {
|
||||
private fun notifyListeners(@IntRange(from = 0, to = 100) batteryLevel: Int) {
|
||||
// We only care about the plugged in status
|
||||
if (plugged) notifyPluggedIn()
|
||||
if (plugged) notifyPluggedIn(batteryLevel)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -66,7 +66,8 @@ open class SystemStatusAnimationScheduler @Inject constructor(
|
||||
companion object {
|
||||
private const val PROPERTY_ENABLE_IMMERSIVE_INDICATOR = "enable_immersive_indicator"
|
||||
}
|
||||
public fun isImmersiveIndicatorEnabled(): Boolean {
|
||||
|
||||
fun isImmersiveIndicatorEnabled(): Boolean {
|
||||
return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
|
||||
PROPERTY_ENABLE_IMMERSIVE_INDICATOR, true)
|
||||
}
|
||||
@@ -80,11 +81,7 @@ open class SystemStatusAnimationScheduler @Inject constructor(
|
||||
|
||||
private var scheduledEvent: StatusEvent? = null
|
||||
private var cancelExecutionRunnable: Runnable? = null
|
||||
private val listeners = mutableSetOf<SystemStatusAnimationCallback>()
|
||||
|
||||
fun getListeners(): MutableSet<SystemStatusAnimationCallback> {
|
||||
return listeners
|
||||
}
|
||||
val listeners = mutableSetOf<SystemStatusAnimationCallback>()
|
||||
|
||||
init {
|
||||
coordinator.attachScheduler(this)
|
||||
@@ -99,9 +96,8 @@ open class SystemStatusAnimationScheduler @Inject constructor(
|
||||
|
||||
// Don't deal with threading for now (no need let's be honest)
|
||||
Assert.isMainThread()
|
||||
if ((event.priority > scheduledEvent?.priority ?: -1) &&
|
||||
animationState != ANIMATING_OUT &&
|
||||
(animationState != SHOWING_PERSISTENT_DOT && event.forceVisible)) {
|
||||
if ((event.priority > (scheduledEvent?.priority ?: -1)) &&
|
||||
animationState != ANIMATING_OUT && animationState != SHOWING_PERSISTENT_DOT) {
|
||||
// events can only be scheduled if a higher priority or no other event is in progress
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "scheduling event $event")
|
||||
@@ -143,7 +139,7 @@ open class SystemStatusAnimationScheduler @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
public fun isTooEarly(): Boolean {
|
||||
fun isTooEarly(): Boolean {
|
||||
return systemClock.uptimeMillis() - Process.getStartUptimeMillis() < MIN_UPTIME
|
||||
}
|
||||
|
||||
|
||||
@@ -64,14 +64,14 @@ class StatusBarSystemEventAnimator(
|
||||
|
||||
override fun onSystemEventAnimationFinish(hasPersistentDot: Boolean): Animator {
|
||||
animatedView.translationX = translationXOut.toFloat()
|
||||
val moveIn = ValueAnimator.ofFloat(1f, 0f).setDuration(28.frames)
|
||||
moveIn.startDelay = 2.frames
|
||||
val moveIn = ValueAnimator.ofFloat(1f, 0f).setDuration(23.frames)
|
||||
moveIn.startDelay = 7.frames
|
||||
moveIn.interpolator = STATUS_BAR_X_MOVE_IN
|
||||
moveIn.addUpdateListener { animation: ValueAnimator ->
|
||||
animatedView.translationX = translationXOut * animation.animatedValue as Float
|
||||
}
|
||||
val alphaIn = ValueAnimator.ofFloat(0f, 1f).setDuration(10.frames)
|
||||
alphaIn.startDelay = 4.frames
|
||||
val alphaIn = ValueAnimator.ofFloat(0f, 1f).setDuration(5.frames)
|
||||
alphaIn.startDelay = 11.frames
|
||||
alphaIn.interpolator = null
|
||||
alphaIn.addUpdateListener { animation: ValueAnimator ->
|
||||
animatedView.alpha = animation.animatedValue as Float
|
||||
|
||||
Reference in New Issue
Block a user