Merge "Lock screen media controls" into rvc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
d6a8e5128e
153
packages/SystemUI/res/layout/keyguard_media_header.xml
Normal file
153
packages/SystemUI/res/layout/keyguard_media_header.xml
Normal file
@@ -0,0 +1,153 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (C) 2020 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
|
||||
-->
|
||||
|
||||
<!-- Layout for media controls on the lockscreen -->
|
||||
<com.android.systemui.statusbar.notification.stack.MediaHeaderView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="0dp"
|
||||
android:paddingEnd="0dp"
|
||||
android:focusable="true"
|
||||
android:clickable="true"
|
||||
>
|
||||
|
||||
<!-- Background views required by ActivatableNotificationView. -->
|
||||
<com.android.systemui.statusbar.notification.row.NotificationBackgroundView
|
||||
android:id="@+id/backgroundNormal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
/>
|
||||
|
||||
<com.android.systemui.statusbar.notification.row.NotificationBackgroundView
|
||||
android:id="@+id/backgroundDimmed"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
/>
|
||||
|
||||
<com.android.systemui.statusbar.notification.FakeShadowView
|
||||
android:id="@+id/fake_shadow"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
/>
|
||||
|
||||
<!-- Layout for media controls. -->
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/keyguard_media_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_horizontal|fill_vertical"
|
||||
android:padding="16dp"
|
||||
>
|
||||
<ImageView
|
||||
android:id="@+id/album_art"
|
||||
android:layout_width="@dimen/qs_media_album_size"
|
||||
android:layout_height="@dimen/qs_media_album_size"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_weight="0"
|
||||
/>
|
||||
|
||||
<!-- Media information -->
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="@dimen/qs_media_album_size"
|
||||
android:layout_weight="1"
|
||||
>
|
||||
<LinearLayout
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
>
|
||||
<com.android.internal.widget.CachingIconView
|
||||
android:id="@+id/icon"
|
||||
android:layout_width="16dp"
|
||||
android:layout_height="16dp"
|
||||
android:layout_marginEnd="5dp"
|
||||
/>
|
||||
<TextView
|
||||
android:id="@+id/app_name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="14sp"
|
||||
android:singleLine="true"
|
||||
/>
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Song name -->
|
||||
<TextView
|
||||
android:id="@+id/header_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true"
|
||||
android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
|
||||
android:textSize="18sp"
|
||||
android:paddingBottom="6dp"
|
||||
android:gravity="center"/>
|
||||
|
||||
<!-- Artist name -->
|
||||
<TextView
|
||||
android:id="@+id/header_artist"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="@*android:string/config_bodyFontFamily"
|
||||
android:textSize="14sp"
|
||||
android:singleLine="true"
|
||||
/>
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Controls -->
|
||||
<LinearLayout
|
||||
android:id="@+id/media_actions"
|
||||
android:orientation="horizontal"
|
||||
android:layoutDirection="ltr"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:layout_gravity="center"
|
||||
>
|
||||
<ImageButton
|
||||
style="@android:style/Widget.Material.Button.Borderless.Small"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:gravity="center"
|
||||
android:visibility="gone"
|
||||
android:id="@+id/action0"
|
||||
/>
|
||||
<ImageButton
|
||||
style="@android:style/Widget.Material.Button.Borderless.Small"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:gravity="center"
|
||||
android:visibility="gone"
|
||||
android:id="@+id/action1"
|
||||
/>
|
||||
<ImageButton
|
||||
style="@android:style/Widget.Material.Button.Borderless.Small"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:gravity="center"
|
||||
android:visibility="gone"
|
||||
android:id="@+id/action2"
|
||||
/>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
</com.android.systemui.statusbar.notification.stack.MediaHeaderView>
|
||||
@@ -0,0 +1,266 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.
|
||||
*/
|
||||
|
||||
package com.android.keyguard;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.Icon;
|
||||
import android.media.MediaMetadata;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.core.graphics.drawable.RoundedBitmapDrawable;
|
||||
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
|
||||
import androidx.palette.graphics.Palette;
|
||||
|
||||
import com.android.internal.util.ContrastColorUtil;
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.dagger.qualifiers.Background;
|
||||
import com.android.systemui.statusbar.notification.MediaNotificationProcessor;
|
||||
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
|
||||
import com.android.systemui.statusbar.notification.stack.MediaHeaderView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
/**
|
||||
* Media controls to display on the lockscreen
|
||||
*
|
||||
* TODO: Should extend MediaControlPanel to avoid code duplication.
|
||||
* Unfortunately, it isn't currently possible because the ActivatableNotificationView background is
|
||||
* different.
|
||||
*/
|
||||
@Singleton
|
||||
public class KeyguardMediaPlayer {
|
||||
|
||||
private static final String TAG = "KeyguardMediaPlayer";
|
||||
// Buttons that can be displayed on lock screen media controls.
|
||||
private static final int[] ACTION_IDS = {R.id.action0, R.id.action1, R.id.action2};
|
||||
|
||||
private final Context mContext;
|
||||
private final Executor mBackgroundExecutor;
|
||||
private float mAlbumArtRadius;
|
||||
private int mAlbumArtSize;
|
||||
private View mMediaNotifView;
|
||||
|
||||
@Inject
|
||||
public KeyguardMediaPlayer(Context context, @Background Executor backgroundExecutor) {
|
||||
mContext = context;
|
||||
mBackgroundExecutor = backgroundExecutor;
|
||||
loadDimens();
|
||||
}
|
||||
|
||||
/** Binds media controls to a view hierarchy. */
|
||||
public void bindView(View v) {
|
||||
if (mMediaNotifView != null) {
|
||||
throw new IllegalStateException("cannot bind views, already bound");
|
||||
}
|
||||
mMediaNotifView = v;
|
||||
loadDimens();
|
||||
}
|
||||
|
||||
/** Unbinds media controls. */
|
||||
public void unbindView() {
|
||||
if (mMediaNotifView == null) {
|
||||
throw new IllegalStateException("cannot unbind views, nothing bound");
|
||||
}
|
||||
mMediaNotifView = null;
|
||||
}
|
||||
|
||||
/** Clear the media controls because there isn't an active session. */
|
||||
public void clearControls() {
|
||||
if (mMediaNotifView != null) {
|
||||
mMediaNotifView.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the media player
|
||||
*
|
||||
* TODO: consider registering a MediaLister instead of exposing this update method.
|
||||
*
|
||||
* @param entry Media notification that will be used to update the player
|
||||
* @param appIcon Icon for the app playing the media
|
||||
* @param mediaMetadata Media metadata that will be used to update the player
|
||||
*/
|
||||
public void updateControls(NotificationEntry entry, Icon appIcon,
|
||||
MediaMetadata mediaMetadata) {
|
||||
if (mMediaNotifView == null) {
|
||||
throw new IllegalStateException("cannot update controls, views not bound");
|
||||
}
|
||||
if (mediaMetadata == null) {
|
||||
throw new IllegalArgumentException("media metadata was null");
|
||||
}
|
||||
mMediaNotifView.setVisibility(View.VISIBLE);
|
||||
|
||||
Notification notif = entry.getSbn().getNotification();
|
||||
|
||||
// Computed foreground and background color based on album art.
|
||||
int fgColor = notif.color;
|
||||
int bgColor = entry.getRow() == null ? -1 : entry.getRow().getCurrentBackgroundTint();
|
||||
Bitmap artworkBitmap = mediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ART);
|
||||
if (artworkBitmap == null) {
|
||||
artworkBitmap = mediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
|
||||
}
|
||||
if (artworkBitmap != null) {
|
||||
// If we have art, get colors from that
|
||||
Palette p = MediaNotificationProcessor.generateArtworkPaletteBuilder(artworkBitmap)
|
||||
.generate();
|
||||
Palette.Swatch swatch = MediaNotificationProcessor.findBackgroundSwatch(p);
|
||||
bgColor = swatch.getRgb();
|
||||
fgColor = MediaNotificationProcessor.selectForegroundColor(bgColor, p);
|
||||
}
|
||||
// Make sure colors will be legible
|
||||
boolean isDark = !ContrastColorUtil.isColorLight(bgColor);
|
||||
fgColor = ContrastColorUtil.resolveContrastColor(mContext, fgColor, bgColor,
|
||||
isDark);
|
||||
fgColor = ContrastColorUtil.ensureTextContrast(fgColor, bgColor, isDark);
|
||||
|
||||
// Album art
|
||||
ImageView albumView = mMediaNotifView.findViewById(R.id.album_art);
|
||||
if (albumView != null) {
|
||||
// Resize art in a background thread
|
||||
final Bitmap bm = artworkBitmap;
|
||||
mBackgroundExecutor.execute(() -> processAlbumArt(bm, albumView));
|
||||
}
|
||||
|
||||
// App icon
|
||||
ImageView appIconView = mMediaNotifView.findViewById(R.id.icon);
|
||||
if (appIconView != null) {
|
||||
Drawable iconDrawable = appIcon.loadDrawable(mContext);
|
||||
iconDrawable.setTint(fgColor);
|
||||
appIconView.setImageDrawable(iconDrawable);
|
||||
}
|
||||
|
||||
// App name
|
||||
TextView appName = mMediaNotifView.findViewById(R.id.app_name);
|
||||
if (appName != null) {
|
||||
Notification.Builder builder = Notification.Builder.recoverBuilder(mContext, notif);
|
||||
String appNameString = builder.loadHeaderAppName();
|
||||
appName.setText(appNameString);
|
||||
appName.setTextColor(fgColor);
|
||||
}
|
||||
|
||||
// Song name
|
||||
TextView titleText = mMediaNotifView.findViewById(R.id.header_title);
|
||||
if (titleText != null) {
|
||||
String songName = mediaMetadata.getString(MediaMetadata.METADATA_KEY_TITLE);
|
||||
titleText.setText(songName);
|
||||
titleText.setTextColor(fgColor);
|
||||
}
|
||||
|
||||
// Artist name
|
||||
TextView artistText = mMediaNotifView.findViewById(R.id.header_artist);
|
||||
if (artistText != null) {
|
||||
String artistName = mediaMetadata.getString(MediaMetadata.METADATA_KEY_ARTIST);
|
||||
artistText.setText(artistName);
|
||||
artistText.setTextColor(fgColor);
|
||||
}
|
||||
|
||||
// Background color
|
||||
if (mMediaNotifView instanceof MediaHeaderView) {
|
||||
MediaHeaderView head = (MediaHeaderView) mMediaNotifView;
|
||||
head.setBackgroundColor(bgColor);
|
||||
}
|
||||
|
||||
// Control buttons
|
||||
final List<Icon> icons = new ArrayList<>();
|
||||
final List<PendingIntent> intents = new ArrayList<>();
|
||||
Notification.Action[] actions = notif.actions;
|
||||
final int[] actionsToShow = notif.extras.getIntArray(Notification.EXTRA_COMPACT_ACTIONS);
|
||||
|
||||
for (int i = 0; i < ACTION_IDS.length; i++) {
|
||||
if (actionsToShow != null && actions != null && i < actionsToShow.length
|
||||
&& actionsToShow[i] < actions.length) {
|
||||
final int idx = actionsToShow[i];
|
||||
icons.add(actions[idx].getIcon());
|
||||
intents.add(actions[idx].actionIntent);
|
||||
} else {
|
||||
icons.add(null);
|
||||
intents.add(null);
|
||||
}
|
||||
}
|
||||
|
||||
Context packageContext = entry.getSbn().getPackageContext(mContext);
|
||||
for (int i = 0; i < ACTION_IDS.length; i++) {
|
||||
ImageButton button = mMediaNotifView.findViewById(ACTION_IDS[i]);
|
||||
if (button == null) {
|
||||
continue;
|
||||
}
|
||||
Icon icon = icons.get(i);
|
||||
if (icon == null) {
|
||||
button.setVisibility(View.GONE);
|
||||
} else {
|
||||
button.setVisibility(View.VISIBLE);
|
||||
button.setImageDrawable(icon.loadDrawable(packageContext));
|
||||
button.setImageTintList(ColorStateList.valueOf(fgColor));
|
||||
final PendingIntent intent = intents.get(i);
|
||||
if (intent != null) {
|
||||
button.setOnClickListener(v -> {
|
||||
try {
|
||||
intent.send();
|
||||
} catch (PendingIntent.CanceledException e) {
|
||||
Log.d(TAG, "failed to send action intent", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process album art for layout
|
||||
* @param albumArt bitmap to use for album art
|
||||
* @param albumView view to hold the album art
|
||||
*/
|
||||
private void processAlbumArt(Bitmap albumArt, ImageView albumView) {
|
||||
RoundedBitmapDrawable roundedDrawable = null;
|
||||
if (albumArt != null) {
|
||||
Bitmap original = albumArt.copy(Bitmap.Config.ARGB_8888, true);
|
||||
Bitmap scaled = Bitmap.createScaledBitmap(original, mAlbumArtSize, mAlbumArtSize,
|
||||
false);
|
||||
roundedDrawable = RoundedBitmapDrawableFactory.create(mContext.getResources(), scaled);
|
||||
roundedDrawable.setCornerRadius(mAlbumArtRadius);
|
||||
} else {
|
||||
Log.e(TAG, "No album art available");
|
||||
}
|
||||
|
||||
// Now that it's resized, update the UI
|
||||
final RoundedBitmapDrawable result = roundedDrawable;
|
||||
albumView.post(() -> {
|
||||
albumView.setImageDrawable(result);
|
||||
albumView.setVisibility(result == null ? View.GONE : View.VISIBLE);
|
||||
});
|
||||
}
|
||||
|
||||
private void loadDimens() {
|
||||
mAlbumArtRadius = mContext.getResources().getDimension(R.dimen.qs_media_corner_radius);
|
||||
mAlbumArtSize = (int) mContext.getResources().getDimension(
|
||||
R.dimen.qs_media_album_size);
|
||||
}
|
||||
}
|
||||
@@ -46,6 +46,7 @@ import android.widget.ImageView;
|
||||
|
||||
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
|
||||
import com.android.internal.statusbar.NotificationVisibility;
|
||||
import com.android.keyguard.KeyguardMediaPlayer;
|
||||
import com.android.systemui.Dependency;
|
||||
import com.android.systemui.Dumpable;
|
||||
import com.android.systemui.Interpolators;
|
||||
@@ -65,6 +66,7 @@ import com.android.systemui.statusbar.phone.ScrimState;
|
||||
import com.android.systemui.statusbar.phone.StatusBar;
|
||||
import com.android.systemui.statusbar.policy.KeyguardStateController;
|
||||
import com.android.systemui.util.DeviceConfigProxy;
|
||||
import com.android.systemui.util.Utils;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.PrintWriter;
|
||||
@@ -111,6 +113,7 @@ public class NotificationMediaManager implements Dumpable {
|
||||
private ScrimController mScrimController;
|
||||
@Nullable
|
||||
private LockscreenWallpaper mLockscreenWallpaper;
|
||||
private final KeyguardMediaPlayer mMediaPlayer;
|
||||
|
||||
private final Executor mMainExecutor;
|
||||
|
||||
@@ -184,11 +187,13 @@ public class NotificationMediaManager implements Dumpable {
|
||||
NotificationEntryManager notificationEntryManager,
|
||||
MediaArtworkProcessor mediaArtworkProcessor,
|
||||
KeyguardBypassController keyguardBypassController,
|
||||
KeyguardMediaPlayer keyguardMediaPlayer,
|
||||
@Main Executor mainExecutor,
|
||||
DeviceConfigProxy deviceConfig) {
|
||||
mContext = context;
|
||||
mMediaArtworkProcessor = mediaArtworkProcessor;
|
||||
mKeyguardBypassController = keyguardBypassController;
|
||||
mMediaPlayer = keyguardMediaPlayer;
|
||||
mMediaListeners = new ArrayList<>();
|
||||
// TODO: use MediaSessionManager.SessionListener to hook us up to future updates
|
||||
// in session state
|
||||
@@ -468,6 +473,7 @@ public class NotificationMediaManager implements Dumpable {
|
||||
&& mBiometricUnlockController.isWakeAndUnlock();
|
||||
if (mKeyguardStateController.isLaunchTransitionFadingAway() || wakeAndUnlock) {
|
||||
mBackdrop.setVisibility(View.INVISIBLE);
|
||||
mMediaPlayer.clearControls();
|
||||
Trace.endSection();
|
||||
return;
|
||||
}
|
||||
@@ -490,6 +496,14 @@ public class NotificationMediaManager implements Dumpable {
|
||||
}
|
||||
}
|
||||
|
||||
NotificationEntry entry = mEntryManager
|
||||
.getActiveNotificationUnfiltered(mMediaNotificationKey);
|
||||
if (entry != null) {
|
||||
mMediaPlayer.updateControls(entry, getMediaIcon(), mediaMetadata);
|
||||
} else {
|
||||
mMediaPlayer.clearControls();
|
||||
}
|
||||
|
||||
// Process artwork on a background thread and send the resulting bitmap to
|
||||
// finishUpdateMediaMetaData.
|
||||
if (metaDataChanged) {
|
||||
@@ -498,7 +512,7 @@ public class NotificationMediaManager implements Dumpable {
|
||||
}
|
||||
mProcessArtworkTasks.clear();
|
||||
}
|
||||
if (artworkBitmap != null) {
|
||||
if (artworkBitmap != null && !Utils.useQsMediaPlayer(mContext)) {
|
||||
mProcessArtworkTasks.add(new ProcessArtworkTask(this, metaDataChanged,
|
||||
allowEnterAnimation).execute(artworkBitmap));
|
||||
} else {
|
||||
@@ -612,6 +626,7 @@ public class NotificationMediaManager implements Dumpable {
|
||||
// We are unlocking directly - no animation!
|
||||
mBackdrop.setVisibility(View.GONE);
|
||||
mBackdropBack.setImageDrawable(null);
|
||||
mMediaPlayer.clearControls();
|
||||
if (windowController != null) {
|
||||
windowController.setBackdropShowing(false);
|
||||
}
|
||||
@@ -628,6 +643,7 @@ public class NotificationMediaManager implements Dumpable {
|
||||
mBackdrop.setVisibility(View.GONE);
|
||||
mBackdropFront.animate().cancel();
|
||||
mBackdropBack.setImageDrawable(null);
|
||||
mMediaPlayer.clearControls();
|
||||
mMainExecutor.execute(mHideBackdropFront);
|
||||
});
|
||||
if (mKeyguardStateController.isKeyguardFadingAway()) {
|
||||
|
||||
@@ -21,6 +21,7 @@ import android.content.Context;
|
||||
import android.os.Handler;
|
||||
|
||||
import com.android.internal.statusbar.IStatusBarService;
|
||||
import com.android.keyguard.KeyguardMediaPlayer;
|
||||
import com.android.systemui.bubbles.BubbleController;
|
||||
import com.android.systemui.dagger.qualifiers.Main;
|
||||
import com.android.systemui.plugins.statusbar.StatusBarStateController;
|
||||
@@ -93,6 +94,7 @@ public interface StatusBarDependenciesModule {
|
||||
NotificationEntryManager notificationEntryManager,
|
||||
MediaArtworkProcessor mediaArtworkProcessor,
|
||||
KeyguardBypassController keyguardBypassController,
|
||||
KeyguardMediaPlayer keyguardMediaPlayer,
|
||||
@Main Executor mainExecutor,
|
||||
DeviceConfigProxy deviceConfigProxy) {
|
||||
return new NotificationMediaManager(
|
||||
@@ -102,6 +104,7 @@ public interface StatusBarDependenciesModule {
|
||||
notificationEntryManager,
|
||||
mediaArtworkProcessor,
|
||||
keyguardBypassController,
|
||||
keyguardMediaPlayer,
|
||||
mainExecutor,
|
||||
deviceConfigProxy);
|
||||
}
|
||||
|
||||
@@ -152,7 +152,13 @@ public class MediaNotificationProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
private int selectForegroundColor(int backgroundColor, Palette palette) {
|
||||
/**
|
||||
* Select a foreground color depending on whether the background color is dark or light
|
||||
* @param backgroundColor Background color to coordinate with
|
||||
* @param palette Artwork palette, should be obtained from {@link generateArtworkPaletteBuilder}
|
||||
* @return foreground color
|
||||
*/
|
||||
public static int selectForegroundColor(int backgroundColor, Palette palette) {
|
||||
if (ContrastColorUtil.isColorLight(backgroundColor)) {
|
||||
return selectForegroundColorForSwatches(palette.getDarkVibrantSwatch(),
|
||||
palette.getVibrantSwatch(),
|
||||
@@ -170,7 +176,7 @@ public class MediaNotificationProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
private int selectForegroundColorForSwatches(Palette.Swatch moreVibrant,
|
||||
private static int selectForegroundColorForSwatches(Palette.Swatch moreVibrant,
|
||||
Palette.Swatch vibrant, Palette.Swatch moreMutedSwatch, Palette.Swatch mutedSwatch,
|
||||
Palette.Swatch dominantSwatch, int fallbackColor) {
|
||||
Palette.Swatch coloredCandidate = selectVibrantCandidate(moreVibrant, vibrant);
|
||||
@@ -194,7 +200,7 @@ public class MediaNotificationProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
private Palette.Swatch selectMutedCandidate(Palette.Swatch first,
|
||||
private static Palette.Swatch selectMutedCandidate(Palette.Swatch first,
|
||||
Palette.Swatch second) {
|
||||
boolean firstValid = hasEnoughPopulation(first);
|
||||
boolean secondValid = hasEnoughPopulation(second);
|
||||
@@ -215,7 +221,8 @@ public class MediaNotificationProcessor {
|
||||
return null;
|
||||
}
|
||||
|
||||
private Palette.Swatch selectVibrantCandidate(Palette.Swatch first, Palette.Swatch second) {
|
||||
private static Palette.Swatch selectVibrantCandidate(Palette.Swatch first,
|
||||
Palette.Swatch second) {
|
||||
boolean firstValid = hasEnoughPopulation(first);
|
||||
boolean secondValid = hasEnoughPopulation(second);
|
||||
if (firstValid && secondValid) {
|
||||
@@ -235,7 +242,7 @@ public class MediaNotificationProcessor {
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean hasEnoughPopulation(Palette.Swatch swatch) {
|
||||
private static boolean hasEnoughPopulation(Palette.Swatch swatch) {
|
||||
// We want a fraction that is at least 1% of the image
|
||||
return swatch != null
|
||||
&& (swatch.getPopulation() / (float) RESIZE_BITMAP_AREA > MINIMUM_IMAGE_FRACTION);
|
||||
@@ -257,7 +264,7 @@ public class MediaNotificationProcessor {
|
||||
* @param palette Artwork palette, should be obtained from {@link generateArtworkPaletteBuilder}
|
||||
* @return Swatch that should be used as the background of the media notification.
|
||||
*/
|
||||
private static Palette.Swatch findBackgroundSwatch(Palette palette) {
|
||||
public static Palette.Swatch findBackgroundSwatch(Palette palette) {
|
||||
// by default we use the dominant palette
|
||||
Palette.Swatch dominantSwatch = palette.getDominantSwatch();
|
||||
if (dominantSwatch == null) {
|
||||
@@ -301,7 +308,7 @@ public class MediaNotificationProcessor {
|
||||
* @param artwork Media artwork
|
||||
* @return Builder that generates the {@link Palette} for the media artwork.
|
||||
*/
|
||||
private static Palette.Builder generateArtworkPaletteBuilder(Bitmap artwork) {
|
||||
public static Palette.Builder generateArtworkPaletteBuilder(Bitmap artwork) {
|
||||
// for the background we only take the left side of the image to ensure
|
||||
// a smooth transition
|
||||
return Palette.from(artwork)
|
||||
|
||||
@@ -23,9 +23,11 @@ import com.android.internal.annotations.VisibleForTesting
|
||||
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.NOTIFICATIONS_USE_PEOPLE_FILTERING
|
||||
import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING
|
||||
import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_HEADS_UP
|
||||
import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_MEDIA_CONTROLS
|
||||
import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_PEOPLE
|
||||
import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_SILENT
|
||||
import com.android.systemui.util.DeviceConfigProxy
|
||||
import com.android.systemui.util.Utils
|
||||
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -43,9 +45,18 @@ class NotificationSectionsFeatureManager @Inject constructor(
|
||||
return usePeopleFiltering(proxy)
|
||||
}
|
||||
|
||||
fun isMediaControlsEnabled(): Boolean {
|
||||
return Utils.useQsMediaPlayer(context)
|
||||
}
|
||||
|
||||
fun getNotificationBuckets(): IntArray {
|
||||
return when {
|
||||
isFilteringEnabled() ->
|
||||
isFilteringEnabled() && isMediaControlsEnabled() ->
|
||||
intArrayOf(BUCKET_HEADS_UP, BUCKET_MEDIA_CONTROLS, BUCKET_PEOPLE, BUCKET_ALERTING,
|
||||
BUCKET_SILENT)
|
||||
!isFilteringEnabled() && isMediaControlsEnabled() ->
|
||||
intArrayOf(BUCKET_HEADS_UP, BUCKET_MEDIA_CONTROLS, BUCKET_ALERTING, BUCKET_SILENT)
|
||||
isFilteringEnabled() && !isMediaControlsEnabled() ->
|
||||
intArrayOf(BUCKET_HEADS_UP, BUCKET_PEOPLE, BUCKET_ALERTING, BUCKET_SILENT)
|
||||
NotificationUtils.useNewInterruptionModel(context) ->
|
||||
intArrayOf(BUCKET_ALERTING, BUCKET_SILENT)
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.
|
||||
*/
|
||||
|
||||
package com.android.systemui.statusbar.notification.stack;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
|
||||
|
||||
/**
|
||||
* Root view to insert Lock screen media controls into the notification stack.
|
||||
*/
|
||||
public class MediaHeaderView extends ActivatableNotificationView {
|
||||
|
||||
private View mContentView;
|
||||
|
||||
public MediaHeaderView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFinishInflate() {
|
||||
super.onFinishInflate();
|
||||
mContentView = findViewById(R.id.keyguard_media_view);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View getContentView() {
|
||||
return mContentView;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the background color, to be used when album art changes.
|
||||
* @param color background
|
||||
*/
|
||||
public void setBackgroundColor(int color) {
|
||||
setTintColor(color);
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,7 @@ import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.keyguard.KeyguardMediaPlayer;
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.plugins.ActivityStarter;
|
||||
import com.android.systemui.plugins.statusbar.StatusBarStateController;
|
||||
@@ -71,6 +72,7 @@ public class NotificationSectionsManager implements StackScrollAlgorithm.Section
|
||||
private final StatusBarStateController mStatusBarStateController;
|
||||
private final ConfigurationController mConfigurationController;
|
||||
private final PeopleHubViewAdapter mPeopleHubViewAdapter;
|
||||
private final KeyguardMediaPlayer mKeyguardMediaPlayer;
|
||||
private final NotificationSectionsFeatureManager mSectionsFeatureManager;
|
||||
private final int mNumberOfSections;
|
||||
|
||||
@@ -110,17 +112,21 @@ public class NotificationSectionsManager implements StackScrollAlgorithm.Section
|
||||
private boolean mPeopleHubVisible = false;
|
||||
@Nullable private Subscription mPeopleHubSubscription;
|
||||
|
||||
private MediaHeaderView mMediaControlsView;
|
||||
|
||||
@Inject
|
||||
NotificationSectionsManager(
|
||||
ActivityStarter activityStarter,
|
||||
StatusBarStateController statusBarStateController,
|
||||
ConfigurationController configurationController,
|
||||
PeopleHubViewAdapter peopleHubViewAdapter,
|
||||
KeyguardMediaPlayer keyguardMediaPlayer,
|
||||
NotificationSectionsFeatureManager sectionsFeatureManager) {
|
||||
mActivityStarter = activityStarter;
|
||||
mStatusBarStateController = statusBarStateController;
|
||||
mConfigurationController = configurationController;
|
||||
mPeopleHubViewAdapter = peopleHubViewAdapter;
|
||||
mKeyguardMediaPlayer = keyguardMediaPlayer;
|
||||
mSectionsFeatureManager = sectionsFeatureManager;
|
||||
mNumberOfSections = mSectionsFeatureManager.getNumberOfBuckets();
|
||||
}
|
||||
@@ -188,6 +194,13 @@ public class NotificationSectionsManager implements StackScrollAlgorithm.Section
|
||||
}
|
||||
mPeopleHubView = reinflateView(mPeopleHubView, layoutInflater, R.layout.people_strip);
|
||||
mPeopleHubSubscription = mPeopleHubViewAdapter.bindView(mPeopleHubViewBoundary);
|
||||
|
||||
if (mMediaControlsView != null) {
|
||||
mKeyguardMediaPlayer.unbindView();
|
||||
}
|
||||
mMediaControlsView = reinflateView(mMediaControlsView, layoutInflater,
|
||||
R.layout.keyguard_media_header);
|
||||
mKeyguardMediaPlayer.bindView(mMediaControlsView);
|
||||
}
|
||||
|
||||
/** Listener for when the "clear all" button is clicked on the gentle notification header. */
|
||||
@@ -198,6 +211,7 @@ public class NotificationSectionsManager implements StackScrollAlgorithm.Section
|
||||
@Override
|
||||
public boolean beginsSection(@NonNull View view, @Nullable View previous) {
|
||||
return view == mGentleHeader
|
||||
|| view == mMediaControlsView
|
||||
|| view == mPeopleHubView
|
||||
|| view == mAlertingHeader
|
||||
|| !Objects.equals(getBucket(view), getBucket(previous));
|
||||
@@ -211,6 +225,8 @@ public class NotificationSectionsManager implements StackScrollAlgorithm.Section
|
||||
private Integer getBucket(View view) {
|
||||
if (view == mGentleHeader) {
|
||||
return BUCKET_SILENT;
|
||||
} else if (view == mMediaControlsView) {
|
||||
return BUCKET_MEDIA_CONTROLS;
|
||||
} else if (view == mPeopleHubView) {
|
||||
return BUCKET_PEOPLE;
|
||||
} else if (view == mAlertingHeader) {
|
||||
@@ -238,9 +254,15 @@ public class NotificationSectionsManager implements StackScrollAlgorithm.Section
|
||||
|
||||
final boolean showHeaders = mStatusBarStateController.getState() != StatusBarState.KEYGUARD;
|
||||
final boolean usingPeopleFiltering = mSectionsFeatureManager.isFilteringEnabled();
|
||||
final boolean isKeyguard = mStatusBarStateController.getState() == StatusBarState.KEYGUARD;
|
||||
final boolean usingMediaControls = mSectionsFeatureManager.isMediaControlsEnabled();
|
||||
|
||||
boolean peopleNotifsPresent = false;
|
||||
|
||||
int currentMediaControlsIdx = -1;
|
||||
// Currently, just putting media controls in the front and incrementing the position based
|
||||
// on the number of heads-up notifs.
|
||||
int mediaControlsTarget = isKeyguard && usingMediaControls ? 0 : -1;
|
||||
int currentPeopleHeaderIdx = -1;
|
||||
int peopleHeaderTarget = -1;
|
||||
int currentAlertingHeaderIdx = -1;
|
||||
@@ -255,6 +277,10 @@ public class NotificationSectionsManager implements StackScrollAlgorithm.Section
|
||||
View child = mParent.getChildAt(i);
|
||||
|
||||
// Track the existing positions of the headers
|
||||
if (child == mMediaControlsView) {
|
||||
currentMediaControlsIdx = i;
|
||||
continue;
|
||||
}
|
||||
if (child == mPeopleHubView) {
|
||||
currentPeopleHeaderIdx = i;
|
||||
continue;
|
||||
@@ -276,6 +302,9 @@ public class NotificationSectionsManager implements StackScrollAlgorithm.Section
|
||||
// Once we enter a new section, calculate the target position for the header.
|
||||
switch (row.getEntry().getBucket()) {
|
||||
case BUCKET_HEADS_UP:
|
||||
if (mediaControlsTarget != -1) {
|
||||
mediaControlsTarget++;
|
||||
}
|
||||
break;
|
||||
case BUCKET_PEOPLE:
|
||||
peopleNotifsPresent = true;
|
||||
@@ -345,6 +374,8 @@ public class NotificationSectionsManager implements StackScrollAlgorithm.Section
|
||||
alertingHeaderTarget, mAlertingHeader, currentAlertingHeaderIdx);
|
||||
adjustHeaderVisibilityAndPosition(
|
||||
peopleHeaderTarget, mPeopleHubView, currentPeopleHeaderIdx);
|
||||
adjustViewPosition(
|
||||
mediaControlsTarget, mMediaControlsView, currentMediaControlsIdx);
|
||||
|
||||
// Update headers to reflect state of section contents
|
||||
mGentleHeader.setAreThereDismissableGentleNotifs(
|
||||
@@ -378,6 +409,28 @@ public class NotificationSectionsManager implements StackScrollAlgorithm.Section
|
||||
}
|
||||
}
|
||||
|
||||
private void adjustViewPosition(int targetPosition, ExpandableView header,
|
||||
int currentPosition) {
|
||||
if (targetPosition == -1) {
|
||||
if (currentPosition != -1) {
|
||||
mParent.removeView(header);
|
||||
}
|
||||
} else {
|
||||
if (currentPosition == -1) {
|
||||
// If the header is animating away, it will still have a parent, so detach it first
|
||||
// TODO: We should really cancel the active animations here. This will happen
|
||||
// automatically when the view's intro animation starts, but it's a fragile link.
|
||||
if (header.getTransientContainer() != null) {
|
||||
header.getTransientContainer().removeTransientView(header);
|
||||
header.setTransientContainer(null);
|
||||
}
|
||||
mParent.addView(header, targetPosition);
|
||||
} else {
|
||||
mParent.changeViewPosition(header, targetPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the boundaries (as tracked by their first and last views) of the priority sections.
|
||||
*
|
||||
@@ -462,6 +515,11 @@ public class NotificationSectionsManager implements StackScrollAlgorithm.Section
|
||||
return mPeopleHubView;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
ExpandableView getMediaControlsView() {
|
||||
return mMediaControlsView;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void setPeopleHubVisible(boolean visible) {
|
||||
mPeopleHubVisible = visible;
|
||||
@@ -501,13 +559,15 @@ public class NotificationSectionsManager implements StackScrollAlgorithm.Section
|
||||
@Retention(SOURCE)
|
||||
@IntDef(prefix = { "BUCKET_" }, value = {
|
||||
BUCKET_HEADS_UP,
|
||||
BUCKET_MEDIA_CONTROLS,
|
||||
BUCKET_PEOPLE,
|
||||
BUCKET_ALERTING,
|
||||
BUCKET_SILENT
|
||||
})
|
||||
public @interface PriorityBucket {}
|
||||
public static final int BUCKET_HEADS_UP = 0;
|
||||
public static final int BUCKET_PEOPLE = 1;
|
||||
public static final int BUCKET_ALERTING = 2;
|
||||
public static final int BUCKET_SILENT = 3;
|
||||
public static final int BUCKET_MEDIA_CONTROLS = 1;
|
||||
public static final int BUCKET_PEOPLE = 2;
|
||||
public static final int BUCKET_ALERTING = 3;
|
||||
public static final int BUCKET_SILENT = 4;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.
|
||||
*/
|
||||
|
||||
package com.android.keyguard
|
||||
|
||||
import android.graphics.drawable.Icon
|
||||
import android.media.MediaMetadata
|
||||
import android.testing.AndroidTestingRunner
|
||||
import android.testing.TestableLooper
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.test.filters.SmallTest
|
||||
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
|
||||
import com.android.systemui.util.concurrency.FakeExecutor
|
||||
import com.android.systemui.util.time.FakeSystemClock
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito.mock
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.Mockito.`when` as whenever
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner::class)
|
||||
@TestableLooper.RunWithLooper
|
||||
public class KeyguardMediaPlayerTest : SysuiTestCase() {
|
||||
|
||||
private lateinit var keyguardMediaPlayer: KeyguardMediaPlayer
|
||||
private lateinit var fakeExecutor: FakeExecutor
|
||||
private lateinit var mediaMetadata: MediaMetadata.Builder
|
||||
private lateinit var entry: NotificationEntryBuilder
|
||||
@Mock private lateinit var mockView: View
|
||||
private lateinit var textView: TextView
|
||||
@Mock private lateinit var mockIcon: Icon
|
||||
|
||||
@Before
|
||||
public fun setup() {
|
||||
fakeExecutor = FakeExecutor(FakeSystemClock())
|
||||
keyguardMediaPlayer = KeyguardMediaPlayer(context, fakeExecutor)
|
||||
mockView = mock(View::class.java)
|
||||
textView = TextView(context)
|
||||
mockIcon = mock(Icon::class.java)
|
||||
mediaMetadata = MediaMetadata.Builder()
|
||||
entry = NotificationEntryBuilder()
|
||||
|
||||
keyguardMediaPlayer.bindView(mockView)
|
||||
}
|
||||
|
||||
@After
|
||||
public fun tearDown() {
|
||||
keyguardMediaPlayer.unbindView()
|
||||
}
|
||||
|
||||
@Test
|
||||
public fun testBind() {
|
||||
keyguardMediaPlayer.unbindView()
|
||||
keyguardMediaPlayer.bindView(mockView)
|
||||
}
|
||||
|
||||
@Test
|
||||
public fun testUnboundClearControls() {
|
||||
keyguardMediaPlayer.unbindView()
|
||||
keyguardMediaPlayer.clearControls()
|
||||
keyguardMediaPlayer.bindView(mockView)
|
||||
}
|
||||
|
||||
@Test
|
||||
public fun testUpdateControls() {
|
||||
keyguardMediaPlayer.updateControls(entry.build(), mockIcon, mediaMetadata.build())
|
||||
verify(mockView).setVisibility(View.VISIBLE)
|
||||
}
|
||||
|
||||
@Test
|
||||
public fun testClearControls() {
|
||||
keyguardMediaPlayer.clearControls()
|
||||
verify(mockView).setVisibility(View.GONE)
|
||||
}
|
||||
|
||||
@Test
|
||||
public fun testSongName() {
|
||||
whenever<TextView>(mockView.findViewById(R.id.header_title)).thenReturn(textView)
|
||||
val song: String = "Song"
|
||||
mediaMetadata.putText(MediaMetadata.METADATA_KEY_TITLE, song)
|
||||
|
||||
keyguardMediaPlayer.updateControls(entry.build(), mockIcon, mediaMetadata.build())
|
||||
|
||||
assertThat(textView.getText()).isEqualTo(song)
|
||||
}
|
||||
|
||||
@Test
|
||||
public fun testArtistName() {
|
||||
whenever<TextView>(mockView.findViewById(R.id.header_artist)).thenReturn(textView)
|
||||
val artist: String = "Artist"
|
||||
mediaMetadata.putText(MediaMetadata.METADATA_KEY_ARTIST, artist)
|
||||
|
||||
keyguardMediaPlayer.updateControls(entry.build(), mockIcon, mediaMetadata.build())
|
||||
|
||||
assertThat(textView.getText()).isEqualTo(artist)
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,7 @@ import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.NOTIFICATIONS
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.util.DeviceConfigProxyFake
|
||||
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
@@ -38,6 +39,7 @@ import org.junit.runner.RunWith
|
||||
class NotificationSectionsFeatureManagerTest : SysuiTestCase() {
|
||||
var manager: NotificationSectionsFeatureManager? = null
|
||||
val proxyFake = DeviceConfigProxyFake()
|
||||
var originalQsMediaPlayer: Int = 0
|
||||
|
||||
@Before
|
||||
public fun setup() {
|
||||
@@ -45,6 +47,15 @@ class NotificationSectionsFeatureManagerTest : SysuiTestCase() {
|
||||
NOTIFICATION_NEW_INTERRUPTION_MODEL, 1)
|
||||
manager = NotificationSectionsFeatureManager(proxyFake, mContext)
|
||||
manager!!.clearCache()
|
||||
originalQsMediaPlayer = Settings.System.getInt(context.getContentResolver(),
|
||||
"qs_media_player", 1)
|
||||
Settings.System.putInt(context.getContentResolver(), "qs_media_player", 0)
|
||||
}
|
||||
|
||||
@After
|
||||
public fun teardown() {
|
||||
Settings.System.putInt(context.getContentResolver(), "qs_media_player",
|
||||
originalQsMediaPlayer)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -42,6 +42,7 @@ import android.view.ViewGroup;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.keyguard.KeyguardMediaPlayer;
|
||||
import com.android.systemui.ActivityStarterDelegate;
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.plugins.statusbar.StatusBarStateController;
|
||||
@@ -73,6 +74,7 @@ public class NotificationSectionsManagerTest extends SysuiTestCase {
|
||||
@Mock private StatusBarStateController mStatusBarStateController;
|
||||
@Mock private ConfigurationController mConfigurationController;
|
||||
@Mock private PeopleHubViewAdapter mPeopleHubAdapter;
|
||||
@Mock private KeyguardMediaPlayer mKeyguardMediaPlayer;
|
||||
@Mock private NotificationSectionsFeatureManager mSectionsFeatureManager;
|
||||
@Mock private NotificationRowComponent mNotificationRowComponent;
|
||||
@Mock private ActivatableNotificationViewController mActivatableNotificationViewController;
|
||||
@@ -91,6 +93,7 @@ public class NotificationSectionsManagerTest extends SysuiTestCase {
|
||||
mStatusBarStateController,
|
||||
mConfigurationController,
|
||||
mPeopleHubAdapter,
|
||||
mKeyguardMediaPlayer,
|
||||
mSectionsFeatureManager
|
||||
);
|
||||
// Required in order for the header inflation to work properly
|
||||
@@ -333,13 +336,82 @@ public class NotificationSectionsManagerTest extends SysuiTestCase {
|
||||
verify(mNssl).changeViewPosition(mSectionsManager.getPeopleHeaderView(), 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMediaControls_AddWhenEnterKeyguard() {
|
||||
enableMediaControls();
|
||||
|
||||
// GIVEN a stack that doesn't include media controls
|
||||
setStackState(ChildType.ALERTING, ChildType.GENTLE_HEADER, ChildType.GENTLE);
|
||||
|
||||
// WHEN we go back to the keyguard
|
||||
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
|
||||
mSectionsManager.updateSectionBoundaries();
|
||||
|
||||
// Then the media controls are added
|
||||
verify(mNssl).addView(mSectionsManager.getMediaControlsView(), 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMediaControls_AddWhenEnterKeyguardWithHeadsUp() {
|
||||
enableMediaControls();
|
||||
|
||||
// GIVEN a stack that doesn't include media controls but includes HEADS_UP
|
||||
setStackState(ChildType.HEADS_UP, ChildType.ALERTING, ChildType.GENTLE_HEADER,
|
||||
ChildType.GENTLE);
|
||||
|
||||
// WHEN we go back to the keyguard
|
||||
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
|
||||
mSectionsManager.updateSectionBoundaries();
|
||||
|
||||
// Then the media controls are added after HEADS_UP
|
||||
verify(mNssl).addView(mSectionsManager.getMediaControlsView(), 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMediaControls_RemoveWhenExitKeyguard() {
|
||||
enableMediaControls();
|
||||
|
||||
// GIVEN a stack with media controls
|
||||
setStackState(ChildType.MEDIA_CONTROLS, ChildType.ALERTING, ChildType.GENTLE_HEADER,
|
||||
ChildType.GENTLE);
|
||||
|
||||
// WHEN we leave the keyguard
|
||||
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
|
||||
mSectionsManager.updateSectionBoundaries();
|
||||
|
||||
// Then the media controls is removed
|
||||
verify(mNssl).removeView(mSectionsManager.getMediaControlsView());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMediaControls_RemoveWhenPullDownShade() {
|
||||
enableMediaControls();
|
||||
|
||||
// GIVEN a stack with media controls
|
||||
setStackState(ChildType.MEDIA_CONTROLS, ChildType.ALERTING, ChildType.GENTLE_HEADER,
|
||||
ChildType.GENTLE);
|
||||
|
||||
// WHEN we pull down the shade on the keyguard
|
||||
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE_LOCKED);
|
||||
mSectionsManager.updateSectionBoundaries();
|
||||
|
||||
// Then the media controls is removed
|
||||
verify(mNssl).removeView(mSectionsManager.getMediaControlsView());
|
||||
}
|
||||
|
||||
private void enablePeopleFiltering() {
|
||||
when(mSectionsFeatureManager.isFilteringEnabled()).thenReturn(true);
|
||||
when(mSectionsFeatureManager.getNumberOfBuckets()).thenReturn(4);
|
||||
}
|
||||
|
||||
private void enableMediaControls() {
|
||||
when(mSectionsFeatureManager.isMediaControlsEnabled()).thenReturn(true);
|
||||
when(mSectionsFeatureManager.getNumberOfBuckets()).thenReturn(4);
|
||||
}
|
||||
|
||||
private enum ChildType {
|
||||
PEOPLE_HEADER, ALERTING_HEADER, GENTLE_HEADER, HEADS_UP, PERSON, ALERTING, GENTLE, OTHER
|
||||
MEDIA_CONTROLS, PEOPLE_HEADER, ALERTING_HEADER, GENTLE_HEADER, HEADS_UP, PERSON, ALERTING,
|
||||
GENTLE, OTHER
|
||||
}
|
||||
|
||||
private void setStackState(ChildType... children) {
|
||||
@@ -347,6 +419,9 @@ public class NotificationSectionsManagerTest extends SysuiTestCase {
|
||||
for (int i = 0; i < children.length; i++) {
|
||||
View child;
|
||||
switch (children[i]) {
|
||||
case MEDIA_CONTROLS:
|
||||
child = mSectionsManager.getMediaControlsView();
|
||||
break;
|
||||
case PEOPLE_HEADER:
|
||||
child = mSectionsManager.getPeopleHeaderView();
|
||||
break;
|
||||
|
||||
Reference in New Issue
Block a user