diff --git a/core/java/com/android/internal/widget/MediaNotificationView.java b/core/java/com/android/internal/widget/MediaNotificationView.java
index 7609b67e492bd..498bc5a7d598a 100644
--- a/core/java/com/android/internal/widget/MediaNotificationView.java
+++ b/core/java/com/android/internal/widget/MediaNotificationView.java
@@ -39,6 +39,7 @@ public class MediaNotificationView extends FrameLayout {
private View mActions;
private View mHeader;
private View mMainColumn;
+ private View mMediaContent;
private int mImagePushIn;
public MediaNotificationView(Context context) {
@@ -70,7 +71,7 @@ public class MediaNotificationView extends FrameLayout {
(MarginLayoutParams) mRightIcon.getLayoutParams();
int imageEndMargin = layoutParams.getMarginEnd();
size -= imageEndMargin;
- int fullHeight = getMeasuredHeight();
+ int fullHeight = mMediaContent.getMeasuredHeight();
if (size > fullHeight) {
size = fullHeight;
} else if (size < fullHeight) {
@@ -154,5 +155,6 @@ public class MediaNotificationView extends FrameLayout {
mActions = findViewById(com.android.internal.R.id.media_actions);
mHeader = findViewById(com.android.internal.R.id.notification_header);
mMainColumn = findViewById(com.android.internal.R.id.notification_main_column);
+ mMediaContent = findViewById(com.android.internal.R.id.notification_media_content);
}
}
diff --git a/core/res/res/layout/notification_material_media_seekbar.xml b/core/res/res/layout/notification_material_media_seekbar.xml
new file mode 100644
index 0000000000000..1b691d68a2013
--- /dev/null
+++ b/core/res/res/layout/notification_material_media_seekbar.xml
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/core/res/res/layout/notification_template_material_big_media.xml b/core/res/res/layout/notification_template_material_big_media.xml
index 56f7a593cc439..3267f726c69d0 100644
--- a/core/res/res/layout/notification_template_material_big_media.xml
+++ b/core/res/res/layout/notification_template_material_big_media.xml
@@ -39,6 +39,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
+ android:id="@+id/notification_media_content"
>
+
diff --git a/core/res/res/layout/notification_template_material_media.xml b/core/res/res/layout/notification_template_material_media.xml
index 01b0866f428c4..64d91ad867f92 100644
--- a/core/res/res/layout/notification_template_material_media.xml
+++ b/core/res/res/layout/notification_template_material_media.xml
@@ -34,49 +34,61 @@
android:layout_width="match_parent"
android:layout_height="@dimen/media_notification_header_height" />
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index c9098736fdc9e..1973bf31053b2 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -195,6 +195,12 @@
+
+
+
+
+
+
@@ -1573,6 +1579,7 @@
+
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 1060211cebf53..e23fda2b15c8e 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -83,6 +83,9 @@
146dp
+
+ 160dp
+
64dp
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index bed2426021a15..b8e33a8e0d2d7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -90,6 +90,7 @@ import com.android.systemui.statusbar.notification.VisualStabilityManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.logging.NotificationCounters;
import com.android.systemui.statusbar.notification.row.NotificationInflater.InflationFlag;
+import com.android.systemui.statusbar.notification.row.wrapper.NotificationMediaTemplateViewWrapper;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
import com.android.systemui.statusbar.notification.stack.AmbientState;
import com.android.systemui.statusbar.notification.stack.AnimationProperties;
@@ -144,6 +145,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
private int mNotificationMinHeightBeforeP;
private int mNotificationMinHeight;
private int mNotificationMinHeightLarge;
+ private int mNotificationMinHeightMedia;
private int mNotificationMaxHeight;
private int mIncreasedPaddingBetweenElements;
private int mNotificationLaunchHeight;
@@ -652,8 +654,15 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
boolean beforeN = mEntry.targetSdk < Build.VERSION_CODES.N;
boolean beforeP = mEntry.targetSdk < Build.VERSION_CODES.P;
int minHeight;
+
+ View expandedView = layout.getExpandedChild();
+ boolean isMediaLayout = expandedView != null
+ && expandedView.findViewById(com.android.internal.R.id.media_actions) != null;
+
if (customView && beforeP && !mIsSummaryWithChildren) {
minHeight = beforeN ? mNotificationMinHeightBeforeN : mNotificationMinHeightBeforeP;
+ } else if (isMediaLayout && !NotificationMediaTemplateViewWrapper.HIDE_COMPACT_SCRUBBER) {
+ minHeight = mNotificationMinHeightMedia;
} else if (mUseIncreasedCollapsedHeight && layout == mPrivateLayout) {
minHeight = mNotificationMinHeightLarge;
} else {
@@ -1642,6 +1651,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
R.dimen.notification_min_height);
mNotificationMinHeightLarge = NotificationUtils.getFontScaledHeight(mContext,
R.dimen.notification_min_height_increased);
+ mNotificationMinHeightMedia = NotificationUtils.getFontScaledHeight(mContext,
+ R.dimen.notification_min_height_media);
mNotificationMaxHeight = NotificationUtils.getFontScaledHeight(mContext,
R.dimen.notification_max_height);
mMaxHeadsUpHeightBeforeN = NotificationUtils.getFontScaledHeight(mContext,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java
index 5a9a56865a5ce..ddda3e50fc2c9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java
@@ -16,26 +16,222 @@
package com.android.systemui.statusbar.notification.row.wrapper;
-import android.content.Context;
-import android.view.View;
+import static com.android.systemui.Dependency.MAIN_HANDLER;
+import android.app.Notification;
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.media.MediaMetadata;
+import android.media.session.MediaController;
+import android.media.session.MediaSession;
+import android.media.session.PlaybackState;
+import android.os.Handler;
+import android.text.format.DateUtils;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewStub;
+import android.widget.SeekBar;
+import android.widget.TextView;
+
+import com.android.internal.R;
+import com.android.systemui.Dependency;
import com.android.systemui.statusbar.TransformableView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import java.util.Timer;
+import java.util.TimerTask;
+
/**
* Wraps a notification containing a media template
*/
public class NotificationMediaTemplateViewWrapper extends NotificationTemplateViewWrapper {
+ private static final String TAG = "NotificationMediaTVW";
+ private static final long PROGRESS_UPDATE_INTERVAL = 1000; // 1s
+ private static final String COMPACT_MEDIA_TAG = "media";
+ private final Handler mHandler = Dependency.get(MAIN_HANDLER);
+ private Timer mSeekBarTimer;
+ private View mActions;
+ private SeekBar mSeekBar;
+ private TextView mSeekBarElapsedTime;
+ private TextView mSeekBarTotalTime;
+ private long mDuration = 0;
+ private MediaController mMediaController;
+ private View mSeekBarView;
+ private Context mContext;
+
+ // TODO: implement as phenotype flag
+ public static final boolean HIDE_COMPACT_SCRUBBER = true;
+
+ private SeekBar.OnSeekBarChangeListener mSeekListener = new SeekBar.OnSeekBarChangeListener() {
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ if (mMediaController != null && canSeekMedia()) {
+ mMediaController.getTransportControls().seekTo(mSeekBar.getProgress());
+ }
+ }
+ };
+
+ private MediaController.Callback mMediaCallback = new MediaController.Callback() {
+ @Override
+ public void onSessionDestroyed() {
+ clearTimer();
+ mMediaController.unregisterCallback(this);
+ }
+
+ @Override
+ public void onPlaybackStateChanged(PlaybackState state) {
+ if (state.getState() != PlaybackState.STATE_PLAYING) {
+ clearTimer();
+ } else if (mSeekBarTimer == null) {
+ startTimer();
+ }
+ }
+ };
+
protected NotificationMediaTemplateViewWrapper(Context ctx, View view,
ExpandableNotificationRow row) {
super(ctx, view, row);
+ mContext = ctx;
}
- View mActions;
-
private void resolveViews() {
mActions = mView.findViewById(com.android.internal.R.id.media_actions);
+
+ final MediaSession.Token token = mRow.getEntry().notification.getNotification().extras
+ .getParcelable(Notification.EXTRA_MEDIA_SESSION);
+
+ if (token == null || (COMPACT_MEDIA_TAG.equals(mView.getTag()) && HIDE_COMPACT_SCRUBBER)) {
+ if (mSeekBarView != null) {
+ mSeekBarView.setVisibility(View.GONE);
+ }
+ return;
+ }
+
+ // Check for existing media controller and clean up / create as necessary
+ if (mMediaController == null || !mMediaController.getSessionToken().equals(token)) {
+ if (mMediaController != null) {
+ mMediaController.unregisterCallback(mMediaCallback);
+ }
+ mMediaController = new MediaController(mContext, token);
+ }
+
+ if (mMediaController.getMetadata() != null) {
+ long duration = mMediaController.getMetadata().getLong(
+ MediaMetadata.METADATA_KEY_DURATION);
+ if (duration <= 0) {
+ // Don't include the seekbar if this is a livestream
+ Log.d(TAG, "removing seekbar");
+ if (mSeekBarView != null) {
+ mSeekBarView.setVisibility(View.GONE);
+ }
+ return;
+ } else {
+ // Otherwise, make sure the seekbar is visible
+ if (mSeekBarView != null) {
+ mSeekBarView.setVisibility(View.VISIBLE);
+ }
+ }
+ }
+
+ // Inflate the seekbar template
+ ViewStub stub = mView.findViewById(R.id.notification_media_seekbar_container);
+ if (stub instanceof ViewStub) {
+ LayoutInflater layoutInflater = LayoutInflater.from(stub.getContext());
+ stub.setLayoutInflater(layoutInflater);
+ stub.setLayoutResource(R.layout.notification_material_media_seekbar);
+ mSeekBarView = stub.inflate();
+
+ mSeekBar = mSeekBarView.findViewById(R.id.notification_media_progress_bar);
+ mSeekBar.setOnSeekBarChangeListener(mSeekListener);
+
+ mSeekBarElapsedTime = mSeekBarView.findViewById(R.id.notification_media_elapsed_time);
+ mSeekBarTotalTime = mSeekBarView.findViewById(R.id.notification_media_total_time);
+
+ if (mSeekBarTimer == null) {
+ // Disable seeking if it is not supported for this media session
+ if (!canSeekMedia()) {
+ mSeekBar.getThumb().setAlpha(0);
+ mSeekBar.setEnabled(false);
+ } else {
+ mSeekBar.getThumb().setAlpha(255);
+ mSeekBar.setEnabled(true);
+ }
+
+ startTimer();
+
+ mMediaController.registerCallback(mMediaCallback);
+ }
+ }
+ updateSeekBarTint(mSeekBarView);
+ }
+
+ private void startTimer() {
+ clearTimer();
+ mSeekBarTimer = new Timer(true /* isDaemon */);
+ mSeekBarTimer.schedule(new TimerTask() {
+ @Override
+ public void run() {
+ mHandler.post(mUpdatePlaybackUi);
+ }
+ }, 0, PROGRESS_UPDATE_INTERVAL);
+ }
+
+ private void clearTimer() {
+ if (mSeekBarTimer != null) {
+ // TODO: also trigger this when the notification panel is collapsed
+ mSeekBarTimer.cancel();
+ mSeekBarTimer.purge();
+ mSeekBarTimer = null;
+ }
+ }
+
+ private boolean canSeekMedia() {
+ if (mMediaController == null || mMediaController.getPlaybackState() == null) {
+ return false;
+ }
+
+ long actions = mMediaController.getPlaybackState().getActions();
+ return (actions == 0 || (actions & PlaybackState.ACTION_SEEK_TO) != 0);
+ }
+
+ protected final Runnable mUpdatePlaybackUi = new Runnable() {
+ @Override
+ public void run() {
+ if (mMediaController != null && mMediaController.getMetadata() != null
+ && mSeekBar != null) {
+ long position = mMediaController.getPlaybackState().getPosition();
+ long duration = mMediaController.getMetadata().getLong(
+ MediaMetadata.METADATA_KEY_DURATION);
+
+ if (mDuration != duration) {
+ mDuration = duration;
+ mSeekBar.setMax((int) mDuration);
+ mSeekBarTotalTime.setText(millisecondsToTimeString(duration));
+ }
+ mSeekBar.setProgress((int) position);
+
+ mSeekBarElapsedTime.setText(millisecondsToTimeString(position));
+ } else {
+ // We no longer have a media session / notification
+ clearTimer();
+ }
+ }
+ };
+
+ private String millisecondsToTimeString(long milliseconds) {
+ long seconds = milliseconds / 1000;
+ String text = DateUtils.formatElapsedTime(seconds);
+ return text;
}
@Override
@@ -46,6 +242,27 @@ public class NotificationMediaTemplateViewWrapper extends NotificationTemplateVi
super.onContentUpdated(row);
}
+ private void updateSeekBarTint(View seekBarContainer) {
+ if (seekBarContainer == null) {
+ return;
+ }
+
+ if (this.getNotificationHeader() == null) {
+ return;
+ }
+
+ int tintColor = getNotificationHeader().getOriginalIconColor();
+ mSeekBarElapsedTime.setTextColor(tintColor);
+ mSeekBarTotalTime.setTextColor(tintColor);
+
+ ColorStateList tintList = ColorStateList.valueOf(tintColor);
+ mSeekBar.setThumbTintList(tintList);
+ tintList = tintList.withAlpha(192); // 75%
+ mSeekBar.setProgressTintList(tintList);
+ tintList = tintList.withAlpha(128); // 50%
+ mSeekBar.setProgressBackgroundTintList(tintList);
+ }
+
@Override
protected void updateTransformedTypes() {
// This also clears the existing types