Merge "Spinner drawable for casting intermediate state" into tm-dev am: 847bd13590
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/17535547 Change-Id: Idf6155764abf67214cea12d89b0ed3bf58ac8960 Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -2303,6 +2303,7 @@
|
|||||||
<java-symbol type="drawable" name="scrubber_control_disabled_holo" />
|
<java-symbol type="drawable" name="scrubber_control_disabled_holo" />
|
||||||
<java-symbol type="drawable" name="scrubber_control_selector_holo" />
|
<java-symbol type="drawable" name="scrubber_control_selector_holo" />
|
||||||
<java-symbol type="drawable" name="scrubber_progress_horizontal_holo_dark" />
|
<java-symbol type="drawable" name="scrubber_progress_horizontal_holo_dark" />
|
||||||
|
<java-symbol type="drawable" name="progress_small_material" />
|
||||||
<java-symbol type="string" name="chooseUsbActivity" />
|
<java-symbol type="string" name="chooseUsbActivity" />
|
||||||
<java-symbol type="string" name="ext_media_badremoval_notification_message" />
|
<java-symbol type="string" name="ext_media_badremoval_notification_message" />
|
||||||
<java-symbol type="string" name="ext_media_badremoval_notification_title" />
|
<java-symbol type="string" name="ext_media_badremoval_notification_title" />
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
<?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: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>
|
||||||
|
</vector>
|
||||||
@@ -2199,6 +2199,8 @@
|
|||||||
<string name="controls_media_button_prev">Previous track</string>
|
<string name="controls_media_button_prev">Previous track</string>
|
||||||
<!-- Description for button in media controls. Pressing button goes to next track [CHAR_LIMIT=NONE] -->
|
<!-- Description for button in media controls. Pressing button goes to next track [CHAR_LIMIT=NONE] -->
|
||||||
<string name="controls_media_button_next">Next track</string>
|
<string name="controls_media_button_next">Next track</string>
|
||||||
|
<!-- Description for button in media controls. Used when media is connecting to a remote device (via something like chromecast). Pressing button does nothing [CHAR_LIMIT=NONE] -->
|
||||||
|
<string name="controls_media_button_connecting">Connecting</string>
|
||||||
|
|
||||||
<!-- Title for Smartspace recommendation card within media controls. The "Play" means the action to play a media [CHAR_LIMIT=10] -->
|
<!-- Title for Smartspace recommendation card within media controls. The "Play" means the action to play a media [CHAR_LIMIT=10] -->
|
||||||
<string name="controls_media_smartspace_rec_title">Play</string>
|
<string name="controls_media_smartspace_rec_title">Play</string>
|
||||||
|
|||||||
@@ -660,8 +660,10 @@ public class MediaControlPanel {
|
|||||||
final ImageButton button, MediaAction mediaAction, ConstraintSet collapsedSet,
|
final ImageButton button, MediaAction mediaAction, ConstraintSet collapsedSet,
|
||||||
ConstraintSet expandedSet, boolean showInCompact) {
|
ConstraintSet expandedSet, boolean showInCompact) {
|
||||||
|
|
||||||
animHandler.unregisterAll();
|
|
||||||
if (mediaAction != null) {
|
if (mediaAction != null) {
|
||||||
|
if (animHandler.updateRebindId(mediaAction.getRebindId())) {
|
||||||
|
animHandler.unregisterAll();
|
||||||
|
|
||||||
final Drawable icon = mediaAction.getIcon();
|
final Drawable icon = mediaAction.getIcon();
|
||||||
button.setImageDrawable(icon);
|
button.setImageDrawable(icon);
|
||||||
button.setContentDescription(mediaAction.getContentDescription());
|
button.setContentDescription(mediaAction.getContentDescription());
|
||||||
@@ -678,6 +680,7 @@ public class MediaControlPanel {
|
|||||||
button.setEnabled(true);
|
button.setEnabled(true);
|
||||||
button.setOnClickListener(v -> {
|
button.setOnClickListener(v -> {
|
||||||
if (!mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
|
if (!mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
|
||||||
|
mLogger.logTapAction(button.getId(), mUid, mPackageName, mInstanceId);
|
||||||
mLogger.logTapAction(button.getId(), mUid, mPackageName, mInstanceId);
|
mLogger.logTapAction(button.getId(), mUid, mPackageName, mInstanceId);
|
||||||
logSmartspaceCardReported(SMARTSPACE_CARD_CLICK_EVENT);
|
logSmartspaceCardReported(SMARTSPACE_CARD_CLICK_EVENT);
|
||||||
action.run();
|
action.run();
|
||||||
@@ -691,7 +694,9 @@ public class MediaControlPanel {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
|
animHandler.unregisterAll();
|
||||||
button.setImageDrawable(null);
|
button.setImageDrawable(null);
|
||||||
button.setContentDescription(null);
|
button.setContentDescription(null);
|
||||||
button.setEnabled(false);
|
button.setEnabled(false);
|
||||||
@@ -702,9 +707,29 @@ public class MediaControlPanel {
|
|||||||
setVisibleAndAlpha(expandedSet, button.getId(), mediaAction != null);
|
setVisibleAndAlpha(expandedSet, button.getId(), mediaAction != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AnimationBindHandler is responsible for tracking the bound animation state and preventing
|
||||||
|
// jank and conflicts due to media notifications arriving at any time during an animation. It
|
||||||
|
// does this in two parts.
|
||||||
|
// - Exit animations fired as a result of user input are tracked. When these are running, any
|
||||||
|
// bind actions are delayed until the animation completes (and then fired in sequence).
|
||||||
|
// - Continuous animations are tracked using their rebind id. Later calls using the same
|
||||||
|
// rebind id will be totally ignored to prevent the continuous animation from restarting.
|
||||||
private static class AnimationBindHandler extends Animatable2.AnimationCallback {
|
private static class AnimationBindHandler extends Animatable2.AnimationCallback {
|
||||||
private ArrayList<Runnable> mOnAnimationsComplete = new ArrayList<>();
|
private ArrayList<Runnable> mOnAnimationsComplete = new ArrayList<>();
|
||||||
private ArrayList<Animatable2> mRegistrations = new ArrayList<>();
|
private ArrayList<Animatable2> mRegistrations = new ArrayList<>();
|
||||||
|
private Integer mRebindId = null;
|
||||||
|
|
||||||
|
// This check prevents rebinding to the action button if the identifier has not changed. A
|
||||||
|
// null value is always considered to be changed. This is used to prevent the connecting
|
||||||
|
// animation from rebinding (and restarting) if multiple buffer PlaybackStates are pushed by
|
||||||
|
// an application in a row.
|
||||||
|
public boolean updateRebindId(Integer rebindId) {
|
||||||
|
if (mRebindId == null || rebindId == null || !mRebindId.equals(rebindId)) {
|
||||||
|
mRebindId = rebindId;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public void tryRegister(Drawable drawable) {
|
public void tryRegister(Drawable drawable) {
|
||||||
if (drawable instanceof Animatable2) {
|
if (drawable instanceof Animatable2) {
|
||||||
|
|||||||
@@ -184,7 +184,12 @@ data class MediaAction(
|
|||||||
val icon: Drawable?,
|
val icon: Drawable?,
|
||||||
val action: Runnable?,
|
val action: Runnable?,
|
||||||
val contentDescription: CharSequence?,
|
val contentDescription: CharSequence?,
|
||||||
val background: Drawable?
|
val background: Drawable?,
|
||||||
|
|
||||||
|
// Rebind Id is used to detect identical rebinds and ignore them. It is intended
|
||||||
|
// to prevent continuously looping animations from restarting due to the arrival
|
||||||
|
// of repeated media notifications that are visually identical.
|
||||||
|
val rebindId: Int? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
/** State of the media device. */
|
/** State of the media device. */
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import android.content.IntentFilter
|
|||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.graphics.ImageDecoder
|
import android.graphics.ImageDecoder
|
||||||
|
import android.graphics.drawable.Animatable
|
||||||
import android.graphics.drawable.Icon
|
import android.graphics.drawable.Icon
|
||||||
import android.media.MediaDescription
|
import android.media.MediaDescription
|
||||||
import android.media.MediaMetadata
|
import android.media.MediaMetadata
|
||||||
@@ -57,6 +58,7 @@ import com.android.systemui.dump.DumpManager
|
|||||||
import com.android.systemui.plugins.ActivityStarter
|
import com.android.systemui.plugins.ActivityStarter
|
||||||
import com.android.systemui.plugins.BcSmartspaceDataPlugin
|
import com.android.systemui.plugins.BcSmartspaceDataPlugin
|
||||||
import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState
|
import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState
|
||||||
|
import com.android.systemui.statusbar.NotificationMediaManager.isConnectingState
|
||||||
import com.android.systemui.statusbar.notification.row.HybridGroupManager
|
import com.android.systemui.statusbar.notification.row.HybridGroupManager
|
||||||
import com.android.systemui.tuner.TunerService
|
import com.android.systemui.tuner.TunerService
|
||||||
import com.android.systemui.util.Assert
|
import com.android.systemui.util.Assert
|
||||||
@@ -777,7 +779,20 @@ class MediaDataManager(
|
|||||||
val actions = MediaButton()
|
val actions = MediaButton()
|
||||||
controller.playbackState?.let { state ->
|
controller.playbackState?.let { state ->
|
||||||
// First, check for standard actions
|
// First, check for standard actions
|
||||||
actions.playOrPause = if (isPlayingState(state.state)) {
|
actions.playOrPause = if (isConnectingState(state.state)) {
|
||||||
|
// Spinner needs to be animating to render anything. Start it here.
|
||||||
|
val drawable = context.getDrawable(
|
||||||
|
com.android.internal.R.drawable.progress_small_material)
|
||||||
|
(drawable as Animatable).start()
|
||||||
|
MediaAction(
|
||||||
|
drawable,
|
||||||
|
null, // no action to perform when clicked
|
||||||
|
context.getString(R.string.controls_media_button_connecting),
|
||||||
|
context.getDrawable(R.drawable.ic_media_connecting_container),
|
||||||
|
// Specify a rebind id to prevent the spinner from restarting on later binds.
|
||||||
|
com.android.internal.R.drawable.progress_small_material
|
||||||
|
)
|
||||||
|
} else if (isPlayingState(state.state)) {
|
||||||
getStandardAction(controller, state.actions, PlaybackState.ACTION_PAUSE)
|
getStandardAction(controller, state.actions, PlaybackState.ACTION_PAUSE)
|
||||||
} else {
|
} else {
|
||||||
getStandardAction(controller, state.actions, PlaybackState.ACTION_PLAY)
|
getStandardAction(controller, state.actions, PlaybackState.ACTION_PLAY)
|
||||||
|
|||||||
@@ -102,12 +102,14 @@ public class NotificationMediaManager implements Dumpable {
|
|||||||
KeyguardStateController.class);
|
KeyguardStateController.class);
|
||||||
private final KeyguardBypassController mKeyguardBypassController;
|
private final KeyguardBypassController mKeyguardBypassController;
|
||||||
private static final HashSet<Integer> PAUSED_MEDIA_STATES = new HashSet<>();
|
private static final HashSet<Integer> PAUSED_MEDIA_STATES = new HashSet<>();
|
||||||
|
private static final HashSet<Integer> CONNECTING_MEDIA_STATES = new HashSet<>();
|
||||||
static {
|
static {
|
||||||
PAUSED_MEDIA_STATES.add(PlaybackState.STATE_NONE);
|
PAUSED_MEDIA_STATES.add(PlaybackState.STATE_NONE);
|
||||||
PAUSED_MEDIA_STATES.add(PlaybackState.STATE_STOPPED);
|
PAUSED_MEDIA_STATES.add(PlaybackState.STATE_STOPPED);
|
||||||
PAUSED_MEDIA_STATES.add(PlaybackState.STATE_PAUSED);
|
PAUSED_MEDIA_STATES.add(PlaybackState.STATE_PAUSED);
|
||||||
PAUSED_MEDIA_STATES.add(PlaybackState.STATE_ERROR);
|
PAUSED_MEDIA_STATES.add(PlaybackState.STATE_ERROR);
|
||||||
PAUSED_MEDIA_STATES.add(PlaybackState.STATE_CONNECTING);
|
CONNECTING_MEDIA_STATES.add(PlaybackState.STATE_CONNECTING);
|
||||||
|
CONNECTING_MEDIA_STATES.add(PlaybackState.STATE_BUFFERING);
|
||||||
}
|
}
|
||||||
|
|
||||||
private final NotificationVisibilityProvider mVisibilityProvider;
|
private final NotificationVisibilityProvider mVisibilityProvider;
|
||||||
@@ -363,7 +365,17 @@ public class NotificationMediaManager implements Dumpable {
|
|||||||
* @return true if playing
|
* @return true if playing
|
||||||
*/
|
*/
|
||||||
public static boolean isPlayingState(int state) {
|
public static boolean isPlayingState(int state) {
|
||||||
return !PAUSED_MEDIA_STATES.contains(state);
|
return !PAUSED_MEDIA_STATES.contains(state)
|
||||||
|
&& !CONNECTING_MEDIA_STATES.contains(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a state should be considered as connecting
|
||||||
|
* @param state a PlaybackState
|
||||||
|
* @return true if connecting or buffering
|
||||||
|
*/
|
||||||
|
public static boolean isConnectingState(int state) {
|
||||||
|
return CONNECTING_MEDIA_STATES.contains(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setUpWithPresenter(NotificationPresenter presenter) {
|
public void setUpWithPresenter(NotificationPresenter presenter) {
|
||||||
|
|||||||
@@ -725,6 +725,25 @@ class MediaDataManagerTest : SysuiTestCase() {
|
|||||||
assertThat(actions.custom1!!.contentDescription).isEqualTo(customDesc[3])
|
assertThat(actions.custom1!!.contentDescription).isEqualTo(customDesc[3])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testPlaybackActions_connecting() {
|
||||||
|
whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
|
||||||
|
val stateActions = PlaybackState.ACTION_PLAY
|
||||||
|
val stateBuilder = PlaybackState.Builder()
|
||||||
|
.setState(PlaybackState.STATE_BUFFERING, 0, 10f)
|
||||||
|
.setActions(stateActions)
|
||||||
|
whenever(controller.playbackState).thenReturn(stateBuilder.build())
|
||||||
|
|
||||||
|
addNotificationAndLoad()
|
||||||
|
|
||||||
|
assertThat(mediaDataCaptor.value!!.semanticActions).isNotNull()
|
||||||
|
val actions = mediaDataCaptor.value!!.semanticActions!!
|
||||||
|
|
||||||
|
assertThat(actions.playOrPause).isNotNull()
|
||||||
|
assertThat(actions.playOrPause!!.contentDescription).isEqualTo(
|
||||||
|
context.getString(R.string.controls_media_button_connecting))
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testPlaybackActions_reservedSpace() {
|
fun testPlaybackActions_reservedSpace() {
|
||||||
val customDesc = arrayOf("custom 1", "custom 2", "custom 3", "custom 4")
|
val customDesc = arrayOf("custom 1", "custom 2", "custom 3", "custom 4")
|
||||||
|
|||||||
Reference in New Issue
Block a user