Merge "Reset when playback state is NONE, and refactoring" into rvc-dev am: b29c517fb1
Change-Id: Ia3f874295b41bb5649e5b53ec140b642e4808c24
This commit is contained in:
@@ -32,7 +32,6 @@
|
|||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:id="@+id/header"
|
|
||||||
android:layout_marginBottom="16dp"
|
android:layout_marginBottom="16dp"
|
||||||
>
|
>
|
||||||
|
|
||||||
@@ -73,7 +72,7 @@
|
|||||||
|
|
||||||
<!-- Song name -->
|
<!-- Song name -->
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/header_text"
|
android:id="@+id/header_title"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:singleLine="true"
|
android:singleLine="true"
|
||||||
|
|||||||
@@ -0,0 +1,425 @@
|
|||||||
|
/*
|
||||||
|
* 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.media;
|
||||||
|
|
||||||
|
import android.annotation.LayoutRes;
|
||||||
|
import android.app.PendingIntent;
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.content.pm.ResolveInfo;
|
||||||
|
import android.content.res.ColorStateList;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.graphics.drawable.GradientDrawable;
|
||||||
|
import android.graphics.drawable.Icon;
|
||||||
|
import android.graphics.drawable.RippleDrawable;
|
||||||
|
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.util.Log;
|
||||||
|
import android.view.KeyEvent;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ImageButton;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.core.graphics.drawable.RoundedBitmapDrawable;
|
||||||
|
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
|
||||||
|
|
||||||
|
import com.android.settingslib.media.MediaDevice;
|
||||||
|
import com.android.settingslib.media.MediaOutputSliceConstants;
|
||||||
|
import com.android.settingslib.widget.AdaptiveIcon;
|
||||||
|
import com.android.systemui.Dependency;
|
||||||
|
import com.android.systemui.R;
|
||||||
|
import com.android.systemui.plugins.ActivityStarter;
|
||||||
|
import com.android.systemui.statusbar.NotificationMediaManager;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base media control panel for System UI
|
||||||
|
*/
|
||||||
|
public class MediaControlPanel implements NotificationMediaManager.MediaListener {
|
||||||
|
private static final String TAG = "MediaControlPanel";
|
||||||
|
private final NotificationMediaManager mMediaManager;
|
||||||
|
private final Executor mBackgroundExecutor;
|
||||||
|
|
||||||
|
private Context mContext;
|
||||||
|
protected LinearLayout mMediaNotifView;
|
||||||
|
private View mSeamless;
|
||||||
|
private MediaSession.Token mToken;
|
||||||
|
private MediaController mController;
|
||||||
|
private int mForegroundColor;
|
||||||
|
private int mBackgroundColor;
|
||||||
|
protected ComponentName mRecvComponent;
|
||||||
|
|
||||||
|
private final int[] mActionIds;
|
||||||
|
|
||||||
|
// Button IDs used in notifications
|
||||||
|
protected static final int[] NOTIF_ACTION_IDS = {
|
||||||
|
com.android.internal.R.id.action0,
|
||||||
|
com.android.internal.R.id.action1,
|
||||||
|
com.android.internal.R.id.action2,
|
||||||
|
com.android.internal.R.id.action3,
|
||||||
|
com.android.internal.R.id.action4
|
||||||
|
};
|
||||||
|
|
||||||
|
private MediaController.Callback mSessionCallback = new MediaController.Callback() {
|
||||||
|
@Override
|
||||||
|
public void onSessionDestroyed() {
|
||||||
|
Log.d(TAG, "session destroyed");
|
||||||
|
mController.unregisterCallback(mSessionCallback);
|
||||||
|
clearControls();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize a new control panel
|
||||||
|
* @param context
|
||||||
|
* @param parent
|
||||||
|
* @param manager
|
||||||
|
* @param layoutId layout resource to use for this control panel
|
||||||
|
* @param actionIds resource IDs for action buttons in the layout
|
||||||
|
* @param backgroundExecutor background executor, used for processing artwork
|
||||||
|
*/
|
||||||
|
public MediaControlPanel(Context context, ViewGroup parent, NotificationMediaManager manager,
|
||||||
|
@LayoutRes int layoutId, int[] actionIds, Executor backgroundExecutor) {
|
||||||
|
mContext = context;
|
||||||
|
LayoutInflater inflater = LayoutInflater.from(mContext);
|
||||||
|
mMediaNotifView = (LinearLayout) inflater.inflate(layoutId, parent, false);
|
||||||
|
mMediaManager = manager;
|
||||||
|
mActionIds = actionIds;
|
||||||
|
mBackgroundExecutor = backgroundExecutor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the view used to display media controls
|
||||||
|
* @return the view
|
||||||
|
*/
|
||||||
|
public View getView() {
|
||||||
|
return mMediaNotifView;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the context
|
||||||
|
* @return context
|
||||||
|
*/
|
||||||
|
public Context getContext() {
|
||||||
|
return mContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the media panel view for the given media session
|
||||||
|
* @param token
|
||||||
|
* @param icon
|
||||||
|
* @param iconColor
|
||||||
|
* @param bgColor
|
||||||
|
* @param contentIntent
|
||||||
|
* @param appNameString
|
||||||
|
* @param device
|
||||||
|
*/
|
||||||
|
public void setMediaSession(MediaSession.Token token, Icon icon, int iconColor,
|
||||||
|
int bgColor, PendingIntent contentIntent, String appNameString, MediaDevice device) {
|
||||||
|
mToken = token;
|
||||||
|
mForegroundColor = iconColor;
|
||||||
|
mBackgroundColor = bgColor;
|
||||||
|
mController = new MediaController(mContext, mToken);
|
||||||
|
|
||||||
|
MediaMetadata mediaMetadata = mController.getMetadata();
|
||||||
|
|
||||||
|
// Try to find a receiver for the media button that matches this app
|
||||||
|
PackageManager pm = mContext.getPackageManager();
|
||||||
|
Intent it = new Intent(Intent.ACTION_MEDIA_BUTTON);
|
||||||
|
List<ResolveInfo> info = pm.queryBroadcastReceiversAsUser(it, 0, mContext.getUser());
|
||||||
|
if (info != null) {
|
||||||
|
for (ResolveInfo inf : info) {
|
||||||
|
if (inf.activityInfo.packageName.equals(mController.getPackageName())) {
|
||||||
|
mRecvComponent = inf.getComponentInfo().getComponentName();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mController.registerCallback(mSessionCallback);
|
||||||
|
|
||||||
|
if (mediaMetadata == null) {
|
||||||
|
Log.e(TAG, "Media metadata was null");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageView albumView = mMediaNotifView.findViewById(R.id.album_art);
|
||||||
|
if (albumView != null) {
|
||||||
|
// Resize art in a background thread
|
||||||
|
mBackgroundExecutor.execute(() -> processAlbumArt(mediaMetadata, albumView));
|
||||||
|
}
|
||||||
|
mMediaNotifView.setBackgroundTintList(ColorStateList.valueOf(mBackgroundColor));
|
||||||
|
|
||||||
|
// Click action
|
||||||
|
mMediaNotifView.setOnClickListener(v -> {
|
||||||
|
try {
|
||||||
|
contentIntent.send();
|
||||||
|
// Also close shade
|
||||||
|
mContext.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
|
||||||
|
} catch (PendingIntent.CanceledException e) {
|
||||||
|
Log.e(TAG, "Pending intent was canceled", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// App icon
|
||||||
|
ImageView appIcon = mMediaNotifView.findViewById(R.id.icon);
|
||||||
|
Drawable iconDrawable = icon.loadDrawable(mContext);
|
||||||
|
iconDrawable.setTint(mForegroundColor);
|
||||||
|
appIcon.setImageDrawable(iconDrawable);
|
||||||
|
|
||||||
|
// Song name
|
||||||
|
TextView titleText = mMediaNotifView.findViewById(R.id.header_title);
|
||||||
|
String songName = mediaMetadata.getString(MediaMetadata.METADATA_KEY_TITLE);
|
||||||
|
titleText.setText(songName);
|
||||||
|
titleText.setTextColor(mForegroundColor);
|
||||||
|
|
||||||
|
// Not in mini player:
|
||||||
|
// App title
|
||||||
|
TextView appName = mMediaNotifView.findViewById(R.id.app_name);
|
||||||
|
if (appName != null) {
|
||||||
|
appName.setText(appNameString);
|
||||||
|
appName.setTextColor(mForegroundColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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(mForegroundColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transfer chip
|
||||||
|
mSeamless = mMediaNotifView.findViewById(R.id.media_seamless);
|
||||||
|
if (mSeamless != null) {
|
||||||
|
mSeamless.setVisibility(View.VISIBLE);
|
||||||
|
updateDevice(device);
|
||||||
|
ActivityStarter mActivityStarter = Dependency.get(ActivityStarter.class);
|
||||||
|
mSeamless.setOnClickListener(v -> {
|
||||||
|
final Intent intent = new Intent()
|
||||||
|
.setAction(MediaOutputSliceConstants.ACTION_MEDIA_OUTPUT)
|
||||||
|
.putExtra(MediaOutputSliceConstants.EXTRA_PACKAGE_NAME,
|
||||||
|
mController.getPackageName())
|
||||||
|
.putExtra(MediaOutputSliceConstants.KEY_MEDIA_SESSION_TOKEN, mToken);
|
||||||
|
mActivityStarter.startActivity(intent, false, true /* dismissShade */,
|
||||||
|
Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure is only added once
|
||||||
|
mMediaManager.removeCallback(this);
|
||||||
|
mMediaManager.addCallback(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the token for the current media session
|
||||||
|
* @return the token
|
||||||
|
*/
|
||||||
|
public MediaSession.Token getMediaSessionToken() {
|
||||||
|
return mToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current media controller
|
||||||
|
* @return the controller
|
||||||
|
*/
|
||||||
|
public MediaController getController() {
|
||||||
|
return mController;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the name of the package associated with the current media controller
|
||||||
|
* @return the package name
|
||||||
|
*/
|
||||||
|
public String getMediaPlayerPackage() {
|
||||||
|
return mController.getPackageName();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether this player has an attached media session.
|
||||||
|
* @return whether there is a controller with a current media session.
|
||||||
|
*/
|
||||||
|
public boolean hasMediaSession() {
|
||||||
|
return mController != null && mController.getPlaybackState() != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether the media controlled by this player is currently playing
|
||||||
|
* @return whether it is playing, or false if no controller information
|
||||||
|
*/
|
||||||
|
public boolean isPlaying() {
|
||||||
|
return isPlaying(mController);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether the given controller is currently playing
|
||||||
|
* @param controller media controller to check
|
||||||
|
* @return whether it is playing, or false if no controller information
|
||||||
|
*/
|
||||||
|
protected boolean isPlaying(MediaController controller) {
|
||||||
|
if (controller == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
PlaybackState state = controller.getPlaybackState();
|
||||||
|
if (state == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (state.getState() == PlaybackState.STATE_PLAYING);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process album art for layout
|
||||||
|
* @param metadata media metadata
|
||||||
|
* @param albumView view to hold the album art
|
||||||
|
*/
|
||||||
|
private void processAlbumArt(MediaMetadata metadata, ImageView albumView) {
|
||||||
|
Bitmap albumArt = metadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
|
||||||
|
float radius = mContext.getResources().getDimension(R.dimen.qs_media_corner_radius);
|
||||||
|
RoundedBitmapDrawable roundedDrawable = null;
|
||||||
|
if (albumArt != null) {
|
||||||
|
Bitmap original = albumArt.copy(Bitmap.Config.ARGB_8888, true);
|
||||||
|
int albumSize = (int) mContext.getResources().getDimension(
|
||||||
|
R.dimen.qs_media_album_size);
|
||||||
|
Bitmap scaled = Bitmap.createScaledBitmap(original, albumSize, albumSize, false);
|
||||||
|
roundedDrawable = RoundedBitmapDrawableFactory.create(mContext.getResources(), scaled);
|
||||||
|
roundedDrawable.setCornerRadius(radius);
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "No album art available");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now that it's resized, update the UI
|
||||||
|
final RoundedBitmapDrawable result = roundedDrawable;
|
||||||
|
albumView.getHandler().post(() -> {
|
||||||
|
if (result != null) {
|
||||||
|
albumView.setImageDrawable(result);
|
||||||
|
albumView.setVisibility(View.VISIBLE);
|
||||||
|
} else {
|
||||||
|
albumView.setImageDrawable(null);
|
||||||
|
albumView.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the current device information
|
||||||
|
* @param device device information to display
|
||||||
|
*/
|
||||||
|
public void updateDevice(MediaDevice device) {
|
||||||
|
if (mSeamless == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Handler handler = mSeamless.getHandler();
|
||||||
|
handler.post(() -> {
|
||||||
|
updateChipInternal(device);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateChipInternal(MediaDevice device) {
|
||||||
|
ColorStateList fgTintList = ColorStateList.valueOf(mForegroundColor);
|
||||||
|
|
||||||
|
// Update the outline color
|
||||||
|
LinearLayout viewLayout = (LinearLayout) mSeamless;
|
||||||
|
RippleDrawable bkgDrawable = (RippleDrawable) viewLayout.getBackground();
|
||||||
|
GradientDrawable rect = (GradientDrawable) bkgDrawable.getDrawable(0);
|
||||||
|
rect.setStroke(2, mForegroundColor);
|
||||||
|
rect.setColor(mBackgroundColor);
|
||||||
|
|
||||||
|
ImageView iconView = mSeamless.findViewById(R.id.media_seamless_image);
|
||||||
|
TextView deviceName = mSeamless.findViewById(R.id.media_seamless_text);
|
||||||
|
deviceName.setTextColor(fgTintList);
|
||||||
|
|
||||||
|
if (device != null) {
|
||||||
|
Drawable icon = device.getIcon();
|
||||||
|
iconView.setVisibility(View.VISIBLE);
|
||||||
|
iconView.setImageTintList(fgTintList);
|
||||||
|
|
||||||
|
if (icon instanceof AdaptiveIcon) {
|
||||||
|
AdaptiveIcon aIcon = (AdaptiveIcon) icon;
|
||||||
|
aIcon.setBackgroundColor(mBackgroundColor);
|
||||||
|
iconView.setImageDrawable(aIcon);
|
||||||
|
} else {
|
||||||
|
iconView.setImageDrawable(icon);
|
||||||
|
}
|
||||||
|
deviceName.setText(device.getName());
|
||||||
|
} else {
|
||||||
|
// Reset to default
|
||||||
|
iconView.setVisibility(View.GONE);
|
||||||
|
deviceName.setText(com.android.internal.R.string.ext_media_seamless_action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Put controls into a resumption state
|
||||||
|
*/
|
||||||
|
public void clearControls() {
|
||||||
|
// Hide all the old buttons
|
||||||
|
for (int i = 0; i < mActionIds.length; i++) {
|
||||||
|
ImageButton thisBtn = mMediaNotifView.findViewById(mActionIds[i]);
|
||||||
|
if (thisBtn != null) {
|
||||||
|
thisBtn.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a restart button
|
||||||
|
ImageButton btn = mMediaNotifView.findViewById(mActionIds[0]);
|
||||||
|
btn.setOnClickListener(v -> {
|
||||||
|
Log.d(TAG, "Attempting to restart session");
|
||||||
|
// Send a media button event to previously found receiver
|
||||||
|
if (mRecvComponent != null) {
|
||||||
|
Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON);
|
||||||
|
intent.setComponent(mRecvComponent);
|
||||||
|
int keyCode = KeyEvent.KEYCODE_MEDIA_PLAY;
|
||||||
|
intent.putExtra(
|
||||||
|
Intent.EXTRA_KEY_EVENT,
|
||||||
|
new KeyEvent(KeyEvent.ACTION_DOWN, keyCode));
|
||||||
|
mContext.sendBroadcast(intent);
|
||||||
|
} else {
|
||||||
|
Log.d(TAG, "No receiver to restart");
|
||||||
|
// If we don't have a receiver, try relaunching the activity instead
|
||||||
|
try {
|
||||||
|
mController.getSessionActivity().send();
|
||||||
|
} catch (PendingIntent.CanceledException e) {
|
||||||
|
Log.e(TAG, "Pending intent was canceled", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
btn.setImageDrawable(mContext.getResources().getDrawable(R.drawable.lb_ic_play));
|
||||||
|
btn.setImageTintList(ColorStateList.valueOf(mForegroundColor));
|
||||||
|
btn.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMetadataOrStateChanged(MediaMetadata metadata, int state) {
|
||||||
|
if (state == PlaybackState.STATE_NONE) {
|
||||||
|
clearControls();
|
||||||
|
mMediaManager.removeCallback(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,158 +17,53 @@
|
|||||||
package com.android.systemui.qs;
|
package com.android.systemui.qs;
|
||||||
|
|
||||||
import android.app.Notification;
|
import android.app.Notification;
|
||||||
import android.app.PendingIntent;
|
|
||||||
import android.content.ComponentName;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.content.pm.ResolveInfo;
|
|
||||||
import android.content.res.ColorStateList;
|
|
||||||
import android.graphics.Bitmap;
|
|
||||||
import android.graphics.Canvas;
|
|
||||||
import android.graphics.Matrix;
|
|
||||||
import android.graphics.Paint;
|
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.graphics.drawable.GradientDrawable;
|
|
||||||
import android.graphics.drawable.Icon;
|
import android.graphics.drawable.Icon;
|
||||||
import android.graphics.drawable.RippleDrawable;
|
|
||||||
import android.media.MediaMetadata;
|
|
||||||
import android.media.session.MediaController;
|
|
||||||
import android.media.session.MediaSession;
|
import android.media.session.MediaSession;
|
||||||
import android.media.session.PlaybackState;
|
|
||||||
import android.os.Handler;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.KeyEvent;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.ImageButton;
|
import android.widget.ImageButton;
|
||||||
import android.widget.ImageView;
|
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import androidx.core.graphics.drawable.RoundedBitmapDrawable;
|
|
||||||
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
|
|
||||||
|
|
||||||
import com.android.settingslib.media.MediaDevice;
|
import com.android.settingslib.media.MediaDevice;
|
||||||
import com.android.settingslib.media.MediaOutputSliceConstants;
|
|
||||||
import com.android.settingslib.widget.AdaptiveIcon;
|
|
||||||
import com.android.systemui.Dependency;
|
|
||||||
import com.android.systemui.R;
|
import com.android.systemui.R;
|
||||||
import com.android.systemui.plugins.ActivityStarter;
|
import com.android.systemui.media.MediaControlPanel;
|
||||||
|
import com.android.systemui.statusbar.NotificationMediaManager;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Single media player for carousel in QSPanel
|
* Single media player for carousel in QSPanel
|
||||||
*/
|
*/
|
||||||
public class QSMediaPlayer {
|
public class QSMediaPlayer extends MediaControlPanel {
|
||||||
|
|
||||||
private static final String TAG = "QSMediaPlayer";
|
private static final String TAG = "QSMediaPlayer";
|
||||||
|
|
||||||
private Context mContext;
|
// Button IDs for QS controls
|
||||||
private LinearLayout mMediaNotifView;
|
static final int[] QS_ACTION_IDS = {
|
||||||
private View mSeamless;
|
R.id.action0,
|
||||||
private MediaSession.Token mToken;
|
R.id.action1,
|
||||||
private MediaController mController;
|
R.id.action2,
|
||||||
private int mForegroundColor;
|
R.id.action3,
|
||||||
private int mBackgroundColor;
|
R.id.action4
|
||||||
private ComponentName mRecvComponent;
|
|
||||||
private QSPanel mParent;
|
|
||||||
|
|
||||||
private MediaController.Callback mSessionCallback = new MediaController.Callback() {
|
|
||||||
@Override
|
|
||||||
public void onSessionDestroyed() {
|
|
||||||
Log.d(TAG, "session destroyed");
|
|
||||||
mController.unregisterCallback(mSessionCallback);
|
|
||||||
|
|
||||||
// Hide all the old buttons
|
|
||||||
final int[] actionIds = {
|
|
||||||
R.id.action0,
|
|
||||||
R.id.action1,
|
|
||||||
R.id.action2,
|
|
||||||
R.id.action3,
|
|
||||||
R.id.action4
|
|
||||||
};
|
|
||||||
for (int i = 0; i < actionIds.length; i++) {
|
|
||||||
ImageButton thisBtn = mMediaNotifView.findViewById(actionIds[i]);
|
|
||||||
if (thisBtn != null) {
|
|
||||||
thisBtn.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add a restart button
|
|
||||||
ImageButton btn = mMediaNotifView.findViewById(actionIds[0]);
|
|
||||||
btn.setOnClickListener(v -> {
|
|
||||||
Log.d(TAG, "Attempting to restart session");
|
|
||||||
// Send a media button event to previously found receiver
|
|
||||||
if (mRecvComponent != null) {
|
|
||||||
Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON);
|
|
||||||
intent.setComponent(mRecvComponent);
|
|
||||||
int keyCode = KeyEvent.KEYCODE_MEDIA_PLAY;
|
|
||||||
intent.putExtra(
|
|
||||||
Intent.EXTRA_KEY_EVENT,
|
|
||||||
new KeyEvent(KeyEvent.ACTION_DOWN, keyCode));
|
|
||||||
mContext.sendBroadcast(intent);
|
|
||||||
} else {
|
|
||||||
Log.d(TAG, "No receiver to restart");
|
|
||||||
// If we don't have a receiver, try relaunching the activity instead
|
|
||||||
try {
|
|
||||||
mController.getSessionActivity().send();
|
|
||||||
} catch (PendingIntent.CanceledException e) {
|
|
||||||
Log.e(TAG, "Pending intent was canceled");
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
btn.setImageDrawable(mContext.getResources().getDrawable(R.drawable.lb_ic_play));
|
|
||||||
btn.setImageTintList(ColorStateList.valueOf(mForegroundColor));
|
|
||||||
btn.setVisibility(View.VISIBLE);
|
|
||||||
|
|
||||||
// Add long-click option to remove the player
|
|
||||||
ViewGroup mMediaCarousel = (ViewGroup) mMediaNotifView.getParent();
|
|
||||||
mMediaNotifView.setOnLongClickListener(v -> {
|
|
||||||
// Replace player view with delete/cancel view
|
|
||||||
v.setVisibility(View.GONE);
|
|
||||||
|
|
||||||
View options = LayoutInflater.from(mContext).inflate(
|
|
||||||
R.layout.qs_media_panel_options, null, false);
|
|
||||||
ImageButton btnDelete = options.findViewById(R.id.remove);
|
|
||||||
btnDelete.setOnClickListener(b -> {
|
|
||||||
mMediaCarousel.removeView(options);
|
|
||||||
mParent.removeMediaPlayer(QSMediaPlayer.this);
|
|
||||||
});
|
|
||||||
ImageButton btnCancel = options.findViewById(R.id.cancel);
|
|
||||||
btnCancel.setOnClickListener(b -> {
|
|
||||||
mMediaCarousel.removeView(options);
|
|
||||||
v.setVisibility(View.VISIBLE);
|
|
||||||
});
|
|
||||||
|
|
||||||
int pos = mMediaCarousel.indexOfChild(v);
|
|
||||||
mMediaCarousel.addView(options, pos, v.getLayoutParams());
|
|
||||||
return true; // consumed click
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* Initialize quick shade version of player
|
||||||
* @param context
|
* @param context
|
||||||
* @param parent
|
* @param parent
|
||||||
|
* @param manager
|
||||||
|
* @param backgroundExecutor
|
||||||
*/
|
*/
|
||||||
public QSMediaPlayer(Context context, ViewGroup parent) {
|
public QSMediaPlayer(Context context, ViewGroup parent, NotificationMediaManager manager,
|
||||||
mContext = context;
|
Executor backgroundExecutor) {
|
||||||
LayoutInflater inflater = LayoutInflater.from(mContext);
|
super(context, parent, manager, R.layout.qs_media_panel, QS_ACTION_IDS, backgroundExecutor);
|
||||||
mMediaNotifView = (LinearLayout) inflater.inflate(R.layout.qs_media_panel, parent, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public View getView() {
|
|
||||||
return mMediaNotifView;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create or update the player view for the given media session
|
* Update media panel view for the given media session
|
||||||
* @param parent the parent QSPanel
|
|
||||||
* @param token token for this media session
|
* @param token token for this media session
|
||||||
* @param icon app notification icon
|
* @param icon app notification icon
|
||||||
* @param iconColor foreground color (for text, icons)
|
* @param iconColor foreground color (for text, icons)
|
||||||
@@ -177,114 +72,20 @@ public class QSMediaPlayer {
|
|||||||
* @param notif reference to original notification
|
* @param notif reference to original notification
|
||||||
* @param device current playback device
|
* @param device current playback device
|
||||||
*/
|
*/
|
||||||
public void setMediaSession(QSPanel parent, MediaSession.Token token, Icon icon, int iconColor,
|
public void setMediaSession(MediaSession.Token token, Icon icon, int iconColor,
|
||||||
int bgColor, View actionsContainer, Notification notif, MediaDevice device) {
|
int bgColor, View actionsContainer, Notification notif, MediaDevice device) {
|
||||||
mParent = parent;
|
|
||||||
mToken = token;
|
|
||||||
mForegroundColor = iconColor;
|
|
||||||
mBackgroundColor = bgColor;
|
|
||||||
mController = new MediaController(mContext, token);
|
|
||||||
|
|
||||||
// Try to find a receiver for the media button that matches this app
|
String appName = Notification.Builder.recoverBuilder(getContext(), notif)
|
||||||
PackageManager pm = mContext.getPackageManager();
|
.loadHeaderAppName();
|
||||||
Intent it = new Intent(Intent.ACTION_MEDIA_BUTTON);
|
super.setMediaSession(token, icon, iconColor, bgColor, notif.contentIntent,
|
||||||
List<ResolveInfo> info = pm.queryBroadcastReceiversAsUser(it, 0, mContext.getUser());
|
appName, device);
|
||||||
if (info != null) {
|
|
||||||
for (ResolveInfo inf : info) {
|
|
||||||
if (inf.activityInfo.packageName.equals(mController.getPackageName())) {
|
|
||||||
mRecvComponent = inf.getComponentInfo().getComponentName();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// reset in case we had previously restarted the stream
|
|
||||||
mMediaNotifView.setOnLongClickListener(null);
|
|
||||||
mController.registerCallback(mSessionCallback);
|
|
||||||
MediaMetadata mMediaMetadata = mController.getMetadata();
|
|
||||||
if (mMediaMetadata == null) {
|
|
||||||
Log.e(TAG, "Media metadata was null");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Notification.Builder builder = Notification.Builder.recoverBuilder(mContext, notif);
|
|
||||||
|
|
||||||
// Album art
|
|
||||||
addAlbumArt(mMediaMetadata, bgColor);
|
|
||||||
|
|
||||||
LinearLayout headerView = mMediaNotifView.findViewById(R.id.header);
|
|
||||||
|
|
||||||
// App icon
|
|
||||||
ImageView appIcon = headerView.findViewById(R.id.icon);
|
|
||||||
Drawable iconDrawable = icon.loadDrawable(mContext);
|
|
||||||
iconDrawable.setTint(iconColor);
|
|
||||||
appIcon.setImageDrawable(iconDrawable);
|
|
||||||
|
|
||||||
// App title
|
|
||||||
TextView appName = headerView.findViewById(R.id.app_name);
|
|
||||||
String appNameString = builder.loadHeaderAppName();
|
|
||||||
appName.setText(appNameString);
|
|
||||||
appName.setTextColor(iconColor);
|
|
||||||
|
|
||||||
// Action
|
|
||||||
mMediaNotifView.setOnClickListener(v -> {
|
|
||||||
try {
|
|
||||||
notif.contentIntent.send();
|
|
||||||
// Also close shade
|
|
||||||
mContext.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
|
|
||||||
} catch (PendingIntent.CanceledException e) {
|
|
||||||
Log.e(TAG, "Pending intent was canceled");
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Transfer chip
|
|
||||||
mSeamless = headerView.findViewById(R.id.media_seamless);
|
|
||||||
mSeamless.setVisibility(View.VISIBLE);
|
|
||||||
updateChip(device);
|
|
||||||
ActivityStarter mActivityStarter = Dependency.get(ActivityStarter.class);
|
|
||||||
mSeamless.setOnClickListener(v -> {
|
|
||||||
final Intent intent = new Intent()
|
|
||||||
.setAction(MediaOutputSliceConstants.ACTION_MEDIA_OUTPUT)
|
|
||||||
.putExtra(MediaOutputSliceConstants.EXTRA_PACKAGE_NAME,
|
|
||||||
mController.getPackageName())
|
|
||||||
.putExtra(MediaOutputSliceConstants.KEY_MEDIA_SESSION_TOKEN, token);
|
|
||||||
mActivityStarter.startActivity(intent, false, true /* dismissShade */,
|
|
||||||
Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Artist name
|
|
||||||
TextView artistText = headerView.findViewById(R.id.header_artist);
|
|
||||||
String artistName = mMediaMetadata.getString(MediaMetadata.METADATA_KEY_ARTIST);
|
|
||||||
artistText.setText(artistName);
|
|
||||||
artistText.setTextColor(iconColor);
|
|
||||||
|
|
||||||
// Song name
|
|
||||||
TextView titleText = headerView.findViewById(R.id.header_text);
|
|
||||||
String songName = mMediaMetadata.getString(MediaMetadata.METADATA_KEY_TITLE);
|
|
||||||
titleText.setText(songName);
|
|
||||||
titleText.setTextColor(iconColor);
|
|
||||||
|
|
||||||
// Media controls
|
// Media controls
|
||||||
LinearLayout parentActionsLayout = (LinearLayout) actionsContainer;
|
LinearLayout parentActionsLayout = (LinearLayout) actionsContainer;
|
||||||
final int[] actionIds = {
|
|
||||||
R.id.action0,
|
|
||||||
R.id.action1,
|
|
||||||
R.id.action2,
|
|
||||||
R.id.action3,
|
|
||||||
R.id.action4
|
|
||||||
};
|
|
||||||
final int[] notifActionIds = {
|
|
||||||
com.android.internal.R.id.action0,
|
|
||||||
com.android.internal.R.id.action1,
|
|
||||||
com.android.internal.R.id.action2,
|
|
||||||
com.android.internal.R.id.action3,
|
|
||||||
com.android.internal.R.id.action4
|
|
||||||
};
|
|
||||||
|
|
||||||
int i = 0;
|
int i = 0;
|
||||||
for (; i < parentActionsLayout.getChildCount() && i < actionIds.length; i++) {
|
for (; i < parentActionsLayout.getChildCount() && i < QS_ACTION_IDS.length; i++) {
|
||||||
ImageButton thisBtn = mMediaNotifView.findViewById(actionIds[i]);
|
ImageButton thisBtn = mMediaNotifView.findViewById(QS_ACTION_IDS[i]);
|
||||||
ImageButton thatBtn = parentActionsLayout.findViewById(notifActionIds[i]);
|
ImageButton thatBtn = parentActionsLayout.findViewById(NOTIF_ACTION_IDS[i]);
|
||||||
if (thatBtn == null || thatBtn.getDrawable() == null
|
if (thatBtn == null || thatBtn.getDrawable() == null
|
||||||
|| thatBtn.getVisibility() != View.VISIBLE) {
|
|| thatBtn.getVisibility() != View.VISIBLE) {
|
||||||
thisBtn.setVisibility(View.GONE);
|
thisBtn.setVisibility(View.GONE);
|
||||||
@@ -301,116 +102,9 @@ public class QSMediaPlayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Hide any unused buttons
|
// Hide any unused buttons
|
||||||
for (; i < actionIds.length; i++) {
|
for (; i < QS_ACTION_IDS.length; i++) {
|
||||||
ImageButton thisBtn = mMediaNotifView.findViewById(actionIds[i]);
|
ImageButton thisBtn = mMediaNotifView.findViewById(QS_ACTION_IDS[i]);
|
||||||
thisBtn.setVisibility(View.GONE);
|
thisBtn.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public MediaSession.Token getMediaSessionToken() {
|
|
||||||
return mToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getMediaPlayerPackage() {
|
|
||||||
return mController.getPackageName();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check whether the media controlled by this player is currently playing
|
|
||||||
* @return whether it is playing, or false if no controller information
|
|
||||||
*/
|
|
||||||
public boolean isPlaying() {
|
|
||||||
if (mController == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
PlaybackState state = mController.getPlaybackState();
|
|
||||||
if (state == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (state.getState() == PlaybackState.STATE_PLAYING);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addAlbumArt(MediaMetadata metadata, int bgColor) {
|
|
||||||
Bitmap albumArt = metadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
|
|
||||||
float radius = mContext.getResources().getDimension(R.dimen.qs_media_corner_radius);
|
|
||||||
ImageView albumView = mMediaNotifView.findViewById(R.id.album_art);
|
|
||||||
if (albumArt != null) {
|
|
||||||
Log.d(TAG, "updating album art");
|
|
||||||
Bitmap original = albumArt.copy(Bitmap.Config.ARGB_8888, true);
|
|
||||||
int albumSize = (int) mContext.getResources().getDimension(R.dimen.qs_media_album_size);
|
|
||||||
Bitmap scaled = scaleBitmap(original, albumSize, albumSize);
|
|
||||||
RoundedBitmapDrawable roundedDrawable = RoundedBitmapDrawableFactory.create(
|
|
||||||
mContext.getResources(), scaled);
|
|
||||||
roundedDrawable.setCornerRadius(radius);
|
|
||||||
albumView.setImageDrawable(roundedDrawable);
|
|
||||||
} else {
|
|
||||||
Log.e(TAG, "No album art available");
|
|
||||||
albumView.setImageDrawable(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
mMediaNotifView.setBackgroundTintList(ColorStateList.valueOf(bgColor));
|
|
||||||
}
|
|
||||||
|
|
||||||
private Bitmap scaleBitmap(Bitmap original, int width, int height) {
|
|
||||||
Bitmap cropped = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
|
|
||||||
Canvas canvas = new Canvas(cropped);
|
|
||||||
|
|
||||||
float scale = (float) cropped.getWidth() / (float) original.getWidth();
|
|
||||||
float dy = (cropped.getHeight() - original.getHeight() * scale) / 2.0f;
|
|
||||||
Matrix transformation = new Matrix();
|
|
||||||
transformation.postTranslate(0, dy);
|
|
||||||
transformation.preScale(scale, scale);
|
|
||||||
|
|
||||||
Paint paint = new Paint();
|
|
||||||
paint.setFilterBitmap(true);
|
|
||||||
canvas.drawBitmap(original, transformation, paint);
|
|
||||||
|
|
||||||
return cropped;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void updateChip(MediaDevice device) {
|
|
||||||
if (mSeamless == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Handler handler = mSeamless.getHandler();
|
|
||||||
handler.post(() -> {
|
|
||||||
updateChipInternal(device);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateChipInternal(MediaDevice device) {
|
|
||||||
ColorStateList fgTintList = ColorStateList.valueOf(mForegroundColor);
|
|
||||||
|
|
||||||
// Update the outline color
|
|
||||||
LinearLayout viewLayout = (LinearLayout) mSeamless;
|
|
||||||
RippleDrawable bkgDrawable = (RippleDrawable) viewLayout.getBackground();
|
|
||||||
GradientDrawable rect = (GradientDrawable) bkgDrawable.getDrawable(0);
|
|
||||||
rect.setStroke(2, mForegroundColor);
|
|
||||||
rect.setColor(mBackgroundColor);
|
|
||||||
|
|
||||||
ImageView iconView = mSeamless.findViewById(R.id.media_seamless_image);
|
|
||||||
TextView deviceName = mSeamless.findViewById(R.id.media_seamless_text);
|
|
||||||
deviceName.setTextColor(fgTintList);
|
|
||||||
|
|
||||||
if (device != null) {
|
|
||||||
Drawable icon = device.getIcon();
|
|
||||||
iconView.setVisibility(View.VISIBLE);
|
|
||||||
iconView.setImageTintList(fgTintList);
|
|
||||||
|
|
||||||
if (icon instanceof AdaptiveIcon) {
|
|
||||||
AdaptiveIcon aIcon = (AdaptiveIcon) icon;
|
|
||||||
aIcon.setBackgroundColor(mBackgroundColor);
|
|
||||||
iconView.setImageDrawable(aIcon);
|
|
||||||
} else {
|
|
||||||
iconView.setImageDrawable(icon);
|
|
||||||
}
|
|
||||||
deviceName.setText(device.getName());
|
|
||||||
} else {
|
|
||||||
// Reset to default
|
|
||||||
iconView.setVisibility(View.GONE);
|
|
||||||
deviceName.setText(com.android.internal.R.string.ext_media_seamless_action);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ import com.android.systemui.Dependency;
|
|||||||
import com.android.systemui.Dumpable;
|
import com.android.systemui.Dumpable;
|
||||||
import com.android.systemui.R;
|
import com.android.systemui.R;
|
||||||
import com.android.systemui.broadcast.BroadcastDispatcher;
|
import com.android.systemui.broadcast.BroadcastDispatcher;
|
||||||
|
import com.android.systemui.dagger.qualifiers.Background;
|
||||||
import com.android.systemui.dump.DumpManager;
|
import com.android.systemui.dump.DumpManager;
|
||||||
import com.android.systemui.plugins.qs.DetailAdapter;
|
import com.android.systemui.plugins.qs.DetailAdapter;
|
||||||
import com.android.systemui.plugins.qs.QSTile;
|
import com.android.systemui.plugins.qs.QSTile;
|
||||||
@@ -60,6 +61,7 @@ import com.android.systemui.qs.external.CustomTile;
|
|||||||
import com.android.systemui.qs.logging.QSLogger;
|
import com.android.systemui.qs.logging.QSLogger;
|
||||||
import com.android.systemui.settings.BrightnessController;
|
import com.android.systemui.settings.BrightnessController;
|
||||||
import com.android.systemui.settings.ToggleSliderView;
|
import com.android.systemui.settings.ToggleSliderView;
|
||||||
|
import com.android.systemui.statusbar.NotificationMediaManager;
|
||||||
import com.android.systemui.statusbar.policy.BrightnessMirrorController;
|
import com.android.systemui.statusbar.policy.BrightnessMirrorController;
|
||||||
import com.android.systemui.statusbar.policy.BrightnessMirrorController.BrightnessMirrorListener;
|
import com.android.systemui.statusbar.policy.BrightnessMirrorController.BrightnessMirrorListener;
|
||||||
import com.android.systemui.tuner.TunerService;
|
import com.android.systemui.tuner.TunerService;
|
||||||
@@ -70,6 +72,7 @@ import java.io.PrintWriter;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
@@ -94,6 +97,8 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
|
|||||||
|
|
||||||
private final LinearLayout mMediaCarousel;
|
private final LinearLayout mMediaCarousel;
|
||||||
private final ArrayList<QSMediaPlayer> mMediaPlayers = new ArrayList<>();
|
private final ArrayList<QSMediaPlayer> mMediaPlayers = new ArrayList<>();
|
||||||
|
private final NotificationMediaManager mNotificationMediaManager;
|
||||||
|
private final Executor mBackgroundExecutor;
|
||||||
private LocalMediaManager mLocalMediaManager;
|
private LocalMediaManager mLocalMediaManager;
|
||||||
private MediaDevice mDevice;
|
private MediaDevice mDevice;
|
||||||
private boolean mUpdateCarousel = false;
|
private boolean mUpdateCarousel = false;
|
||||||
@@ -128,7 +133,7 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
|
|||||||
if (mDevice == null || !mDevice.equals(currentDevice)) {
|
if (mDevice == null || !mDevice.equals(currentDevice)) {
|
||||||
mDevice = currentDevice;
|
mDevice = currentDevice;
|
||||||
for (QSMediaPlayer p : mMediaPlayers) {
|
for (QSMediaPlayer p : mMediaPlayers) {
|
||||||
p.updateChip(mDevice);
|
p.updateDevice(mDevice);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -138,7 +143,7 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
|
|||||||
if (mDevice == null || !mDevice.equals(device)) {
|
if (mDevice == null || !mDevice.equals(device)) {
|
||||||
mDevice = device;
|
mDevice = device;
|
||||||
for (QSMediaPlayer p : mMediaPlayers) {
|
for (QSMediaPlayer p : mMediaPlayers) {
|
||||||
p.updateChip(mDevice);
|
p.updateDevice(mDevice);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -150,12 +155,16 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
|
|||||||
AttributeSet attrs,
|
AttributeSet attrs,
|
||||||
DumpManager dumpManager,
|
DumpManager dumpManager,
|
||||||
BroadcastDispatcher broadcastDispatcher,
|
BroadcastDispatcher broadcastDispatcher,
|
||||||
QSLogger qsLogger
|
QSLogger qsLogger,
|
||||||
|
NotificationMediaManager notificationMediaManager,
|
||||||
|
@Background Executor backgroundExecutor
|
||||||
) {
|
) {
|
||||||
super(context, attrs);
|
super(context, attrs);
|
||||||
mContext = context;
|
mContext = context;
|
||||||
mQSLogger = qsLogger;
|
mQSLogger = qsLogger;
|
||||||
mDumpManager = dumpManager;
|
mDumpManager = dumpManager;
|
||||||
|
mNotificationMediaManager = notificationMediaManager;
|
||||||
|
mBackgroundExecutor = backgroundExecutor;
|
||||||
|
|
||||||
setOrientation(VERTICAL);
|
setOrientation(VERTICAL);
|
||||||
|
|
||||||
@@ -255,7 +264,8 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
|
|||||||
|
|
||||||
if (player == null) {
|
if (player == null) {
|
||||||
Log.d(TAG, "creating new player");
|
Log.d(TAG, "creating new player");
|
||||||
player = new QSMediaPlayer(mContext, this);
|
player = new QSMediaPlayer(mContext, this, mNotificationMediaManager,
|
||||||
|
mBackgroundExecutor);
|
||||||
|
|
||||||
if (player.isPlaying()) {
|
if (player.isPlaying()) {
|
||||||
mMediaCarousel.addView(player.getView(), 0, lp); // add in front
|
mMediaCarousel.addView(player.getView(), 0, lp); // add in front
|
||||||
@@ -268,7 +278,7 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
|
|||||||
}
|
}
|
||||||
|
|
||||||
Log.d(TAG, "setting player session");
|
Log.d(TAG, "setting player session");
|
||||||
player.setMediaSession(this, token, icon, iconColor, bgColor, actionsContainer,
|
player.setMediaSession(token, icon, iconColor, bgColor, actionsContainer,
|
||||||
notif.getNotification(), mDevice);
|
notif.getNotification(), mDevice);
|
||||||
|
|
||||||
if (mMediaPlayers.size() > 0) {
|
if (mMediaPlayers.size() > 0) {
|
||||||
|
|||||||
@@ -17,108 +17,47 @@
|
|||||||
package com.android.systemui.qs;
|
package com.android.systemui.qs;
|
||||||
|
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
import android.content.ComponentName;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.content.pm.ResolveInfo;
|
|
||||||
import android.content.res.ColorStateList;
|
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.graphics.drawable.Icon;
|
import android.graphics.drawable.Icon;
|
||||||
import android.media.MediaMetadata;
|
|
||||||
import android.media.session.MediaController;
|
import android.media.session.MediaController;
|
||||||
import android.media.session.MediaSession;
|
import android.media.session.MediaSession;
|
||||||
import android.media.session.PlaybackState;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.view.KeyEvent;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.ImageButton;
|
import android.widget.ImageButton;
|
||||||
import android.widget.ImageView;
|
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import com.android.systemui.R;
|
import com.android.systemui.R;
|
||||||
|
import com.android.systemui.media.MediaControlPanel;
|
||||||
|
import com.android.systemui.statusbar.NotificationMediaManager;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* QQS mini media player
|
* QQS mini media player
|
||||||
*/
|
*/
|
||||||
public class QuickQSMediaPlayer {
|
public class QuickQSMediaPlayer extends MediaControlPanel {
|
||||||
|
|
||||||
private static final String TAG = "QQSMediaPlayer";
|
private static final String TAG = "QQSMediaPlayer";
|
||||||
|
|
||||||
private Context mContext;
|
// Button IDs for QS controls
|
||||||
private LinearLayout mMediaNotifView;
|
private static final int[] QQS_ACTION_IDS = {R.id.action0, R.id.action1, R.id.action2};
|
||||||
private MediaSession.Token mToken;
|
|
||||||
private MediaController mController;
|
|
||||||
private int mForegroundColor;
|
|
||||||
private ComponentName mRecvComponent;
|
|
||||||
|
|
||||||
private MediaController.Callback mSessionCallback = new MediaController.Callback() {
|
|
||||||
@Override
|
|
||||||
public void onSessionDestroyed() {
|
|
||||||
Log.d(TAG, "session destroyed");
|
|
||||||
mController.unregisterCallback(mSessionCallback);
|
|
||||||
|
|
||||||
// Hide all the old buttons
|
|
||||||
final int[] actionIds = {R.id.action0, R.id.action1, R.id.action2};
|
|
||||||
for (int i = 0; i < actionIds.length; i++) {
|
|
||||||
ImageButton thisBtn = mMediaNotifView.findViewById(actionIds[i]);
|
|
||||||
if (thisBtn != null) {
|
|
||||||
thisBtn.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add a restart button
|
|
||||||
ImageButton btn = mMediaNotifView.findViewById(actionIds[0]);
|
|
||||||
btn.setOnClickListener(v -> {
|
|
||||||
Log.d(TAG, "Attempting to restart session");
|
|
||||||
// Send a media button event to previously found receiver
|
|
||||||
if (mRecvComponent != null) {
|
|
||||||
Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON);
|
|
||||||
intent.setComponent(mRecvComponent);
|
|
||||||
int keyCode = KeyEvent.KEYCODE_MEDIA_PLAY;
|
|
||||||
intent.putExtra(
|
|
||||||
Intent.EXTRA_KEY_EVENT,
|
|
||||||
new KeyEvent(KeyEvent.ACTION_DOWN, keyCode));
|
|
||||||
mContext.sendBroadcast(intent);
|
|
||||||
} else {
|
|
||||||
Log.d(TAG, "No receiver to restart");
|
|
||||||
// If we don't have a receiver, try relaunching the activity instead
|
|
||||||
try {
|
|
||||||
mController.getSessionActivity().send();
|
|
||||||
} catch (PendingIntent.CanceledException e) {
|
|
||||||
Log.e(TAG, "Pending intent was canceled");
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
btn.setImageDrawable(mContext.getResources().getDrawable(R.drawable.lb_ic_play));
|
|
||||||
btn.setImageTintList(ColorStateList.valueOf(mForegroundColor));
|
|
||||||
btn.setVisibility(View.VISIBLE);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* Initialize mini media player for QQS
|
||||||
* @param context
|
* @param context
|
||||||
* @param parent
|
* @param parent
|
||||||
|
* @param manager
|
||||||
|
* @param backgroundExecutor
|
||||||
*/
|
*/
|
||||||
public QuickQSMediaPlayer(Context context, ViewGroup parent) {
|
public QuickQSMediaPlayer(Context context, ViewGroup parent, NotificationMediaManager manager,
|
||||||
mContext = context;
|
Executor backgroundExecutor) {
|
||||||
LayoutInflater inflater = LayoutInflater.from(mContext);
|
super(context, parent, manager, R.layout.qqs_media_panel, QQS_ACTION_IDS,
|
||||||
mMediaNotifView = (LinearLayout) inflater.inflate(R.layout.qqs_media_panel, parent, false);
|
backgroundExecutor);
|
||||||
}
|
|
||||||
|
|
||||||
public View getView() {
|
|
||||||
return mMediaNotifView;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* Update media panel view for the given media session
|
||||||
* @param token token for this media session
|
* @param token token for this media session
|
||||||
* @param icon app notification icon
|
* @param icon app notification icon
|
||||||
* @param iconColor foreground color (for text, icons)
|
* @param iconColor foreground color (for text, icons)
|
||||||
@@ -130,84 +69,30 @@ public class QuickQSMediaPlayer {
|
|||||||
*/
|
*/
|
||||||
public void setMediaSession(MediaSession.Token token, Icon icon, int iconColor, int bgColor,
|
public void setMediaSession(MediaSession.Token token, Icon icon, int iconColor, int bgColor,
|
||||||
View actionsContainer, int[] actionsToShow, PendingIntent contentIntent) {
|
View actionsContainer, int[] actionsToShow, PendingIntent contentIntent) {
|
||||||
mToken = token;
|
// Only update if this is a different session and currently playing
|
||||||
mForegroundColor = iconColor;
|
|
||||||
|
|
||||||
String oldPackage = "";
|
String oldPackage = "";
|
||||||
if (mController != null) {
|
if (getController() != null) {
|
||||||
oldPackage = mController.getPackageName();
|
oldPackage = getController().getPackageName();
|
||||||
}
|
}
|
||||||
MediaController controller = new MediaController(mContext, token);
|
MediaController controller = new MediaController(getContext(), token);
|
||||||
boolean samePlayer = mToken.equals(token) && oldPackage.equals(controller.getPackageName());
|
MediaSession.Token currentToken = getMediaSessionToken();
|
||||||
if (mController != null && !samePlayer && !isPlaying(controller)) {
|
boolean samePlayer = currentToken != null
|
||||||
// Only update if this is a different session and currently playing
|
&& currentToken.equals(token)
|
||||||
return;
|
&& oldPackage.equals(controller.getPackageName());
|
||||||
}
|
if (getController() != null && !samePlayer && !isPlaying(controller)) {
|
||||||
mController = controller;
|
|
||||||
MediaMetadata mMediaMetadata = mController.getMetadata();
|
|
||||||
|
|
||||||
// Try to find a receiver for the media button that matches this app
|
|
||||||
PackageManager pm = mContext.getPackageManager();
|
|
||||||
Intent it = new Intent(Intent.ACTION_MEDIA_BUTTON);
|
|
||||||
List<ResolveInfo> info = pm.queryBroadcastReceiversAsUser(it, 0, mContext.getUser());
|
|
||||||
if (info != null) {
|
|
||||||
for (ResolveInfo inf : info) {
|
|
||||||
if (inf.activityInfo.packageName.equals(mController.getPackageName())) {
|
|
||||||
mRecvComponent = inf.getComponentInfo().getComponentName();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mController.registerCallback(mSessionCallback);
|
|
||||||
|
|
||||||
if (mMediaMetadata == null) {
|
|
||||||
Log.e(TAG, "Media metadata was null");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Action
|
super.setMediaSession(token, icon, iconColor, bgColor, contentIntent, null, null);
|
||||||
mMediaNotifView.setOnClickListener(v -> {
|
|
||||||
try {
|
|
||||||
contentIntent.send();
|
|
||||||
mContext.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
|
|
||||||
} catch (PendingIntent.CanceledException e) {
|
|
||||||
Log.e(TAG, "Pending intent was canceled: " + e.getMessage());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
mMediaNotifView.setBackgroundTintList(ColorStateList.valueOf(bgColor));
|
|
||||||
|
|
||||||
// App icon
|
|
||||||
ImageView appIcon = mMediaNotifView.findViewById(R.id.icon);
|
|
||||||
Drawable iconDrawable = icon.loadDrawable(mContext);
|
|
||||||
iconDrawable.setTint(mForegroundColor);
|
|
||||||
appIcon.setImageDrawable(iconDrawable);
|
|
||||||
|
|
||||||
// Song name
|
|
||||||
TextView titleText = mMediaNotifView.findViewById(R.id.header_title);
|
|
||||||
String songName = mMediaMetadata.getString(MediaMetadata.METADATA_KEY_TITLE);
|
|
||||||
titleText.setText(songName);
|
|
||||||
titleText.setTextColor(mForegroundColor);
|
|
||||||
|
|
||||||
// Buttons we can display
|
|
||||||
final int[] actionIds = {R.id.action0, R.id.action1, R.id.action2};
|
|
||||||
|
|
||||||
// Existing buttons in the notification
|
|
||||||
LinearLayout parentActionsLayout = (LinearLayout) actionsContainer;
|
LinearLayout parentActionsLayout = (LinearLayout) actionsContainer;
|
||||||
final int[] notifActionIds = {
|
|
||||||
com.android.internal.R.id.action0,
|
|
||||||
com.android.internal.R.id.action1,
|
|
||||||
com.android.internal.R.id.action2,
|
|
||||||
com.android.internal.R.id.action3,
|
|
||||||
com.android.internal.R.id.action4
|
|
||||||
};
|
|
||||||
|
|
||||||
int i = 0;
|
int i = 0;
|
||||||
if (actionsToShow != null) {
|
if (actionsToShow != null) {
|
||||||
int maxButtons = Math.min(actionsToShow.length, parentActionsLayout.getChildCount());
|
int maxButtons = Math.min(actionsToShow.length, parentActionsLayout.getChildCount());
|
||||||
maxButtons = Math.min(maxButtons, actionIds.length);
|
maxButtons = Math.min(maxButtons, QQS_ACTION_IDS.length);
|
||||||
for (; i < maxButtons; i++) {
|
for (; i < maxButtons; i++) {
|
||||||
ImageButton thisBtn = mMediaNotifView.findViewById(actionIds[i]);
|
ImageButton thisBtn = mMediaNotifView.findViewById(QQS_ACTION_IDS[i]);
|
||||||
int thatId = notifActionIds[actionsToShow[i]];
|
int thatId = NOTIF_ACTION_IDS[actionsToShow[i]];
|
||||||
ImageButton thatBtn = parentActionsLayout.findViewById(thatId);
|
ImageButton thatBtn = parentActionsLayout.findViewById(thatId);
|
||||||
if (thatBtn == null || thatBtn.getDrawable() == null
|
if (thatBtn == null || thatBtn.getDrawable() == null
|
||||||
|| thatBtn.getVisibility() != View.VISIBLE) {
|
|| thatBtn.getVisibility() != View.VISIBLE) {
|
||||||
@@ -225,38 +110,9 @@ public class QuickQSMediaPlayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Hide any unused buttons
|
// Hide any unused buttons
|
||||||
for (; i < actionIds.length; i++) {
|
for (; i < QQS_ACTION_IDS.length; i++) {
|
||||||
ImageButton thisBtn = mMediaNotifView.findViewById(actionIds[i]);
|
ImageButton thisBtn = mMediaNotifView.findViewById(QQS_ACTION_IDS[i]);
|
||||||
thisBtn.setVisibility(View.GONE);
|
thisBtn.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public MediaSession.Token getMediaSessionToken() {
|
|
||||||
return mToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check whether the media controlled by this player is currently playing
|
|
||||||
* @return whether it is playing, or false if no controller information
|
|
||||||
*/
|
|
||||||
public boolean isPlaying(MediaController controller) {
|
|
||||||
if (controller == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
PlaybackState state = controller.getPlaybackState();
|
|
||||||
if (state == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (state.getState() == PlaybackState.STATE_PLAYING);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check whether this player has an attached media session.
|
|
||||||
* @return whether there is a controller with a current media session.
|
|
||||||
*/
|
|
||||||
public boolean hasMediaSession() {
|
|
||||||
return mController != null && mController.getPlaybackState() != null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,18 +29,21 @@ import android.widget.LinearLayout;
|
|||||||
import com.android.systemui.Dependency;
|
import com.android.systemui.Dependency;
|
||||||
import com.android.systemui.R;
|
import com.android.systemui.R;
|
||||||
import com.android.systemui.broadcast.BroadcastDispatcher;
|
import com.android.systemui.broadcast.BroadcastDispatcher;
|
||||||
|
import com.android.systemui.dagger.qualifiers.Background;
|
||||||
import com.android.systemui.dump.DumpManager;
|
import com.android.systemui.dump.DumpManager;
|
||||||
import com.android.systemui.plugins.qs.QSTile;
|
import com.android.systemui.plugins.qs.QSTile;
|
||||||
import com.android.systemui.plugins.qs.QSTile.SignalState;
|
import com.android.systemui.plugins.qs.QSTile.SignalState;
|
||||||
import com.android.systemui.plugins.qs.QSTile.State;
|
import com.android.systemui.plugins.qs.QSTile.State;
|
||||||
import com.android.systemui.qs.customize.QSCustomizer;
|
import com.android.systemui.qs.customize.QSCustomizer;
|
||||||
import com.android.systemui.qs.logging.QSLogger;
|
import com.android.systemui.qs.logging.QSLogger;
|
||||||
|
import com.android.systemui.statusbar.NotificationMediaManager;
|
||||||
import com.android.systemui.tuner.TunerService;
|
import com.android.systemui.tuner.TunerService;
|
||||||
import com.android.systemui.tuner.TunerService.Tunable;
|
import com.android.systemui.tuner.TunerService.Tunable;
|
||||||
import com.android.systemui.util.Utils;
|
import com.android.systemui.util.Utils;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
@@ -72,9 +75,12 @@ public class QuickQSPanel extends QSPanel {
|
|||||||
AttributeSet attrs,
|
AttributeSet attrs,
|
||||||
DumpManager dumpManager,
|
DumpManager dumpManager,
|
||||||
BroadcastDispatcher broadcastDispatcher,
|
BroadcastDispatcher broadcastDispatcher,
|
||||||
QSLogger qsLogger
|
QSLogger qsLogger,
|
||||||
|
NotificationMediaManager notificationMediaManager,
|
||||||
|
@Background Executor backgroundExecutor
|
||||||
) {
|
) {
|
||||||
super(context, attrs, dumpManager, broadcastDispatcher, qsLogger);
|
super(context, attrs, dumpManager, broadcastDispatcher, qsLogger, notificationMediaManager,
|
||||||
|
backgroundExecutor);
|
||||||
if (mFooter != null) {
|
if (mFooter != null) {
|
||||||
removeView(mFooter.getView());
|
removeView(mFooter.getView());
|
||||||
}
|
}
|
||||||
@@ -93,7 +99,8 @@ public class QuickQSPanel extends QSPanel {
|
|||||||
mHorizontalLinearLayout.setClipToPadding(false);
|
mHorizontalLinearLayout.setClipToPadding(false);
|
||||||
|
|
||||||
int marginSize = (int) mContext.getResources().getDimension(R.dimen.qqs_media_spacing);
|
int marginSize = (int) mContext.getResources().getDimension(R.dimen.qqs_media_spacing);
|
||||||
mMediaPlayer = new QuickQSMediaPlayer(mContext, mHorizontalLinearLayout);
|
mMediaPlayer = new QuickQSMediaPlayer(mContext, mHorizontalLinearLayout,
|
||||||
|
notificationMediaManager, backgroundExecutor);
|
||||||
LayoutParams lp2 = new LayoutParams(0, LayoutParams.MATCH_PARENT, 1);
|
LayoutParams lp2 = new LayoutParams(0, LayoutParams.MATCH_PARENT, 1);
|
||||||
lp2.setMarginEnd(marginSize);
|
lp2.setMarginEnd(marginSize);
|
||||||
lp2.setMarginStart(0);
|
lp2.setMarginStart(0);
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ import com.android.systemui.plugins.qs.QSTileView;
|
|||||||
import com.android.systemui.qs.customize.QSCustomizer;
|
import com.android.systemui.qs.customize.QSCustomizer;
|
||||||
import com.android.systemui.qs.logging.QSLogger;
|
import com.android.systemui.qs.logging.QSLogger;
|
||||||
import com.android.systemui.qs.tileimpl.QSTileImpl;
|
import com.android.systemui.qs.tileimpl.QSTileImpl;
|
||||||
|
import com.android.systemui.statusbar.NotificationMediaManager;
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@@ -53,6 +54,7 @@ import java.io.FileDescriptor;
|
|||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.io.StringWriter;
|
import java.io.StringWriter;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
@RunWith(AndroidTestingRunner.class)
|
@RunWith(AndroidTestingRunner.class)
|
||||||
@RunWithLooper
|
@RunWithLooper
|
||||||
@@ -79,6 +81,10 @@ public class QSPanelTest extends SysuiTestCase {
|
|||||||
private QSDetail.Callback mCallback;
|
private QSDetail.Callback mCallback;
|
||||||
@Mock
|
@Mock
|
||||||
private QSTileView mQSTileView;
|
private QSTileView mQSTileView;
|
||||||
|
@Mock
|
||||||
|
private NotificationMediaManager mNotificationMediaManager;
|
||||||
|
@Mock
|
||||||
|
private Executor mBackgroundExecutor;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setup() throws Exception {
|
public void setup() throws Exception {
|
||||||
@@ -88,7 +94,7 @@ public class QSPanelTest extends SysuiTestCase {
|
|||||||
mTestableLooper.runWithLooper(() -> {
|
mTestableLooper.runWithLooper(() -> {
|
||||||
mMetricsLogger = mDependency.injectMockDependency(MetricsLogger.class);
|
mMetricsLogger = mDependency.injectMockDependency(MetricsLogger.class);
|
||||||
mQsPanel = new QSPanel(mContext, null, mDumpManager, mBroadcastDispatcher,
|
mQsPanel = new QSPanel(mContext, null, mDumpManager, mBroadcastDispatcher,
|
||||||
mQSLogger);
|
mQSLogger, mNotificationMediaManager, mBackgroundExecutor);
|
||||||
// Provides a parent with non-zero size for QSPanel
|
// Provides a parent with non-zero size for QSPanel
|
||||||
mParentView = new FrameLayout(mContext);
|
mParentView = new FrameLayout(mContext);
|
||||||
mParentView.addView(mQsPanel);
|
mParentView.addView(mQsPanel);
|
||||||
|
|||||||
Reference in New Issue
Block a user