Add Play/Pause Icon & Background Animations

Fixes: 220876706
Test: Manual using video in chrome
Change-Id: I3100ba553554e2c6fdd4c3a8ed6a4d8bebb18d0a
This commit is contained in:
Hawkwood Glazier
2022-03-17 16:36:57 +00:00
parent 4f027ec47c
commit 0b28a7b48d
10 changed files with 531 additions and 97 deletions

View File

@@ -1,26 +1,91 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- <!--
~ Copyright (C) 2022 The Android Open Source Project ~ Copyright (C) 2022 The Android Open Source Project
~ ~
~ Licensed under the Apache License, Version 2.0 (the "License"); ~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License. ~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at ~ You may obtain a copy of the License at
~ ~
~ http://www.apache.org/licenses/LICENSE-2.0 ~ http://www.apache.org/licenses/LICENSE-2.0
~ ~
~ Unless required by applicable law or agreed to in writing, software ~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS, ~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and ~ See the License for the specific language governing permissions and
~ limitations under the License ~ limitations under the License
--> -->
<vector xmlns:android="http://schemas.android.com/apk/res/android" <animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="14dp" xmlns:aapt="http://schemas.android.com/aapt">
android:height="16dp" <aapt:attr name="android:drawable">
android:viewportWidth="14" <vector android:height="24dp"
android:viewportHeight="16"> android:width="24dp"
<path android:viewportHeight="24"
android:pathData="M9.1818,15.6363H13.5455V0.3635H9.1818V15.6363ZM0.4546,15.6363H4.8182V0.3635H0.4546V15.6363Z" android:viewportWidth="24">
android:fillColor="#FFFFFF" <group android:name="_R_G">
android:fillType="evenOdd"/> <group android:name="_R_G_L_1_G"
</vector> android:translateX="12"
android:translateY="12.125">
<path android:name="_R_G_L_1_G_D_0_P_0"
android:fillColor="#ffffff"
android:fillAlpha="1"
android:fillType="nonZero"
android:pathData=" M-6 7 C-6,7 -2,7 -2,7 C-2,7 -2,-7 -2,-7 C-2,-7 -6,-7 -6,-7 C-6,-7 -6,7 -6,7c "/>
</group>
<group android:name="_R_G_L_0_G"
android:translateX="12"
android:translateY="12.125">
<path android:name="_R_G_L_0_G_D_0_P_0"
android:fillColor="#ffffff"
android:fillAlpha="1"
android:fillType="nonZero"
android:pathData=" M2 7 C2,7 6,7 6,7 C6,7 6,-0.12 6,-0.12 C6,-0.12 6,-7 6,-7 C6,-7 2,-7 2,-7 C2,-7 2,7 2,7c "/>
</group>
</group>
<group android:name="time_group"/>
</vector>
</aapt:attr>
<target android:name="_R_G_L_1_G_D_0_P_0">
<aapt:attr name="android:animation">
<set android:ordering="together">
<objectAnimator android:propertyName="pathData"
android:duration="333"
android:startOffset="0"
android:valueFrom="M-6 7 C-6,7 -2,7 -2,7 C-2,7 -2,-7 -2,-7 C-2,-7 -6,-7 -6,-7 C-6,-7 -6,7 -6,7c "
android:valueTo="M-6 7 C-6,7 -2,3.94 -2,3.94 C-2,3.94 -2,-4.37 -2,-4.37 C-2,-4.37 -6,-7 -6,-7 C-6,-7 -6,7 -6,7c "
android:valueType="pathType">
<aapt:attr name="android:interpolator">
<pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/>
</aapt:attr>
</objectAnimator>
</set>
</aapt:attr>
</target>
<target android:name="_R_G_L_0_G_D_0_P_0">
<aapt:attr name="android:animation">
<set android:ordering="together">
<objectAnimator android:propertyName="pathData"
android:duration="333"
android:startOffset="0"
android:valueFrom="M2 7 C2,7 6,7 6,7 C6,7 6,-0.12 6,-0.12 C6,-0.12 6,-7 6,-7 C6,-7 2,-7 2,-7 C2,-7 2,7 2,7c "
android:valueTo="M-5.62 7 C-5.62,7 -4.75,7 -4.75,7 C-4.75,7 6,-0.12 6,-0.12 C6,-0.12 -4.44,-7 -4.44,-7 C-4.44,-7 -5.62,-7 -5.62,-7 C-5.62,-7 -5.62,7 -5.62,7c "
android:valueType="pathType">
<aapt:attr name="android:interpolator">
<pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/>
</aapt:attr>
</objectAnimator>
</set>
</aapt:attr>
</target>
<target android:name="time_group">
<aapt:attr name="android:animation">
<set android:ordering="together">
<objectAnimator android:propertyName="translateX"
android:duration="517"
android:startOffset="0"
android:valueFrom="0"
android:valueTo="1"
android:valueType="floatType"/>
</set>
</aapt:attr>
</target>
</animated-vector>

View File

@@ -0,0 +1,73 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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
-->
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt">
<aapt:attr name="android:drawable">
<vector android:height="48dp"
android:width="48dp"
android:viewportHeight="48"
android:viewportWidth="48">
<group android:name="_R_G">
<group android:name="_R_G_L_1_G"
android:translateX="24"
android:translateY="24"
android:scaleX="0.5"
android:scaleY="0.5"/>
<group android:name="_R_G_L_0_G"
android:translateX="24"
android:translateY="24"
android:scaleX="0.5"
android:scaleY="0.5">
<path android:name="_R_G_L_0_G_D_0_P_0"
android:fillColor="#ffddb3"
android:fillAlpha="1"
android:fillType="nonZero"
android:pathData=" M48 -16 C48,-16 48,16 48,16 C48,33.67 33.67,48 16,48 C16,48 -16,48 -16,48 C-33.67,48 -48,33.67 -48,16 C-48,16 -48,-16 -48,-16 C-48,-33.67 -33.67,-48 -16,-48 C-16,-48 16,-48 16,-48 C33.67,-48 48,-33.67 48,-16c "/>
</group>
</group>
<group android:name="time_group"/>
</vector>
</aapt:attr>
<target android:name="_R_G_L_0_G_D_0_P_0">
<aapt:attr name="android:animation">
<set android:ordering="together">
<objectAnimator android:propertyName="pathData"
android:duration="500"
android:startOffset="0"
android:valueFrom="M48 -16 C48,-16 48,16 48,16 C48,33.67 33.67,48 16,48 C16,48 -16,48 -16,48 C-33.67,48 -48,33.67 -48,16 C-48,16 -48,-16 -48,-16 C-48,-33.67 -33.67,-48 -16,-48 C-16,-48 16,-48 16,-48 C33.67,-48 48,-33.67 48,-16c "
android:valueTo="M48 0.25 C48,0.25 48,0 48,0 C47.75,26 31.25,48 0,48 C0,48 0,48 0,48 C-30,48 -48,25.75 -48,-0.25 C-48,-0.25 -48,-0.25 -48,-0.25 C-47.75,-23.5 -32.25,-47.75 0.5,-48 C0.5,-48 0.5,-48 0.5,-48 C28,-47.75 47.75,-29.75 48,0.25c "
android:valueType="pathType">
<aapt:attr name="android:interpolator">
<pathInterpolator android:pathData="M 0.0,0.0 c0.526,0 0,1 1.0,1.0"/>
</aapt:attr>
</objectAnimator>
</set>
</aapt:attr>
</target>
<target android:name="time_group">
<aapt:attr name="android:animation">
<set android:ordering="together">
<objectAnimator android:propertyName="translateX"
android:duration="517"
android:startOffset="0"
android:valueFrom="0"
android:valueTo="1"
android:valueType="floatType"/>
</set>
</aapt:attr>
</target>
</animated-vector>

View File

@@ -1,26 +1,91 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- <!--
~ Copyright (C) 2022 The Android Open Source Project ~ Copyright (C) 2022 The Android Open Source Project
~ ~
~ Licensed under the Apache License, Version 2.0 (the "License"); ~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License. ~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at ~ You may obtain a copy of the License at
~ ~
~ http://www.apache.org/licenses/LICENSE-2.0 ~ http://www.apache.org/licenses/LICENSE-2.0
~ ~
~ Unless required by applicable law or agreed to in writing, software ~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS, ~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and ~ See the License for the specific language governing permissions and
~ limitations under the License ~ limitations under the License
--> -->
<vector xmlns:android="http://schemas.android.com/apk/res/android" <animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" xmlns:aapt="http://schemas.android.com/aapt">
android:height="24dp" <aapt:attr name="android:drawable">
android:viewportWidth="24" <vector android:height="24dp"
android:viewportHeight="24"> android:width="24dp"
<path android:viewportHeight="24"
android:pathData="M20,12L6,21V3L20,12ZM15.26,12L8.55,7.68V16.32L15.26,12Z" android:viewportWidth="24">
android:fillColor="#FFFFFF" <group android:name="_R_G">
android:fillType="evenOdd"/> <group android:name="_R_G_L_1_G"
</vector> android:translateX="12"
android:translateY="12.125">
<path android:name="_R_G_L_1_G_D_0_P_0"
android:fillColor="#ffffff"
android:fillAlpha="1"
android:fillType="nonZero"
android:pathData=" M-6 7 C-6,7 -2,3.94 -2,3.94 C-2,3.94 -2,-4.37 -2,-4.37 C-2,-4.37 -6,-7 -6,-7 C-6,-7 -6,7 -6,7c "/>
</group>
<group android:name="_R_G_L_0_G"
android:translateX="12"
android:translateY="12.125">
<path android:name="_R_G_L_0_G_D_0_P_0"
android:fillColor="#ffffff"
android:fillAlpha="1"
android:fillType="nonZero"
android:pathData=" M-5.62 7 C-5.62,7 -4.75,7 -4.75,7 C-4.75,7 6,-0.12 6,-0.12 C6,-0.12 -4.44,-7 -4.44,-7 C-4.44,-7 -5.62,-7 -5.62,-7 C-5.62,-7 -5.62,7 -5.62,7c "/>
</group>
</group>
<group android:name="time_group"/>
</vector>
</aapt:attr>
<target android:name="_R_G_L_1_G_D_0_P_0">
<aapt:attr name="android:animation">
<set android:ordering="together">
<objectAnimator android:propertyName="pathData"
android:duration="333"
android:startOffset="0"
android:valueFrom="M-6 7 C-6,7 -2,3.94 -2,3.94 C-2,3.94 -2,-4.37 -2,-4.37 C-2,-4.37 -6,-7 -6,-7 C-6,-7 -6,7 -6,7c "
android:valueTo="M-6 7 C-6,7 -2,7 -2,7 C-2,7 -2,-7 -2,-7 C-2,-7 -6,-7 -6,-7 C-6,-7 -6,7 -6,7c "
android:valueType="pathType">
<aapt:attr name="android:interpolator">
<pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/>
</aapt:attr>
</objectAnimator>
</set>
</aapt:attr>
</target>
<target android:name="_R_G_L_0_G_D_0_P_0">
<aapt:attr name="android:animation">
<set android:ordering="together">
<objectAnimator android:propertyName="pathData"
android:duration="333"
android:startOffset="0"
android:valueFrom="M-5.62 7 C-5.62,7 -4.75,7 -4.75,7 C-4.75,7 6,-0.12 6,-0.12 C6,-0.12 -4.44,-7 -4.44,-7 C-4.44,-7 -5.62,-7 -5.62,-7 C-5.62,-7 -5.62,7 -5.62,7c "
android:valueTo="M2 7 C2,7 6,7 6,7 C6,7 6,-0.12 6,-0.12 C6,-0.12 6,-7 6,-7 C6,-7 2,-7 2,-7 C2,-7 2,7 2,7c "
android:valueType="pathType">
<aapt:attr name="android:interpolator">
<pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/>
</aapt:attr>
</objectAnimator>
</set>
</aapt:attr>
</target>
<target android:name="time_group">
<aapt:attr name="android:animation">
<set android:ordering="together">
<objectAnimator android:propertyName="translateX"
android:duration="517"
android:startOffset="0"
android:valueFrom="0"
android:valueTo="1"
android:valueType="floatType"/>
</set>
</aapt:attr>
</target>
</animated-vector>

View File

@@ -0,0 +1,68 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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
-->
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt">
<aapt:attr name="android:drawable">
<vector android:height="48dp"
android:width="48dp"
android:viewportHeight="48"
android:viewportWidth="48">
<group android:name="_R_G">
<group android:name="_R_G_L_0_G"
android:translateX="24"
android:translateY="24"
android:scaleX="0.5"
android:scaleY="0.5">
<path android:name="_R_G_L_0_G_D_0_P_0"
android:fillColor="#ffddb3"
android:fillAlpha="1"
android:fillType="nonZero"
android:pathData=" M48 0.25 C48,0.25 48,0 48,0 C47.75,26 31.25,48 0,48 C0,48 0,48 0,48 C-30,48 -48,25.75 -48,-0.25 C-48,-0.25 -48,-0.25 -48,-0.25 C-47.75,-23.5 -32.25,-47.75 0.5,-48 C0.5,-48 0.5,-48 0.5,-48 C28,-47.75 47.75,-29.75 48,0.25c "/>
</group>
</group>
<group android:name="time_group"/>
</vector>
</aapt:attr>
<target android:name="_R_G_L_0_G_D_0_P_0">
<aapt:attr name="android:animation">
<set android:ordering="together">
<objectAnimator android:propertyName="pathData"
android:duration="500"
android:startOffset="0"
android:valueFrom="M48 0.25 C48,0.25 48,0 48,0 C47.75,26 31.25,48 0,48 C0,48 0,48 0,48 C-30,48 -48,25.75 -48,-0.25 C-48,-0.25 -48,-0.25 -48,-0.25 C-47.75,-23.5 -32.25,-47.75 0.5,-48 C0.5,-48 0.5,-48 0.5,-48 C28,-47.75 47.75,-29.75 48,0.25c "
android:valueTo="M48 -16 C48,-16 48,16 48,16 C48,33.67 33.67,48 16,48 C16,48 -16,48 -16,48 C-33.67,48 -48,33.67 -48,16 C-48,16 -48,-16 -48,-16 C-48,-33.67 -33.67,-48 -16,-48 C-16,-48 16,-48 16,-48 C33.67,-48 48,-33.67 48,-16c "
android:valueType="pathType">
<aapt:attr name="android:interpolator">
<pathInterpolator android:pathData="M 0.0,0.0 c0.518,0 0,1 1.0,1.0"/>
</aapt:attr>
</objectAnimator>
</set>
</aapt:attr>
</target>
<target android:name="time_group">
<aapt:attr name="android:animation">
<set android:ordering="together">
<objectAnimator android:propertyName="translateX"
android:duration="517"
android:startOffset="0"
android:valueFrom="0"
android:valueTo="1"
android:valueType="floatType"/>
</set>
</aapt:attr>
</target>
</animated-vector>

View File

@@ -145,8 +145,7 @@
android:layout_width="48dp" android:layout_width="48dp"
android:layout_height="48dp" android:layout_height="48dp"
android:layout_marginStart="@dimen/qs_media_padding" android:layout_marginStart="@dimen/qs_media_padding"
android:layout_marginEnd="@dimen/qs_media_padding" android:layout_marginEnd="@dimen/qs_media_padding" />
/>
<!-- See comment in media_session_collapsed.xml for how these barriers are used --> <!-- See comment in media_session_collapsed.xml for how these barriers are used -->
<androidx.constraintlayout.widget.Barrier <androidx.constraintlayout.widget.Barrier
@@ -184,7 +183,7 @@
<!-- Button visibility will be controlled in code --> <!-- Button visibility will be controlled in code -->
<ImageButton <ImageButton
android:id="@+id/actionPrev" android:id="@+id/actionPrev"
style="@style/MediaPlayer.SessionAction" style="@style/MediaPlayer.SessionAction.Secondary"
android:layout_width="48dp" android:layout_width="48dp"
android:layout_height="48dp" android:layout_height="48dp"
android:layout_marginStart="4dp" android:layout_marginStart="4dp"
@@ -211,7 +210,7 @@
<ImageButton <ImageButton
android:id="@+id/actionNext" android:id="@+id/actionNext"
style="@style/MediaPlayer.SessionAction" style="@style/MediaPlayer.SessionAction.Secondary"
android:layout_width="48dp" android:layout_width="48dp"
android:layout_height="48dp" android:layout_height="48dp"
android:layout_marginStart="0dp" android:layout_marginStart="0dp"
@@ -221,7 +220,7 @@
<ImageButton <ImageButton
android:id="@+id/action0" android:id="@+id/action0"
style="@style/MediaPlayer.SessionAction" style="@style/MediaPlayer.SessionAction.Secondary"
android:layout_width="48dp" android:layout_width="48dp"
android:layout_height="48dp" android:layout_height="48dp"
android:layout_marginStart="@dimen/qs_media_action_spacing" android:layout_marginStart="@dimen/qs_media_action_spacing"
@@ -231,7 +230,7 @@
<ImageButton <ImageButton
android:id="@+id/action1" android:id="@+id/action1"
style="@style/MediaPlayer.SessionAction" style="@style/MediaPlayer.SessionAction.Secondary"
android:layout_width="48dp" android:layout_width="48dp"
android:layout_height="48dp" android:layout_height="48dp"
android:layout_marginStart="@dimen/qs_media_action_spacing" android:layout_marginStart="@dimen/qs_media_action_spacing"
@@ -241,7 +240,7 @@
<ImageButton <ImageButton
android:id="@+id/action2" android:id="@+id/action2"
style="@style/MediaPlayer.SessionAction" style="@style/MediaPlayer.SessionAction.Secondary"
android:layout_width="48dp" android:layout_width="48dp"
android:layout_height="48dp" android:layout_height="48dp"
android:layout_marginStart="@dimen/qs_media_action_spacing" android:layout_marginStart="@dimen/qs_media_action_spacing"
@@ -251,7 +250,7 @@
<ImageButton <ImageButton
android:id="@+id/action3" android:id="@+id/action3"
style="@style/MediaPlayer.SessionAction" style="@style/MediaPlayer.SessionAction.Secondary"
android:layout_width="48dp" android:layout_width="48dp"
android:layout_height="48dp" android:layout_height="48dp"
android:layout_marginStart="@dimen/qs_media_action_spacing" android:layout_marginStart="@dimen/qs_media_action_spacing"
@@ -261,7 +260,7 @@
<ImageButton <ImageButton
android:id="@+id/action4" android:id="@+id/action4"
style="@style/MediaPlayer.SessionAction" style="@style/MediaPlayer.SessionAction.Secondary"
android:layout_width="48dp" android:layout_width="48dp"
android:layout_height="48dp" android:layout_height="48dp"
android:layout_marginStart="@dimen/qs_media_action_spacing" android:layout_marginStart="@dimen/qs_media_action_spacing"

View File

@@ -616,7 +616,6 @@
parent="@android:style/Widget.Material.Button.Borderless.Small"> parent="@android:style/Widget.Material.Button.Borderless.Small">
<item name="android:background">@drawable/qs_media_light_source</item> <item name="android:background">@drawable/qs_media_light_source</item>
<item name="android:tint">?android:attr/textColorPrimary</item> <item name="android:tint">?android:attr/textColorPrimary</item>
<item name="android:stateListAnimator">@anim/media_button_state_list_animator</item>
<item name="android:paddingTop">12dp</item> <item name="android:paddingTop">12dp</item>
<item name="android:paddingStart">12dp</item> <item name="android:paddingStart">12dp</item>
<item name="android:paddingEnd">12dp</item> <item name="android:paddingEnd">12dp</item>
@@ -629,6 +628,10 @@
<item name="android:backgroundTint">@color/media_player_solid_button_bg</item> <item name="android:backgroundTint">@color/media_player_solid_button_bg</item>
</style> </style>
<style name="MediaPlayer.SessionAction.Secondary" parent="MediaPlayer.SessionAction">
<item name="android:stateListAnimator">@anim/media_button_state_list_animator</item>
</style>
<style name="MediaPlayer.OutlineButton"> <style name="MediaPlayer.OutlineButton">
<item name="android:background">@drawable/qs_media_outline_button</item> <item name="android:background">@drawable/qs_media_outline_button</item>
<item name="android:textColor">?android:attr/textColorPrimary</item> <item name="android:textColor">?android:attr/textColorPrimary</item>

View File

@@ -30,13 +30,14 @@ import android.content.res.ColorStateList;
import android.graphics.ColorMatrix; import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter; import android.graphics.ColorMatrixColorFilter;
import android.graphics.Rect; import android.graphics.Rect;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Animatable2;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon; import android.graphics.drawable.Icon;
import android.media.session.MediaController; import android.media.session.MediaController;
import android.media.session.MediaSession; import android.media.session.MediaSession;
import android.media.session.PlaybackState; import android.media.session.PlaybackState;
import android.os.Process; import android.os.Process;
import android.text.Layout;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import android.view.View; import android.view.View;
@@ -67,6 +68,7 @@ import com.android.systemui.util.animation.TransitionLayout;
import com.android.systemui.util.time.SystemClock; import com.android.systemui.util.time.SystemClock;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
@@ -637,9 +639,34 @@ public class MediaControlPanel {
private void setSemanticButton(final ImageButton button, MediaAction mediaAction, private void setSemanticButton(final ImageButton button, MediaAction mediaAction,
ConstraintSet collapsedSet, ConstraintSet expandedSet, boolean showInCompact) { ConstraintSet collapsedSet, ConstraintSet expandedSet, boolean showInCompact) {
AnimationBindHandler animHandler;
if (button.getTag() == null) {
animHandler = new AnimationBindHandler();
button.setTag(animHandler);
} else {
animHandler = (AnimationBindHandler) button.getTag();
}
animHandler.tryExecute(() -> {
bindSemanticButton(animHandler, button, mediaAction,
collapsedSet, expandedSet, showInCompact);
});
}
private void bindSemanticButton(final AnimationBindHandler animHandler,
final ImageButton button, MediaAction mediaAction, ConstraintSet collapsedSet,
ConstraintSet expandedSet, boolean showInCompact) {
animHandler.unregisterAll();
if (mediaAction != null) { if (mediaAction != null) {
button.setImageIcon(mediaAction.getIcon()); final Drawable icon = mediaAction.getIcon();
button.setImageDrawable(icon);
button.setContentDescription(mediaAction.getContentDescription()); button.setContentDescription(mediaAction.getContentDescription());
final Drawable bgDrawable = mediaAction.getBackground();
button.setBackground(bgDrawable);
animHandler.tryRegister(icon);
animHandler.tryRegister(bgDrawable);
Runnable action = mediaAction.getAction(); Runnable action = mediaAction.getAction();
if (action == null) { if (action == null) {
@@ -651,19 +678,75 @@ public class MediaControlPanel {
logSmartspaceCardReported(SMARTSPACE_CARD_CLICK_EVENT, logSmartspaceCardReported(SMARTSPACE_CARD_CLICK_EVENT,
/* isRecommendationCard */ false); /* isRecommendationCard */ false);
action.run(); action.run();
if (icon instanceof Animatable) {
((Animatable) icon).start();
}
if (bgDrawable instanceof Animatable) {
((Animatable) bgDrawable).start();
}
} }
}); });
} }
} else { } else {
button.setImageIcon(null); button.setImageDrawable(null);
button.setContentDescription(null); button.setContentDescription(null);
button.setEnabled(false); button.setEnabled(false);
button.setBackground(mContext.getDrawable(R.drawable.qs_media_round_button_background));
} }
setVisibleAndAlpha(collapsedSet, button.getId(), mediaAction != null && showInCompact); setVisibleAndAlpha(collapsedSet, button.getId(), mediaAction != null && showInCompact);
setVisibleAndAlpha(expandedSet, button.getId(), mediaAction != null); setVisibleAndAlpha(expandedSet, button.getId(), mediaAction != null);
} }
private static class AnimationBindHandler extends Animatable2.AnimationCallback {
private ArrayList<Runnable> mOnAnimationsComplete = new ArrayList<>();
private ArrayList<Animatable2> mRegistrations = new ArrayList<>();
public void tryRegister(Drawable drawable) {
if (drawable instanceof Animatable2) {
Animatable2 anim = (Animatable2) drawable;
anim.registerAnimationCallback(this);
mRegistrations.add(anim);
}
}
public void unregisterAll() {
for (Animatable2 anim : mRegistrations) {
anim.unregisterAnimationCallback(this);
}
mRegistrations.clear();
}
public boolean isAnimationRunning() {
for (Animatable2 anim : mRegistrations) {
if (anim.isRunning()) {
return true;
}
}
return false;
}
public void tryExecute(Runnable action) {
if (isAnimationRunning()) {
mOnAnimationsComplete.add(action);
} else {
action.run();
}
}
@Override
public void onAnimationEnd(Drawable drawable) {
super.onAnimationEnd(drawable);
if (!isAnimationRunning()) {
for (Runnable action : mOnAnimationsComplete) {
action.run();
}
mOnAnimationsComplete.clear();
}
}
}
@Nullable @Nullable
private ActivityLaunchAnimator.Controller buildLaunchAnimatorController( private ActivityLaunchAnimator.Controller buildLaunchAnimatorController(
TransitionLayout player) { TransitionLayout player) {

View File

@@ -170,9 +170,10 @@ data class MediaButton(
/** State of a media action. */ /** State of a media action. */
data class MediaAction( data class MediaAction(
val icon: Icon?, val icon: Drawable?,
val action: Runnable?, val action: Runnable?,
val contentDescription: CharSequence? val contentDescription: CharSequence?,
val background: Drawable?
) )
/** State of the media device. */ /** State of the media device. */

View File

@@ -695,11 +695,12 @@ class MediaDataManager(
Icon.createWithResource(sbn.packageName, action.getIcon()!!.getResId()) Icon.createWithResource(sbn.packageName, action.getIcon()!!.getResId())
} else { } else {
action.getIcon() action.getIcon()
}.setTint(themeText) }.setTint(themeText).loadDrawable(context)
val mediaAction = MediaAction( val mediaAction = MediaAction(
mediaActionIcon, mediaActionIcon,
runnable, runnable,
action.title) action.title,
context.getDrawable(R.drawable.qs_media_round_button_background))
actionIcons.add(mediaAction) actionIcons.add(mediaAction)
} }
} }
@@ -789,30 +790,34 @@ class MediaDataManager(
return when (action) { return when (action) {
PlaybackState.ACTION_PLAY -> { PlaybackState.ACTION_PLAY -> {
MediaAction( MediaAction(
Icon.createWithResource(context, R.drawable.ic_media_play), context.getDrawable(R.drawable.ic_media_play),
{ controller.transportControls.play() }, { controller.transportControls.play() },
context.getString(R.string.controls_media_button_play) context.getString(R.string.controls_media_button_play),
context.getDrawable(R.drawable.ic_media_play_container)
) )
} }
PlaybackState.ACTION_PAUSE -> { PlaybackState.ACTION_PAUSE -> {
MediaAction( MediaAction(
Icon.createWithResource(context, R.drawable.ic_media_pause), context.getDrawable(R.drawable.ic_media_pause),
{ controller.transportControls.pause() }, { controller.transportControls.pause() },
context.getString(R.string.controls_media_button_pause) context.getString(R.string.controls_media_button_pause),
context.getDrawable(R.drawable.ic_media_pause_container)
) )
} }
PlaybackState.ACTION_SKIP_TO_PREVIOUS -> { PlaybackState.ACTION_SKIP_TO_PREVIOUS -> {
MediaAction( MediaAction(
Icon.createWithResource(context, R.drawable.ic_media_prev), context.getDrawable(R.drawable.ic_media_prev),
{ controller.transportControls.skipToPrevious() }, { controller.transportControls.skipToPrevious() },
context.getString(R.string.controls_media_button_prev) context.getString(R.string.controls_media_button_prev),
context.getDrawable(R.drawable.qs_media_round_button_background)
) )
} }
PlaybackState.ACTION_SKIP_TO_NEXT -> { PlaybackState.ACTION_SKIP_TO_NEXT -> {
MediaAction( MediaAction(
Icon.createWithResource(context, R.drawable.ic_media_next), context.getDrawable(R.drawable.ic_media_next),
{ controller.transportControls.skipToNext() }, { controller.transportControls.skipToNext() },
context.getString(R.string.controls_media_button_next) context.getString(R.string.controls_media_button_next),
context.getDrawable(R.drawable.qs_media_round_button_background)
) )
} }
else -> null else -> null
@@ -835,9 +840,10 @@ class MediaDataManager(
val it = state.customActions[index] val it = state.customActions[index]
return MediaAction( return MediaAction(
Icon.createWithResource(packageName, it.icon), Icon.createWithResource(packageName, it.icon).loadDrawable(context),
{ controller.transportControls.sendCustomAction(it, it.extras) }, { controller.transportControls.sendCustomAction(it, it.extras) },
it.name it.name,
context.getDrawable(R.drawable.ic_media_pause_container)
) )
} }
@@ -900,9 +906,11 @@ class MediaDataManager(
private fun getResumeMediaAction(action: Runnable): MediaAction { private fun getResumeMediaAction(action: Runnable): MediaAction {
return MediaAction( return MediaAction(
Icon.createWithResource(context, R.drawable.ic_media_play).setTint(themeText), Icon.createWithResource(context, R.drawable.ic_media_play)
.setTint(themeText).loadDrawable(context),
action, action,
context.getString(R.string.controls_media_resume) context.getString(R.string.controls_media_resume),
context.getDrawable(R.drawable.ic_media_play_container)
) )
} }

View File

@@ -19,8 +19,9 @@ package com.android.systemui.media
import org.mockito.Mockito.`when` as whenever import org.mockito.Mockito.`when` as whenever
import android.content.Intent import android.content.Intent
import android.graphics.Color import android.graphics.Color
import android.graphics.drawable.Animatable2
import android.graphics.drawable.AnimatedVectorDrawable
import android.graphics.drawable.GradientDrawable import android.graphics.drawable.GradientDrawable
import android.graphics.drawable.Icon
import android.graphics.drawable.RippleDrawable import android.graphics.drawable.RippleDrawable
import android.media.MediaMetadata import android.media.MediaMetadata
import android.media.session.MediaSession import android.media.session.MediaSession
@@ -60,6 +61,7 @@ import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.anyLong import org.mockito.ArgumentMatchers.anyLong
import org.mockito.Mock import org.mockito.Mock
import org.mockito.Mockito.any
import org.mockito.Mockito.mock import org.mockito.Mockito.mock
import org.mockito.Mockito.never import org.mockito.Mockito.never
import org.mockito.Mockito.times import org.mockito.Mockito.times
@@ -290,15 +292,15 @@ public class MediaControlPanelTest : SysuiTestCase() {
@Test @Test
fun bindSemanticActions() { fun bindSemanticActions() {
val icon = Icon.createWithResource(context, android.R.drawable.ic_media_play) val icon = context.getDrawable(android.R.drawable.ic_media_play)
val bg = context.getDrawable(R.drawable.qs_media_round_button_background)
val semanticActions = MediaButton( val semanticActions = MediaButton(
playOrPause = MediaAction(icon, Runnable {}, "play"), playOrPause = MediaAction(icon, Runnable {}, "play", bg),
nextOrCustom = MediaAction(icon, Runnable {}, "next"), nextOrCustom = MediaAction(icon, Runnable {}, "next", bg),
custom0 = MediaAction(icon, null, "custom 0"), custom0 = MediaAction(icon, null, "custom 0", bg),
custom1 = MediaAction(icon, null, "custom 1") custom1 = MediaAction(icon, null, "custom 1", bg)
) )
val state = mediaData.copy(semanticActions = semanticActions) val state = mediaData.copy(semanticActions = semanticActions)
player.attachPlayer(viewHolder) player.attachPlayer(viewHolder)
player.bindPlayer(state, PACKAGE) player.bindPlayer(state, PACKAGE)
@@ -338,10 +340,10 @@ public class MediaControlPanelTest : SysuiTestCase() {
fun bind_seekBarDisabled_seekBarVisibilityIsSetToInvisible() { fun bind_seekBarDisabled_seekBarVisibilityIsSetToInvisible() {
whenever(seekBarViewModel.getEnabled()).thenReturn(false) whenever(seekBarViewModel.getEnabled()).thenReturn(false)
val icon = Icon.createWithResource(context, android.R.drawable.ic_media_play) val icon = context.getDrawable(android.R.drawable.ic_media_play)
val semanticActions = MediaButton( val semanticActions = MediaButton(
playOrPause = MediaAction(icon, Runnable {}, "play"), playOrPause = MediaAction(icon, Runnable {}, "play", null),
nextOrCustom = MediaAction(icon, Runnable {}, "next") nextOrCustom = MediaAction(icon, Runnable {}, "next", null)
) )
val state = mediaData.copy(semanticActions = semanticActions) val state = mediaData.copy(semanticActions = semanticActions)
@@ -365,13 +367,14 @@ public class MediaControlPanelTest : SysuiTestCase() {
@Test @Test
fun bindNotificationActions() { fun bindNotificationActions() {
val icon = Icon.createWithResource(context, android.R.drawable.ic_media_play) val icon = context.getDrawable(android.R.drawable.ic_media_play)
val bg = context.getDrawable(R.drawable.qs_media_round_button_background)
val actions = listOf( val actions = listOf(
MediaAction(icon, Runnable {}, "previous"), MediaAction(icon, Runnable {}, "previous", bg),
MediaAction(icon, Runnable {}, "play"), MediaAction(icon, Runnable {}, "play", bg),
MediaAction(icon, null, "next"), MediaAction(icon, null, "next", bg),
MediaAction(icon, null, "custom 0"), MediaAction(icon, null, "custom 0", bg),
MediaAction(icon, Runnable {}, "custom 1") MediaAction(icon, Runnable {}, "custom 1", bg)
) )
val state = mediaData.copy(actions = actions, val state = mediaData.copy(actions = actions,
actionsToShowInCompact = listOf(1, 2), actionsToShowInCompact = listOf(1, 2),
@@ -412,6 +415,72 @@ public class MediaControlPanelTest : SysuiTestCase() {
verify(collapsedSet).setVisibility(R.id.action4, ConstraintSet.GONE) verify(collapsedSet).setVisibility(R.id.action4, ConstraintSet.GONE)
} }
@Test
fun bindAnimatedSemanticActions() {
val mockAvd0 = mock(AnimatedVectorDrawable::class.java)
val mockAvd1 = mock(AnimatedVectorDrawable::class.java)
val mockAvd2 = mock(AnimatedVectorDrawable::class.java)
whenever(mockAvd0.mutate()).thenReturn(mockAvd0)
whenever(mockAvd1.mutate()).thenReturn(mockAvd1)
whenever(mockAvd2.mutate()).thenReturn(mockAvd2)
val icon = context.getDrawable(R.drawable.ic_media_play)
val bg = context.getDrawable(R.drawable.ic_media_play_container)
val semanticActions0 = MediaButton(
playOrPause = MediaAction(mockAvd0, Runnable {}, "play", null))
val semanticActions1 = MediaButton(
playOrPause = MediaAction(mockAvd1, Runnable {}, "pause", null))
val semanticActions2 = MediaButton(
playOrPause = MediaAction(mockAvd2, Runnable {}, "loading", null))
val state0 = mediaData.copy(semanticActions = semanticActions0)
val state1 = mediaData.copy(semanticActions = semanticActions1)
val state2 = mediaData.copy(semanticActions = semanticActions2)
player.attachPlayer(viewHolder)
player.bindPlayer(state0, PACKAGE)
// Validate first binding
assertThat(actionPlayPause.isEnabled()).isTrue()
assertThat(actionPlayPause.contentDescription).isEqualTo("play")
verify(collapsedSet).setVisibility(R.id.actionPlayPause, ConstraintSet.VISIBLE)
assertThat(actionPlayPause.hasOnClickListeners()).isTrue()
// Trigger animation & update mock
actionPlayPause.performClick()
verify(mockAvd0, times(1)).start()
whenever(mockAvd0.isRunning()).thenReturn(true)
// Validate states no longer bind
player.bindPlayer(state1, PACKAGE)
player.bindPlayer(state2, PACKAGE)
assertThat(actionPlayPause.contentDescription).isEqualTo("play")
// Complete animation and run callbacks
whenever(mockAvd0.isRunning()).thenReturn(false)
val captor = ArgumentCaptor.forClass(Animatable2.AnimationCallback::class.java)
verify(mockAvd0, times(1)).registerAnimationCallback(captor.capture())
verify(mockAvd1, never())
.registerAnimationCallback(any(Animatable2.AnimationCallback::class.java))
verify(mockAvd2, never())
.registerAnimationCallback(any(Animatable2.AnimationCallback::class.java))
captor.getValue().onAnimationEnd(mockAvd0)
// Validate correct state was bound
assertThat(actionPlayPause.contentDescription).isEqualTo("loading")
verify(mockAvd0, times(1))
.registerAnimationCallback(any(Animatable2.AnimationCallback::class.java))
verify(mockAvd1, times(1)
).registerAnimationCallback(any(Animatable2.AnimationCallback::class.java))
verify(mockAvd2, times(1))
.registerAnimationCallback(any(Animatable2.AnimationCallback::class.java))
verify(mockAvd0, times(1))
.unregisterAnimationCallback(any(Animatable2.AnimationCallback::class.java))
verify(mockAvd1, times(1))
.unregisterAnimationCallback(any(Animatable2.AnimationCallback::class.java))
verify(mockAvd2, never())
.unregisterAnimationCallback(any(Animatable2.AnimationCallback::class.java))
}
@Test @Test
fun bindText() { fun bindText() {
player.attachPlayer(viewHolder) player.attachPlayer(viewHolder)