package me.barrasso.android.volume.popup;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.PixelFormat;
import android.graphics.PorterDuff;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.media.AudioManager;
import android.os.Build;
import android.provider.Settings;
import android.support.v4.media.MediaMetadataCompat;
import android.support.v4.media.session.PlaybackStateCompat;
import android.text.TextUtils;
import android.util.Pair;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.SeekBar;
import android.widget.TextView;
import com.squareup.otto.Subscribe;
import java.io.FileNotFoundException;
import me.barrasso.android.volume.R;
import me.barrasso.android.volume.media.StreamResources;
import me.barrasso.android.volume.media.VolumePanelInfo;
import me.barrasso.android.volume.media.compat.RemoteControlCompat;
import me.barrasso.android.volume.ui.transition.TransitionCompat;
import me.barrasso.android.volume.utils.Utils;
import me.barrasso.android.volume.utils.VolumeManager;
import static me.barrasso.android.volume.LogUtils.LOGD;
import static me.barrasso.android.volume.LogUtils.LOGE;
import static me.barrasso.android.volume.LogUtils.LOGI;
/**
* Android "L" style (API 21) Heads Up Notifications... but for volume_3!
* TODO: support song seeking like the Android lockscreen.
*/
public final class HeadsUpVolumePanel extends VolumePanel {
public static final String TAG = HeadsUpVolumePanel.class.getSimpleName();
static final int _COLOR = Color.argb(204, 0, 0, 0);
public static final VolumePanelInfo<HeadsUpVolumePanel> VOLUME_PANEL_INFO =
new VolumePanelInfo<HeadsUpVolumePanel>(HeadsUpVolumePanel.class);
public HeadsUpVolumePanel(PopupWindowManager pWindowManager) {
super(pWindowManager);
}
private TransitionCompat transition;
TextView artist;
TextView song;
TextView clock;
ImageView icon;
ProgressBar seekBar;
ViewGroup root, musicPanel, visiblePanel;
ImageButton playPause, mBtnNext, mBtnPrev;
@TargetApi(Build.VERSION_CODES.KITKAT)
@Override
public void onCreate() {
super.onCreate();
Context context = getContext();
transition = TransitionCompat.get();
LayoutInflater inflater = LayoutInflater.from(context);
root = (ViewGroup) inflater.inflate(R.layout.hu_volume_adjust, null);
seekBar = (ProgressBar) root.findViewById(android.R.id.progress);
icon = (ImageView) root.findViewById(R.id.stream_icon);
icon.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
openMusic();
}
});
clock = (TextView) root.findViewById(android.R.id.text2);
artist = (TextView) root.findViewById(R.id.track_artist);
song = (TextView) root.findViewById(R.id.track_song);
musicPanel = (ViewGroup) root.findViewById(R.id.music_panel);
attachPlaybackListeners(root, new MediaButtonClickListener(), new MusicLongClickListener());
playPause = (ImageButton) root.findViewById(R.id.media_play_pause);
mBtnNext = (ImageButton) root.findViewById(R.id.media_next);
mBtnPrev = (ImageButton) root.findViewById(R.id.media_previous);
visiblePanel = (ViewGroup) root.findViewById(R.id.visible_panel);
toggleSeekBar(seek);
setEnableMarquee(true);
updateMediaIcons();
transition.beginDelayedTransition((ViewGroup) root.findViewById(R.id.slider_group));
mLayout = root;
}
protected void updateMediaIcons() {
LOGI(TAG, "updateMediaIcons()");
Resources res = getResources();
Drawable next = res.getDrawable(R.drawable.ic_media_next_white);
Drawable prev = res.getDrawable(R.drawable.ic_media_previous_white);
next.mutate().setColorFilter(color, PorterDuff.Mode.MULTIPLY);
prev.mutate().setColorFilter(color, PorterDuff.Mode.MULTIPLY);
mBtnNext.setImageDrawable(next);
mBtnPrev.setImageDrawable(prev);
updatePlayState();
}
@Override public void setBackgroundColor(final int newColor) {
super.setBackgroundColor(newColor);
visiblePanel.setBackground(new ColorDrawable(newColor));
}
@Override public void setColor(final int newColor) {
super.setColor(newColor);
toggleSeekBar(seek);
updateMediaIcons();
updateIcon((StreamResources) seekBar.getTag());
}
@Override
public void setTertiaryColor(final int newColor) {
super.setTertiaryColor(newColor);
song.setTextColor(newColor);
artist.setTextColor(newColor);
clock.setTextColor(newColor);
}
@Override public void setSeek(final boolean shouldSeek) {
super.setSeek(shouldSeek);
toggleSeekBar(shouldSeek);
}
protected void toggleSeekBar(final boolean shouldSeek) {
// If we've got a SeekBar, handle seeking!
if (seekBar instanceof SeekBar) {
SeekBar seeker = (SeekBar) seekBar;
seeker.setOnSeekBarChangeListener((shouldSeek) ? this : null);
seeker.setOnTouchListener((shouldSeek) ? null : noTouchListener);
Drawable thumb = null;
if (shouldSeek) {
thumb = getResources().getDrawable(R.drawable.scrubber_control_selector_mini);
thumb.mutate().setColorFilter(color, PorterDuff.Mode.MULTIPLY);
thumb.setBounds(0,0, thumb.getIntrinsicWidth(), thumb.getIntrinsicHeight());
}
seeker.setThumb(thumb);
// NOTE: there's so weird issue with setting the thumb dynamically.
// This seems to do the trick (fingers crossed).
Utils.tap((View) seeker.getParent());
seeker.invalidate();
}
}
/*package*/ class MusicLongClickListener implements View.OnLongClickListener {
@Override
public boolean onLongClick(View view) {
LOGD("MusicTouchListener", "onLongClick(" + view.getId() + ")");
switch (view.getId()) {
case R.id.media_play_pause:
case R.id.stream_icon:
openMusic();
return true;
}
return false;
}
}
// Bit of a misnomer because it'll launch settings when music isn't playing.
/*package*/ void openMusic() {
hide();
if (hasAlbumArt) {
launchMusicApp();
} else {
Intent volumeSettings = new Intent(Settings.ACTION_SOUND_SETTINGS);
startActivity(volumeSettings);
}
}
/*package*/ void updatePlayState() {
int icon = ((mMusicActive) ? R.drawable.ic_media_pause_white : R.drawable.ic_media_play_white);
Drawable play = getResources().getDrawable(icon);
play.mutate().setColorFilter(color, PorterDuff.Mode.MULTIPLY);
playPause.setImageDrawable(play);
}
// Used to keep track if we're displaying album art.
protected boolean hasAlbumArt = false;
protected boolean hasDuration = false;
@TargetApi(Build.VERSION_CODES.KITKAT)
@Override
public void onPlayStateChanged(Pair<MediaMetadataCompat, PlaybackStateCompat> mediaInfo) {
if (!created) return;
super.onPlayStateChanged(mediaInfo);
LOGI(TAG, "onPlayStateChanged()");
LOGI(TAG, RemoteControlCompat.getMediaMetadataLog(mediaInfo.first));
String sTitle = mediaInfo.first.getString(MediaMetadataCompat.METADATA_KEY_TITLE);
String sArtist = mediaInfo.first.getString(MediaMetadataCompat.METADATA_KEY_ARTIST);
boolean hasArtist = !TextUtils.isEmpty(sArtist);
boolean hasTitle = !TextUtils.isEmpty(sTitle);
boolean missingData = (!hasArtist || !hasTitle);
if (mMusicActive && !missingData) {
transition.beginDelayedTransition(musicPanel, TransitionCompat.KEY_AUDIO_TRANSITION);
}
updatePlayState();
song.setText(sTitle);
artist.setText(sArtist);
artist.setVisibility(View.VISIBLE);
Object tag = seekBar.getTag();
// Update button visibility based on the transport flags.
if (null == mediaInfo.second || !mMusicActive || mediaInfo.second.getActions() <= 0) {
mBtnNext.setVisibility(View.VISIBLE);
mBtnPrev.setVisibility(View.VISIBLE);
playPause.setVisibility(View.VISIBLE);
} else {
final long flags = mediaInfo.second.getActions();
setVisibilityBasedOnFlag(mBtnPrev, flags, PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS);
setVisibilityBasedOnFlag(mBtnNext, flags, PlaybackStateCompat.ACTION_SKIP_TO_NEXT);
setVisibilityBasedOnFlag(playPause, flags,
PlaybackStateCompat.ACTION_PLAY
| PlaybackStateCompat.ACTION_PAUSE
| PlaybackStateCompat.ACTION_PLAY_PAUSE);
}
// If we have album art, use it!
if (mMusicActive) {
Bitmap albumArt = null;
try {
albumArt = RemoteControlCompat.getBitmap(getContext(), mediaInfo.first);
} catch (FileNotFoundException fne) {
LOGE(TAG, "Album art URI invalid.", fne);
}
if (null != albumArt) {
LOGI(TAG, "Loading album art from RemoteController.");
albumArt = Bitmap.createScaledBitmap(albumArt, icon.getWidth(), icon.getHeight(), false);
icon.setImageAlpha(0xFF);
icon.setColorFilter(null);
icon.setImageBitmap(albumArt);
hasAlbumArt = true;
} else {
hasAlbumArt = false;
}
}
// Next, we'll try to display the app's icon.
if (mMusicActive && !hasAlbumArt && !TextUtils.isEmpty(musicPackageName)) {
Drawable appIcon = null;
try {
Bitmap iconBmp = RemoteControlCompat.getBitmap(getContext(), mediaInfo.first);
if (null != iconBmp) {
appIcon = new BitmapDrawable(getContext().getResources(), iconBmp);
LOGI(TAG, "App icon loaded from MediaMetadata instead.");
}
} catch (FileNotFoundException fne) {
LOGE(TAG, "Album art URI invalid.", fne);
}
if (null == appIcon)
appIcon = getAppIcon(musicPackageName);
if (null != appIcon) {
LOGI(TAG, "Loading app icon instead of album art.");
final ColorMatrix cm = new ColorMatrix();
cm.setSaturation(0);
appIcon.setColorFilter(new ColorMatrixColorFilter(cm));
icon.setColorFilter(_COLOR, PorterDuff.Mode.MULTIPLY);
icon.setImageAlpha(0xEF);
icon.setImageDrawable(appIcon);
hasAlbumArt = true;
} else {
hasAlbumArt = false;
}
}
if (!mMusicActive) hasAlbumArt = false;
// If music isn't playing, display the stream mode text.
if (tag instanceof StreamResources) {
StreamResources sr = (StreamResources) tag;
if ((!mMusicActive || missingData)) {
song.setText(getStreamName(sr));
artist.setText(null);
artist.setVisibility(View.GONE);
}
if (!hasAlbumArt) updateIcon(sr);
}
}
protected Drawable getAppIcon(String packageName) {
PackageManager mPM = getContext().getPackageManager();
try {
return mPM.getApplicationIcon(packageName);
} catch (PackageManager.NameNotFoundException nfe) {
LOGE(TAG, "Couldn't get app icon for `" + packageName + "`", nfe);
return null;
}
}
@Override
public void screen(boolean on) {
super.screen(on);
setEnableMarquee(on);
}
private void setEnableMarquee(boolean enabled) {
LOGD(TAG, "setEnableMarquee(" + enabled + ')');
if (artist != null) artist.setSelected(enabled);
if (song != null) song.setSelected(enabled);
}
@Override public void onStreamVolumeChange(int streamType, int volume, int max) {
StreamResources resources = StreamResources.resourceForStreamType(streamType);
LOGD(TAG, "onStreamVolumeChange(" + VolumeManager.getStreamName(streamType) +
", " + volume + ", " + max + ")");
resources.setVolume(volume);
if (!hasAlbumArt) updateIcon(resources);
if (!hasDuration) {
seekBar.setMax(max);
seekBar.setProgress(volume);
}
LayerDrawable layer = (LayerDrawable) seekBar.getProgressDrawable();
layer.findDrawableByLayerId(android.R.id.progress).mutate().setColorFilter(color, PorterDuff.Mode.MULTIPLY);
seekBar.setTag(resources);
if (!mMusicActive) {
int descRes = resources.getDescRes();
// Display silent/ vibrate based on the ringer setting.
if (resources.getVolume() <= 0 &&
(resources == StreamResources.RingerStream ||
resources == StreamResources.NotificationStream)) {
if (mRingerMode == AudioManager.RINGER_MODE_VIBRATE) {
descRes = R.string.vibrate_c;
} else if (mRingerMode == AudioManager.RINGER_MODE_SILENT) {
descRes = R.string.silent_c;
}
}
song.setText(descRes);
artist.setText(null);
artist.setVisibility(View.GONE);
}
show();
}
protected void updateIcon(StreamResources sr) {
if (null == sr) return;
LOGI(TAG, "updateIcon(" + sr.getStreamType() + ')');
int iconRes = ((sr.getVolume() <= 0) ? sr.getIconMuteRes() : sr.getIconRes());
Drawable drawable = getResources().getDrawable(iconRes);
icon.setColorFilter(color, PorterDuff.Mode.MULTIPLY);
icon.setImageAlpha(0xFF);
icon.setImageDrawable(drawable);
}
@Override public void onVisibilityChanged(int visibility) {
super.onVisibilityChanged(visibility);
switch (visibility) {
case View.GONE:
setEnableMarquee(false);
break;
case View.VISIBLE:
setEnableMarquee(true);
break;
}
}
@SuppressWarnings("unused")
@Subscribe
public void onPlaybackEvent(Pair<MediaMetadataCompat, PlaybackStateCompat> mediaInfo) {
// NOTE: This MUST be added to the final descendant of VolumePanel!
LOGI(TAG, "onPlaybackEvent()");
this.onPlayStateChanged(mediaInfo);
}
@Override public boolean supportsMediaPlayback() { return true; }
@Override public boolean isInteractive() { return true; }
@Override public void onRotationChanged(final int rotation) {
super.onRotationChanged(rotation);
mWindowAttributes = getWindowLayoutParams();
onWindowAttributesChanged();
}
@Override public void setStretch(boolean stretchy) {
super.setStretch(stretchy);
mWindowAttributes = getWindowLayoutParams();
onWindowAttributesChanged();
}
@Override public WindowManager.LayoutParams getWindowLayoutParams() {
int flags = (WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH |
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL |
WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR |
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN |
WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED );
WindowManager.LayoutParams WPARAMS = new WindowManager.LayoutParams(
WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.WRAP_CONTENT,
0, 0, WindowManager.LayoutParams.TYPE_SYSTEM_ERROR, flags, PixelFormat.TRANSLUCENT);
WPARAMS.windowAnimations = android.R.style.Animation_Translucent;
WPARAMS.packageName = getContext().getPackageName();
WPARAMS.setTitle(TAG);
WPARAMS.rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE;
Resources res = getResources();
final int panelWidth = getNotificationPanelWidth();
final int maxWidth = ((panelWidth > 0) ? panelWidth : res.getDimensionPixelSize(R.dimen.notification_panel_width));
final int menuWidth = res.getDimensionPixelSize(R.dimen.max_menu_width);
final int screenWidth = getWindowWidth();
if (stretch || (maxWidth <= 0 && (!res.getBoolean(R.bool.isTablet) && screenWidth < menuWidth))) {
WPARAMS.gravity = (Gravity.FILL_HORIZONTAL | Gravity.TOP);
} else {
WPARAMS.gravity = (Gravity.CENTER_HORIZONTAL | Gravity.TOP);
WPARAMS.width = (maxWidth <= 0) ? menuWidth : maxWidth;
}
WPARAMS.screenBrightness = WPARAMS.buttonBrightness = WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE;
return WPARAMS;
}
}