Introducing Notification.MediaStyle.

Allows you to create a nice music player with all the
trimmings, including:

  * up to 5 actions in the bigContentView, styled as
    transport control buttons
  * up to 2 of those actions in the 1U (!)
  * a nice custom progress bar
  * a tinted background (taken from your Notification.color)
  * a special place to put your MediaSessionToken

Bug: 15147533
Change-Id: Ic20a2b369eb6c5fe4853987a44ffd9bace720c7f
This commit is contained in:
Dan Sandler
2014-05-15 09:36:47 -04:00
parent 7de53d693e
commit 842dd77bb9
13 changed files with 541 additions and 5 deletions

View File

@@ -4456,6 +4456,7 @@ package android.app {
field public static final java.lang.String EXTRA_INFO_TEXT = "android.infoText";
field public static final java.lang.String EXTRA_LARGE_ICON = "android.largeIcon";
field public static final java.lang.String EXTRA_LARGE_ICON_BIG = "android.largeIcon.big";
field public static final java.lang.String EXTRA_MEDIA_SESSION = "android.mediaSession";
field public static final java.lang.String EXTRA_PEOPLE = "android.people";
field public static final java.lang.String EXTRA_PICTURE = "android.picture";
field public static final java.lang.String EXTRA_PROGRESS = "android.progress";
@@ -4624,6 +4625,14 @@ package android.app {
method public android.app.Notification.InboxStyle setSummaryText(java.lang.CharSequence);
}
public static class Notification.MediaStyle extends android.app.Notification.Style {
ctor public Notification.MediaStyle();
ctor public Notification.MediaStyle(android.app.Notification.Builder);
method public android.app.Notification buildStyled(android.app.Notification);
method public android.app.Notification.MediaStyle setMediaSession(android.media.session.MediaSessionToken);
method public android.app.Notification.MediaStyle setShowActionsInCompactView(int...);
}
public static abstract class Notification.Style {
ctor public Notification.Style();
method public android.app.Notification build();

View File

@@ -23,6 +23,7 @@ import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.PorterDuff;
import android.media.AudioManager;
import android.media.session.MediaSessionToken;
import android.net.Uri;
import android.os.BadParcelableException;
import android.os.Build;
@@ -686,6 +687,11 @@ public class Notification implements Parcelable
* notifications, each of which was supplied to {@link InboxStyle#addLine(CharSequence)}.
*/
public static final String EXTRA_TEXT_LINES = "android.textLines";
/**
* {@link #extras} key: A string representing the name of the specific
* {@link android.app.Notification.Style} used to create this notification.
*/
public static final String EXTRA_TEMPLATE = "android.template";
/**
@@ -725,6 +731,13 @@ public class Notification implements Parcelable
*/
public static final String EXTRA_BACKGROUND_IMAGE_URI = "android.backgroundImageUri";
/**
* {@link #extras} key: A
* {@link android.media.session.MediaSessionToken} associated with a
* {@link android.app.Notification.MediaStyle} notification.
*/
public static final String EXTRA_MEDIA_SESSION = "android.mediaSession";
/**
* Value for {@link #EXTRA_AS_HEADS_UP}.
* @hide
@@ -2298,14 +2311,12 @@ public class Notification implements Parcelable
int N = mActions.size();
if (N > 0) {
// Log.d("Notification", "has actions: " + mContentText);
big.setViewVisibility(R.id.actions, View.VISIBLE);
big.setViewVisibility(R.id.action_divider, View.VISIBLE);
if (N>MAX_ACTION_BUTTONS) N=MAX_ACTION_BUTTONS;
big.removeAllViews(R.id.actions);
for (int i=0; i<N; i++) {
final RemoteViews button = generateActionButton(mActions.get(i));
//Log.d("Notification", "adding action " + i + ": " + mActions.get(i).title);
big.addView(R.id.actions, button);
}
}
@@ -3014,4 +3025,142 @@ public class Notification implements Parcelable
return wip;
}
}
/**
* Notification style for media playback notifications.
*
* In the expanded form, {@link Notification#bigContentView}, up to 5
* {@link Notification.Action}s specified with
* {@link Notification.Builder#addAction(int, CharSequence, PendingIntent) addAction} will be
* shown as icon-only pushbuttons, suitable for transport controls. The Bitmap given to
* {@link Notification.Builder#setLargeIcon(android.graphics.Bitmap) setLargeIcon()} will be
* treated as album artwork.
*
* Unlike the other styles provided here, MediaStyle can also modify the standard-size
* {@link Notification#contentView}; by providing action indices to
* {@link #setShowActionsInCompactView(int...)} you can promote up to 2 actions to be displayed
* in the standard view alongside the usual content.
*
* Finally, if you attach a {@link android.media.session.MediaSessionToken} using
* {@link android.app.Notification.MediaStyle#setMediaSession(MediaSessionToken)},
* the System UI can identify this as a notification representing an active media session
* and respond accordingly (by showing album artwork in the lockscreen, for example).
*
* To use this style with your Notification, feed it to
* {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so:
* <pre class="prettyprint">
* Notification noti = new Notification.Builder()
* .setSmallIcon(R.drawable.ic_stat_player)
* .setContentTitle(&quot;Track title&quot;) // these three lines are optional
* .setContentText(&quot;Artist - Album&quot;) // if you use
* .setLargeIcon(albumArtBitmap)) // setMediaSession(token, true)
* .setMediaSession(mySession, true)
* .setStyle(<b>new Notification.MediaStyle()</b>)
* .build();
* </pre>
*
* @see Notification#bigContentView
*/
public static class MediaStyle extends Style {
static final int MAX_MEDIA_BUTTONS_IN_COMPACT = 2;
static final int MAX_MEDIA_BUTTONS = 5;
private int[] mActionsToShowInCompact = null;
private MediaSessionToken mToken;
public MediaStyle() {
}
public MediaStyle(Builder builder) {
setBuilder(builder);
}
/**
* Request up to 2 actions (by index in the order of addition) to be shown in the compact
* notification view.
*/
public MediaStyle setShowActionsInCompactView(int...actions) {
mActionsToShowInCompact = actions;
return this;
}
/**
* Attach a {@link android.media.session.MediaSessionToken} to this Notification to provide
* additional playback information and control to the SystemUI.
*/
public MediaStyle setMediaSession(MediaSessionToken token) {
mToken = token;
return this;
}
@Override
public Notification buildStyled(Notification wip) {
wip.contentView = makeMediaContentView();
wip.bigContentView = makeMediaBigContentView();
return wip;
}
/** @hide */
@Override
public void addExtras(Bundle extras) {
super.addExtras(extras);
if (mToken != null) {
extras.putParcelable(EXTRA_MEDIA_SESSION, mToken);
}
}
private RemoteViews generateMediaActionButton(Action action) {
final boolean tombstone = (action.actionIntent == null);
RemoteViews button = new RemoteViews(mBuilder.mContext.getPackageName(),
R.layout.notification_quantum_media_action);
button.setImageViewResource(R.id.action0, action.icon);
if (!tombstone) {
button.setOnClickPendingIntent(R.id.action0, action.actionIntent);
}
button.setContentDescription(R.id.action0, action.title);
return button;
}
private RemoteViews makeMediaContentView() {
RemoteViews view = mBuilder.applyStandardTemplate(
R.layout.notification_template_quantum_media, true /* 1U */);
final int numActions = mBuilder.mActions.size();
final int N = mActionsToShowInCompact == null
? 0
: Math.min(mActionsToShowInCompact.length, MAX_MEDIA_BUTTONS_IN_COMPACT);
if (N > 0) {
view.removeAllViews(R.id.actions);
for (int i = 0; i < N; i++) {
if (i >= numActions) {
throw new IllegalArgumentException(String.format(
"setShowActionsInCompactView: action %d out of bounds (max %d)",
i, numActions - 1));
}
final Action action = mBuilder.mActions.get(mActionsToShowInCompact[i]);
final RemoteViews button = generateMediaActionButton(action);
view.addView(R.id.actions, button);
}
}
return view;
}
private RemoteViews makeMediaBigContentView() {
RemoteViews big = mBuilder.applyStandardTemplate(
R.layout.notification_template_quantum_big_media, false);
final int N = Math.min(mBuilder.mActions.size(), MAX_MEDIA_BUTTONS);
if (N > 0) {
big.removeAllViews(R.id.actions);
for (int i=0; i<N; i++) {
final RemoteViews button = generateMediaActionButton(mBuilder.mActions.get(i));
big.addView(R.id.actions, button);
}
}
return big;
}
}
}

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2014 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.
-->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@id/background" android:drawable="@color/transparent">
</item>
<item android:id="@id/secondaryProgress">
<scale android:scaleWidth="100%"
android:drawable="@color/notification_media_progress" />
</item>
<item android:id="@id/progress">
<scale android:scaleWidth="100%"
android:drawable="@color/notification_media_progress" />
</item>
</layer-list>

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2014 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
-->
<ImageButton xmlns:android="http://schemas.android.com/apk/res/android"
style="@android:style/Widget.Quantum.Light.Button.Borderless.Small"
android:id="@+id/action0"
android:layout_width="60dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
/>

View File

@@ -15,7 +15,7 @@
-->
<Chronometer android:id="@+id/chronometer" xmlns:android="http://schemas.android.com/apk/res/android"
android:textAppearance="@style/TextAppearance.StatusBar.EventContent.Time"
android:textAppearance="@style/TextAppearance.StatusBar.Quantum.EventContent.Time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"

View File

@@ -15,7 +15,7 @@
-->
<DateTimeView android:id="@+id/time" xmlns:android="http://schemas.android.com/apk/res/android"
android:textAppearance="@style/TextAppearance.StatusBar.EventContent.Time"
android:textAppearance="@style/TextAppearance.StatusBar.Quantum.EventContent.Time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"

View File

@@ -0,0 +1,158 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2014 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
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:internal="http://schemas.android.com/apk/prv/res/android"
android:id="@+id/status_bar_latest_event_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
internal:layout_minHeight="65dp"
internal:layout_maxHeight="unbounded"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="fill_vertical"
android:minHeight="@dimen/notification_large_icon_height"
android:orientation="vertical"
android:gravity="top"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="@dimen/notification_large_icon_width"
android:minHeight="@dimen/notification_large_icon_height"
android:paddingTop="2dp"
android:orientation="vertical"
android:background="@color/notification_media_info_bg"
>
<LinearLayout
android:id="@+id/line1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="6dp"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:orientation="horizontal"
>
<TextView android:id="@+id/title"
android:textAppearance="@style/TextAppearance.StatusBar.Quantum.EventContent.Title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:ellipsize="marquee"
android:fadingEdge="horizontal"
android:layout_weight="1"
/>
<ViewStub android:id="@+id/time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="0"
android:visibility="gone"
android:layout="@layout/notification_template_part_time"
/>
<ViewStub android:id="@+id/chronometer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="0"
android:visibility="gone"
android:layout="@layout/notification_template_part_chronometer"
/>
</LinearLayout>
<TextView android:id="@+id/text2"
android:textAppearance="@style/TextAppearance.StatusBar.Quantum.EventContent.Line2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="-2dp"
android:layout_marginBottom="-2dp"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:singleLine="true"
android:fadingEdge="horizontal"
android:ellipsize="marquee"
android:visibility="gone"
/>
<TextView android:id="@+id/big_text"
android:textAppearance="@style/TextAppearance.StatusBar.Quantum.EventContent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:singleLine="false"
android:visibility="gone"
/>
<LinearLayout
android:id="@+id/line3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:orientation="horizontal"
android:gravity="center_vertical"
>
<TextView android:id="@+id/text"
android:textAppearance="@style/TextAppearance.StatusBar.Quantum.EventContent"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_gravity="center"
android:singleLine="true"
android:ellipsize="marquee"
android:fadingEdge="horizontal"
/>
<TextView android:id="@+id/info"
android:textAppearance="@style/TextAppearance.StatusBar.Quantum.EventContent.Info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_weight="0"
android:singleLine="true"
android:gravity="center"
android:paddingStart="8dp"
/>
</LinearLayout>
</LinearLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="60dp"
android:id="@+id/media_action_area"
android:background="@color/notification_media_action_bg"
>
<LinearLayout
android:id="@+id/actions"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="6dp"
android:orientation="horizontal"
>
<!-- media buttons will be added here -->
</LinearLayout>
<ProgressBar
android:id="@android:id/progress"
android:layout_width="match_parent"
android:layout_height="6dp"
android:layout_gravity="top"
android:visibility="gone"
style="@style/Widget.StatusBar.Quantum.ProgressBar"
/>
</FrameLayout>
</LinearLayout>
<include layout="@layout/notification_template_icon_group"
android:layout_width="@dimen/notification_large_icon_width"
android:layout_height="@dimen/notification_large_icon_height"
/>
</FrameLayout>

View File

@@ -0,0 +1,135 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2014 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
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:internal="http://schemas.android.com/apk/prv/res/android"
android:id="@+id/status_bar_latest_event_content"
android:layout_width="match_parent"
android:layout_height="64dp"
android:orientation="horizontal"
internal:layout_minHeight="64dp"
internal:layout_maxHeight="64dp"
>
<include layout="@layout/notification_template_icon_group"
android:layout_width="@dimen/notification_large_icon_width"
android:layout_height="@dimen/notification_large_icon_height"
android:layout_weight="0"
/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_gravity="fill_vertical"
android:minHeight="@dimen/notification_large_icon_height"
android:orientation="vertical"
android:paddingEnd="8dp"
android:paddingTop="2dp"
android:paddingBottom="2dp"
android:gravity="top"
>
<LinearLayout
android:id="@+id/line1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="6dp"
android:layout_marginStart="8dp"
android:orientation="horizontal"
>
<TextView android:id="@+id/title"
android:textAppearance="@style/TextAppearance.StatusBar.Quantum.EventContent.Title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:ellipsize="marquee"
android:fadingEdge="horizontal"
android:layout_weight="1"
/>
<ViewStub android:id="@+id/time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="0"
android:visibility="gone"
android:layout="@layout/notification_template_part_time"
/>
<ViewStub android:id="@+id/chronometer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="0"
android:visibility="gone"
android:layout="@layout/notification_template_part_chronometer"
/>
</LinearLayout>
<TextView android:id="@+id/text2"
android:textAppearance="@style/TextAppearance.StatusBar.Quantum.EventContent.Line2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="-2dp"
android:layout_marginBottom="-2dp"
android:layout_marginStart="8dp"
android:singleLine="true"
android:fadingEdge="horizontal"
android:ellipsize="marquee"
android:visibility="gone"
/>
<ProgressBar
android:id="@android:id/progress"
android:layout_width="match_parent"
android:layout_height="12dp"
android:layout_marginStart="8dp"
android:visibility="gone"
style="@style/Widget.StatusBar.Quantum.ProgressBar"
/>
<LinearLayout
android:id="@+id/line3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_marginStart="8dp"
>
<TextView android:id="@+id/text"
android:textAppearance="@style/TextAppearance.StatusBar.Quantum.EventContent"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_gravity="center"
android:singleLine="true"
android:ellipsize="marquee"
android:fadingEdge="horizontal"
/>
<TextView android:id="@+id/info"
android:textAppearance="@style/TextAppearance.StatusBar.Quantum.EventContent.Info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_weight="0"
android:singleLine="true"
android:gravity="center"
android:paddingStart="8dp"
/>
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/actions"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center_vertical|end"
android:orientation="horizontal"
>
<!-- media buttons will be added here -->
</LinearLayout>
</LinearLayout>

View File

@@ -129,6 +129,10 @@
<color name="notification_icon_bg_color">#ffa3a3a3</color>
<color name="notification_action_legacy_color_filter">#ff555555</color>
<color name="notification_media_action_bg">#00000000</color>
<color name="notification_media_info_bg">#40FFFFFF</color>
<color name="notification_media_progress">#FFFFFFFF</color>
<!-- Keyguard colors -->
<color name="keyguard_avatar_frame_color">#ffffffff</color>
<color name="keyguard_avatar_frame_shadow_color">#80000000</color>

View File

@@ -299,6 +299,10 @@ please see styles_device_defaults.xml.
<style name="TextAppearance.StatusBar.Quantum.EventContent.Emphasis">
<item name="android:textColor">#66000000</item>
</style>
<style name="Widget.StatusBar.Quantum.ProgressBar"
parent="Widget.Quantum.Light.ProgressBar.Horizontal">
<item name="android:progressDrawable">@drawable/notification_quantum_media_progress</item>
</style>
<style name="Widget.StatusBar.Quantum.ProgressBar"
parent="Widget.Quantum.Light.ProgressBar.Horizontal">

View File

@@ -1662,13 +1662,21 @@
<java-symbol type="layout" name="notification_template_quantum_big_picture" />
<java-symbol type="layout" name="notification_template_quantum_big_text" />
<java-symbol type="layout" name="notification_template_quantum_inbox" />
<java-symbol type="layout" name="notification_template_quantum_media" />
<java-symbol type="layout" name="notification_template_quantum_big_media" />
<java-symbol type="layout" name="notification_template_icon_group" />
<java-symbol type="layout" name="notification_quantum_media_action" />
<java-symbol type="color" name="notification_action_legacy_color_filter" />
<java-symbol type="color" name="notification_icon_bg_color" />
<java-symbol type="drawable" name="notification_icon_legacy_bg" />
<java-symbol type="drawable" name="notification_icon_legacy_bg_inset" />
<java-symbol type="drawable" name="notification_quantum_bg_dim" />
<java-symbol type="drawable" name="notification_quantum_bg" />
<java-symbol type="drawable" name="notification_quantum_media_progress" />
<java-symbol type="color" name="notification_media_action_bg" />
<java-symbol type="color" name="notification_media_info_bg" />
<java-symbol type="color" name="notification_media_progress" />
<java-symbol type="id" name="media_action_area" />
<!-- From SystemUI -->
<java-symbol type="anim" name="push_down_in" />

View File

@@ -446,9 +446,25 @@ public abstract class BaseStatusBar extends SystemUI implements
com.android.internal.R.drawable.notification_bg,
com.android.internal.R.drawable.notification_bg_dim);
}
} else {
// Using platform templates
final int color = sbn.getNotification().color;
if (isMediaNotification(entry)) {
entry.row.setBackgroundResourceIds(
com.android.internal.R.drawable.notification_quantum_bg,
color,
com.android.internal.R.drawable.notification_quantum_bg_dim,
color);
}
}
}
private boolean isMediaNotification(NotificationData.Entry entry) {
// TODO: confirm that there's a valid media key
return entry.expandedBig != null &&
entry.expandedBig.findViewById(com.android.internal.R.id.media_action_area) != null;
}
private void startApplicationDetailsActivity(String packageName) {
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
Uri.fromParts("package", packageName, null));

View File

@@ -36,7 +36,7 @@ public class NotificationData {
public View expanded; // the inflated RemoteViews
public View expandedPublic; // for insecure lockscreens
public ImageView largeIcon;
private View expandedBig;
public View expandedBig;
private boolean interruption;
public Entry() {}
public Entry(IBinder key, StatusBarNotification n, StatusBarIconView ic) {