package me.barrasso.android.volume.popup; import android.animation.Animator; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.graphics.PixelFormat; import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; import android.graphics.Typeface; import android.media.AudioManager; import android.provider.Settings; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; import android.view.animation.AccelerateInterpolator; import android.widget.ImageView; import android.widget.TextView; import me.barrasso.android.volume.R; import me.barrasso.android.volume.media.VolumePanelInfo; import me.barrasso.android.volume.media.StreamResources; import miui.v5.widget.CircleProgressView; import static android.media.AudioManager.RINGER_MODE_VIBRATE; import static me.barrasso.android.volume.LogUtils.LOGD; import static me.barrasso.android.volume.LogUtils.LOGE; import static me.barrasso.android.volume.LogUtils.LOGI; /** * Simple theme "borrowed" from MIUI V5! */ public class CircleVolumePanel extends VolumePanel { public static final String TAG = CircleVolumePanel.class.getSimpleName(); public static final VolumePanelInfo<CircleVolumePanel> VOLUME_PANEL_INFO = new VolumePanelInfo<CircleVolumePanel>(CircleVolumePanel.class); public CircleVolumePanel(PopupWindowManager pWindowManager) { super(pWindowManager); } ImageView icon, headset; CircleProgressView seekBar; ViewGroup root; TextView streamName; View divider; @Override public void onCreate() { super.onCreate(); Context context = getContext(); LayoutInflater inflater = LayoutInflater.from(context); root = (ViewGroup) inflater.inflate(R.layout.miui_volume_adjust, null); seekBar = (CircleProgressView) root.findViewById(android.R.id.progress); icon = (ImageView) root.findViewById(R.id.stream_icon); streamName = (TextView) root.findViewById(R.id.streamName); headset = (ImageView) root.findViewById(R.id.v5_volume_headset); divider = root.findViewById(R.id.divider); // Launch the system sound settings when the icon is clicked. icon.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { hide(); Intent volumeSettings = new Intent(Settings.ACTION_SOUND_SETTINGS); startActivity(volumeSettings); } }); // Set our font to MIUI Typeface miui = Typeface.createFromAsset(context.getAssets(), "fonts/Miui-Regular.ttf"); streamName.setTypeface(miui); // Change the icon to use for Bluetooth Music. MusicMode.BLUETOOTH.iconResId = R.drawable.v5_ic_audio_bt; MusicMode.BLUETOOTH.iconMuteResId = R.drawable.v5_ic_audio_bt_mute; mLayout = root; } public static int[] iconForStream(StreamResources res) { switch (res) { case NotificationStream: return new int[] { R.drawable.v5_ic_audio_notification, R.drawable.v5_ic_audio_notification_mute }; case MediaStream: return new int[] { R.drawable.v5_ic_audio_media, R.drawable.v5_ic_audio_media }; case BluetoothSCOStream: return new int[] { R.drawable.v5_ic_audio_bt, R.drawable.v5_ic_audio_bt_mute }; case RingerStream: return new int[] { R.drawable.v5_ic_audio_ring_notif, R.drawable.v5_ic_audio_ring_notif_mute }; case VoiceStream: return new int[] { R.drawable.v5_ic_audio_phone, R.drawable.v5_ic_audio_phone_mute }; case AlarmStream: return new int[] { R.drawable.v5_ic_audio_alarm, R.drawable.v5_ic_audio_alarm_mute }; default: return new int[] { R.drawable.v5_ic_audio_ring_notif, R.drawable.v5_ic_audio_ring_notif_mute }; } } @Override public void setTertiaryColor(final int newColor) { super.setTertiaryColor(newColor); streamName.setTextColor(newColor); } @Override public void setColor(int color) { LOGI(TAG, "setColor(" + color + ')'); super.setColor(color); try { seekBar.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY)); } catch (Throwable t) { LOGE(TAG, "Error applying color filter to the MIUI VP.", t); } } protected MusicMode musicMode = MusicMode.DEFAULT; @Override public void setMusicIcon(MusicMode mode) { musicMode = mode; switch (mode) { case BLUETOOTH: super.setMusicIcon(mode); break; case HEADSET: setHeadset(R.drawable.v5_ic_audio_headset); setHeadsetVisibility(View.VISIBLE); break; case DEFAULT: setHeadset(R.drawable.v5_ic_audio_speaker); setHeadsetVisibility(View.VISIBLE); break; } } protected void updateIcon(StreamResources sr) { int[] icons = iconForStream(sr); // Special case for when the device is in vibrate mode. if ((sr == StreamResources.RingerStream || sr == StreamResources.NotificationStream) && mRingerMode == AudioManager.RINGER_MODE_VIBRATE) icons = new int[] { R.drawable.v5_ic_audio_ring_notif, R.drawable.v5_ic_audio_ring_notif_vibrate }; int iconRes = ((sr.getVolume() <= 0) ? icons[1] : icons[0]); icon.setImageResource(iconRes); } @Override public void onVisibilityChanged(int visibility) { super.onVisibilityChanged(visibility); // If an animation is running, cancel it. switch (visibility) { case View.GONE: cancelAnimation(); hasPulsed = false; break; } } protected void cancelAnimation() { if (null != animator) animator.cancel(); animator = null; } protected Animator animator; protected boolean hasPulsed = false; protected Animator shuffle(View target) { LOGI(TAG, "shuffle()"); final int duration = Resources.getSystem().getInteger(android.R.integer.config_mediumAnimTime); ObjectAnimator rotation = ObjectAnimator.ofFloat(target, View.TRANSLATION_X, 5, 0, -5, 0, 5, 0, -5, 0, 5, 0, -5, 0, 5, 0); rotation.setDuration(duration); rotation.setStartDelay(duration / 4); target.setHasTransientState(true); return rotation; } protected Animator wiggle(View target) { LOGI(TAG, "wiggle()"); final int duration = Resources.getSystem().getInteger(android.R.integer.config_mediumAnimTime); ObjectAnimator rotation = ObjectAnimator.ofFloat(target, View.ROTATION, 5, 0, -5, 0, 5, 0, -5, 0, 5, 0, -5, 0, 5, 0); rotation.setDuration(duration); rotation.setStartDelay(duration / 4); target.setHasTransientState(true); return rotation; } protected Animator pulse() { LOGI(TAG, "pulse()"); final float scaleFactor = 1.075f; final int duration = Resources.getSystem().getInteger(android.R.integer.config_shortAnimTime); ObjectAnimator scaleX = ObjectAnimator.ofFloat(root, View.SCALE_X, scaleFactor, 1.0f); ObjectAnimator scaleY = ObjectAnimator.ofFloat(root, View.SCALE_Y, scaleFactor, 1.0f); final AnimatorSet scaleAnim = new AnimatorSet(); scaleAnim.play(scaleX).with(scaleY); scaleAnim.setDuration(duration); scaleAnim.setStartDelay(duration / 4); scaleAnim.setInterpolator(new AccelerateInterpolator()); root.setHasTransientState(true); hasPulsed = true; return scaleAnim; } @Override public void onStreamVolumeChange(int streamType, int volume, int max) { // Cancel any animations already running. if (isShowing()) cancelAnimation(); // Update the icon & progress based on the volume_3 change. StreamResources resources = StreamResources.resourceForStreamType(streamType); resources.setVolume(volume); LOGD(TAG, "onStreamVolumeChange(" + streamType + ", " + volume + ", " + max + ")"); // Hide the headset when it's not its turn! if (mMusicActive || streamType == AudioManager.STREAM_MUSIC) { setHeadsetVisibility(View.VISIBLE); setHeadset((musicMode == MusicMode.HEADSET) ? R.drawable.v5_ic_audio_headset : R.drawable.v5_ic_audio_speaker); } else if (streamType == STREAM_BLUETOOTH_SCO) { setHeadsetVisibility(View.VISIBLE); setHeadset(R.drawable.v5_ic_audio_headset); } else { setHeadsetVisibility(View.GONE); } try { seekBar.setMax(max); } catch (Throwable t) { LOGE(TAG, "Error CircleProgressView#setMax(int)", t); } seekBar.setProgress(volume); seekBar.setTag(resources); int descRes = resources.getDescRes(); if (resources.getVolume() <= 0 && (resources == StreamResources.RingerStream || resources == StreamResources.NotificationStream)) { if (mRingerMode == AudioManager.RINGER_MODE_VIBRATE) { descRes = R.string.vibrate_c; AnimatorSet set = new AnimatorSet(); set.play(wiggle(icon)).with(shuffle(root)); set.start(); animator = set; } else if (mRingerMode == AudioManager.RINGER_MODE_SILENT) { descRes = R.string.silent_c; } } streamName.setText(descRes); updateIcon(resources); // If we've reached the max volume_3, and we weren't there already, pulse! if (!hasPulsed && max == volume) { animator = pulse(); animator.start(); } show(); } public void setHeadset(int iconRes) { headset.setImageResource(iconRes); } public void setHeadsetVisibility(int visibility) { headset.setVisibility(visibility); divider.setVisibility(visibility); } @Override public boolean isInteractive() { return true; } @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.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT, 0, 0, WindowManager.LayoutParams.TYPE_SYSTEM_ERROR, flags, PixelFormat.TRANSLUCENT); WPARAMS.windowAnimations = android.R.style.Animation_Dialog; WPARAMS.packageName = getContext().getPackageName(); WPARAMS.setTitle(getName()); WPARAMS.gravity = Gravity.CENTER; WPARAMS.rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_JUMPCUT; WPARAMS.screenBrightness = WPARAMS.buttonBrightness = WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE; return WPARAMS; } }