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"?>
<!--
~ 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
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="14dp"
android:height="16dp"
android:viewportWidth="14"
android:viewportHeight="16">
<path
android:pathData="M9.1818,15.6363H13.5455V0.3635H9.1818V15.6363ZM0.4546,15.6363H4.8182V0.3635H0.4546V15.6363Z"
android:fillColor="#FFFFFF"
android:fillType="evenOdd"/>
</vector>
<?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="24dp"
android:width="24dp"
android:viewportHeight="24"
android:viewportWidth="24">
<group android:name="_R_G">
<group android:name="_R_G_L_1_G"
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"?>
<!--
~ 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
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M20,12L6,21V3L20,12ZM15.26,12L8.55,7.68V16.32L15.26,12Z"
android:fillColor="#FFFFFF"
android:fillType="evenOdd"/>
</vector>
<?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="24dp"
android:width="24dp"
android:viewportHeight="24"
android:viewportWidth="24">
<group android:name="_R_G">
<group android:name="_R_G_L_1_G"
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_height="48dp"
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 -->
<androidx.constraintlayout.widget.Barrier
@@ -184,7 +183,7 @@
<!-- Button visibility will be controlled in code -->
<ImageButton
android:id="@+id/actionPrev"
style="@style/MediaPlayer.SessionAction"
style="@style/MediaPlayer.SessionAction.Secondary"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginStart="4dp"
@@ -211,7 +210,7 @@
<ImageButton
android:id="@+id/actionNext"
style="@style/MediaPlayer.SessionAction"
style="@style/MediaPlayer.SessionAction.Secondary"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginStart="0dp"
@@ -221,7 +220,7 @@
<ImageButton
android:id="@+id/action0"
style="@style/MediaPlayer.SessionAction"
style="@style/MediaPlayer.SessionAction.Secondary"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginStart="@dimen/qs_media_action_spacing"
@@ -231,7 +230,7 @@
<ImageButton
android:id="@+id/action1"
style="@style/MediaPlayer.SessionAction"
style="@style/MediaPlayer.SessionAction.Secondary"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginStart="@dimen/qs_media_action_spacing"
@@ -241,7 +240,7 @@
<ImageButton
android:id="@+id/action2"
style="@style/MediaPlayer.SessionAction"
style="@style/MediaPlayer.SessionAction.Secondary"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginStart="@dimen/qs_media_action_spacing"
@@ -251,7 +250,7 @@
<ImageButton
android:id="@+id/action3"
style="@style/MediaPlayer.SessionAction"
style="@style/MediaPlayer.SessionAction.Secondary"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginStart="@dimen/qs_media_action_spacing"
@@ -261,7 +260,7 @@
<ImageButton
android:id="@+id/action4"
style="@style/MediaPlayer.SessionAction"
style="@style/MediaPlayer.SessionAction.Secondary"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginStart="@dimen/qs_media_action_spacing"

View File

@@ -616,7 +616,6 @@
parent="@android:style/Widget.Material.Button.Borderless.Small">
<item name="android:background">@drawable/qs_media_light_source</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:paddingStart">12dp</item>
<item name="android:paddingEnd">12dp</item>
@@ -629,6 +628,10 @@
<item name="android:backgroundTint">@color/media_player_solid_button_bg</item>
</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">
<item name="android:background">@drawable/qs_media_outline_button</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.ColorMatrixColorFilter;
import android.graphics.Rect;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Animatable2;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.media.session.MediaController;
import android.media.session.MediaSession;
import android.media.session.PlaybackState;
import android.os.Process;
import android.text.Layout;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
@@ -67,6 +68,7 @@ import com.android.systemui.util.animation.TransitionLayout;
import com.android.systemui.util.time.SystemClock;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
@@ -637,9 +639,34 @@ public class MediaControlPanel {
private void setSemanticButton(final ImageButton button, MediaAction mediaAction,
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) {
button.setImageIcon(mediaAction.getIcon());
final Drawable icon = mediaAction.getIcon();
button.setImageDrawable(icon);
button.setContentDescription(mediaAction.getContentDescription());
final Drawable bgDrawable = mediaAction.getBackground();
button.setBackground(bgDrawable);
animHandler.tryRegister(icon);
animHandler.tryRegister(bgDrawable);
Runnable action = mediaAction.getAction();
if (action == null) {
@@ -651,19 +678,75 @@ public class MediaControlPanel {
logSmartspaceCardReported(SMARTSPACE_CARD_CLICK_EVENT,
/* isRecommendationCard */ false);
action.run();
if (icon instanceof Animatable) {
((Animatable) icon).start();
}
if (bgDrawable instanceof Animatable) {
((Animatable) bgDrawable).start();
}
}
});
}
} else {
button.setImageIcon(null);
button.setImageDrawable(null);
button.setContentDescription(null);
button.setEnabled(false);
button.setBackground(mContext.getDrawable(R.drawable.qs_media_round_button_background));
}
setVisibleAndAlpha(collapsedSet, button.getId(), mediaAction != null && showInCompact);
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
private ActivityLaunchAnimator.Controller buildLaunchAnimatorController(
TransitionLayout player) {

View File

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

View File

@@ -695,11 +695,12 @@ class MediaDataManager(
Icon.createWithResource(sbn.packageName, action.getIcon()!!.getResId())
} else {
action.getIcon()
}.setTint(themeText)
}.setTint(themeText).loadDrawable(context)
val mediaAction = MediaAction(
mediaActionIcon,
runnable,
action.title)
action.title,
context.getDrawable(R.drawable.qs_media_round_button_background))
actionIcons.add(mediaAction)
}
}
@@ -789,30 +790,34 @@ class MediaDataManager(
return when (action) {
PlaybackState.ACTION_PLAY -> {
MediaAction(
Icon.createWithResource(context, R.drawable.ic_media_play),
context.getDrawable(R.drawable.ic_media_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 -> {
MediaAction(
Icon.createWithResource(context, R.drawable.ic_media_pause),
context.getDrawable(R.drawable.ic_media_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 -> {
MediaAction(
Icon.createWithResource(context, R.drawable.ic_media_prev),
context.getDrawable(R.drawable.ic_media_prev),
{ 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 -> {
MediaAction(
Icon.createWithResource(context, R.drawable.ic_media_next),
context.getDrawable(R.drawable.ic_media_next),
{ 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
@@ -835,9 +840,10 @@ class MediaDataManager(
val it = state.customActions[index]
return MediaAction(
Icon.createWithResource(packageName, it.icon),
Icon.createWithResource(packageName, it.icon).loadDrawable(context),
{ 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 {
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,
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 android.content.Intent
import android.graphics.Color
import android.graphics.drawable.Animatable2
import android.graphics.drawable.AnimatedVectorDrawable
import android.graphics.drawable.GradientDrawable
import android.graphics.drawable.Icon
import android.graphics.drawable.RippleDrawable
import android.media.MediaMetadata
import android.media.session.MediaSession
@@ -60,6 +61,7 @@ import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.anyLong
import org.mockito.Mock
import org.mockito.Mockito.any
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.times
@@ -290,15 +292,15 @@ public class MediaControlPanelTest : SysuiTestCase() {
@Test
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(
playOrPause = MediaAction(icon, Runnable {}, "play"),
nextOrCustom = MediaAction(icon, Runnable {}, "next"),
custom0 = MediaAction(icon, null, "custom 0"),
custom1 = MediaAction(icon, null, "custom 1")
playOrPause = MediaAction(icon, Runnable {}, "play", bg),
nextOrCustom = MediaAction(icon, Runnable {}, "next", bg),
custom0 = MediaAction(icon, null, "custom 0", bg),
custom1 = MediaAction(icon, null, "custom 1", bg)
)
val state = mediaData.copy(semanticActions = semanticActions)
player.attachPlayer(viewHolder)
player.bindPlayer(state, PACKAGE)
@@ -338,10 +340,10 @@ public class MediaControlPanelTest : SysuiTestCase() {
fun bind_seekBarDisabled_seekBarVisibilityIsSetToInvisible() {
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(
playOrPause = MediaAction(icon, Runnable {}, "play"),
nextOrCustom = MediaAction(icon, Runnable {}, "next")
playOrPause = MediaAction(icon, Runnable {}, "play", null),
nextOrCustom = MediaAction(icon, Runnable {}, "next", null)
)
val state = mediaData.copy(semanticActions = semanticActions)
@@ -365,13 +367,14 @@ public class MediaControlPanelTest : SysuiTestCase() {
@Test
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(
MediaAction(icon, Runnable {}, "previous"),
MediaAction(icon, Runnable {}, "play"),
MediaAction(icon, null, "next"),
MediaAction(icon, null, "custom 0"),
MediaAction(icon, Runnable {}, "custom 1")
MediaAction(icon, Runnable {}, "previous", bg),
MediaAction(icon, Runnable {}, "play", bg),
MediaAction(icon, null, "next", bg),
MediaAction(icon, null, "custom 0", bg),
MediaAction(icon, Runnable {}, "custom 1", bg)
)
val state = mediaData.copy(actions = actions,
actionsToShowInCompact = listOf(1, 2),
@@ -412,6 +415,72 @@ public class MediaControlPanelTest : SysuiTestCase() {
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
fun bindText() {
player.attachPlayer(viewHolder)