summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorLikai Ding <likaid@codeaurora.org>2013-07-22 10:36:14 +0800
committercretin45 <cretin45@gmail.com>2014-12-15 14:38:27 -0800
commitbc669ec5d0f624efd4cd034ed088fe73a3c97f97 (patch)
treef467f9c934d6382caca58891e674298d5314d5c7 /src
parentf4df1e6ada8955167154602a26fedb30726e87ec (diff)
downloadandroid_packages_apps_Gallery2-bc669ec5d0f624efd4cd034ed088fe73a3c97f97.tar.gz
android_packages_apps_Gallery2-bc669ec5d0f624efd4cd034ed088fe73a3c97f97.tar.bz2
android_packages_apps_Gallery2-bc669ec5d0f624efd4cd034ed088fe73a3c97f97.zip
Gallery2: support audio effects during video play.
Porting from kk with fixes Conflicts: res/values/strings.xml Change-Id: I267c3c7e402fd5cf2bb223547003af491dd8289e Signed-off-by: Xiaojing Zhang <zhangx@codeaurora.org>
Diffstat (limited to 'src')
-rw-r--r--src/com/android/gallery3d/app/MovieActivity.java234
-rw-r--r--src/com/android/gallery3d/app/MoviePlayer.java8
-rw-r--r--src/com/android/gallery3d/ui/Knob.java337
3 files changed, 579 insertions, 0 deletions
diff --git a/src/com/android/gallery3d/app/MovieActivity.java b/src/com/android/gallery3d/app/MovieActivity.java
index 1547f6faf..30b12243f 100644
--- a/src/com/android/gallery3d/app/MovieActivity.java
+++ b/src/com/android/gallery3d/app/MovieActivity.java
@@ -19,30 +19,49 @@ package com.android.gallery3d.app;
import android.annotation.TargetApi;
import android.app.ActionBar;
import android.app.Activity;
+import android.app.AlertDialog;
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothDevice;
import android.content.AsyncQueryHandler;
+import android.content.BroadcastReceiver;
import android.content.ContentResolver;
+import android.content.Context;
+import android.content.DialogInterface;
import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
import android.content.pm.ActivityInfo;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.media.AudioManager;
+import android.media.audiofx.AudioEffect;
+import android.media.audiofx.AudioEffect.Descriptor;
+import android.media.audiofx.BassBoost;
+import android.media.audiofx.Virtualizer;
+import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.MediaStore;
import android.provider.OpenableColumns;
+import android.view.Gravity;
import android.view.KeyEvent;
+import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
+import android.widget.CompoundButton;
import android.widget.ShareActionProvider;
+import android.widget.Switch;
+import android.widget.Toast;
import com.android.gallery3d.R;
import com.android.gallery3d.common.ApiHelper;
import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.ui.Knob;
/**
* This activity plays a video from a specified URI.
@@ -62,6 +81,56 @@ public class MovieActivity extends Activity {
private Uri mUri;
private boolean mTreatUpAsBack;
+ private static final short BASSBOOST_MAX_STRENGTH = 1000;
+ private static final short VIRTUALIZER_MAX_STRENGTH = 1000;
+
+ private boolean mIsHeadsetOn = false;
+ private boolean mVirtualizerSupported = false;
+ private boolean mBassBoostSupported = false;
+
+ private SharedPreferences mPrefs;
+ static enum Key {
+ global_enabled, bb_strength, virt_strength
+ };
+
+ private BassBoost mBassBoostEffect;
+ private Virtualizer mVirtualizerEffect;
+ private AlertDialog mEffectDialog;
+ private Switch mSwitch;
+ private Knob mBassBoostKnob;
+ private Knob mVirtualizerKnob;
+
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(final Context context, final Intent intent) {
+ final String action = intent.getAction();
+ final AudioManager audioManager =
+ (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+ if (action.equals(Intent.ACTION_HEADSET_PLUG)) {
+ mIsHeadsetOn = (intent.getIntExtra("state", 0) == 1)
+ || audioManager.isBluetoothA2dpOn();
+ } else if (action.equals(BluetoothDevice.ACTION_ACL_CONNECTED)
+ || action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) {
+ final int deviceClass = ((BluetoothDevice)
+ intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE))
+ .getBluetoothClass().getDeviceClass();
+ if ((deviceClass == BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES)
+ || (deviceClass == BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET)) {
+ mIsHeadsetOn = action.equals(BluetoothDevice.ACTION_ACL_CONNECTED)
+ || audioManager.isWiredHeadsetOn();
+ }
+ } else if (action.equals(AudioManager.ACTION_AUDIO_BECOMING_NOISY)) {
+ mIsHeadsetOn = audioManager.isBluetoothA2dpOn() || audioManager.isWiredHeadsetOn();
+ }
+ if (mEffectDialog != null) {
+ if (!mIsHeadsetOn && mEffectDialog.isShowing()) {
+ mEffectDialog.dismiss();
+ showHeadsetPlugToast();
+ }
+ }
+ }
+ };
+
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
private void setSystemUiVisibility(View rootView) {
if (ApiHelper.HAS_VIEW_SYSTEM_UI_FLAG_LAYOUT_STABLE) {
@@ -114,6 +183,46 @@ public class MovieActivity extends Activity {
// We set the background in the theme to have the launching animation.
// But for the performance (and battery), we remove the background here.
win.setBackgroundDrawable(null);
+
+ // Determine available/supported effects
+ final Descriptor[] effects = AudioEffect.queryEffects();
+ for (final Descriptor effect : effects) {
+ if (effect.type.equals(AudioEffect.EFFECT_TYPE_VIRTUALIZER)) {
+ mVirtualizerSupported = true;
+ } else if (effect.type.equals(AudioEffect.EFFECT_TYPE_BASS_BOOST)) {
+ mBassBoostSupported = true;
+ }
+ }
+
+ mPrefs = getSharedPreferences(getApplicationContext().getPackageName(),
+ Context.MODE_PRIVATE);
+
+ mPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
+ @Override
+ public void onPrepared(MediaPlayer mp) {
+ int sessionId = mp.getAudioSessionId();
+ if (mBassBoostSupported) {
+ mBassBoostEffect = new BassBoost(0, sessionId);
+ }
+ if (mVirtualizerSupported) {
+ mVirtualizerEffect = new Virtualizer(0, sessionId);
+ }
+ if (mIsHeadsetOn) {
+ if (mPrefs.getBoolean(Key.global_enabled.toString(), false)) {
+ if (mBassBoostSupported) {
+ mBassBoostEffect.setStrength((short)
+ mPrefs.getInt(Key.bb_strength.toString(), 0));
+ mBassBoostEffect.setEnabled(true);
+ }
+ if (mVirtualizerSupported) {
+ mVirtualizerEffect.setStrength((short)
+ mPrefs.getInt(Key.virt_strength.toString(), 0));
+ mVirtualizerEffect.setEnabled(true);
+ }
+ }
+ }
+ }
+ });
}
private void setActionBarLogoFromIntent(Intent intent) {
@@ -181,9 +290,107 @@ public class MovieActivity extends Activity {
} else {
shareItem.setVisible(false);
}
+
+ final MenuItem mi = menu.add(R.string.audio_effects);
+ mi.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ onAudioEffectsMenuItemClick();
+ return true;
+ }
+ });
return true;
}
+ private void onAudioEffectsMenuItemClick() {
+ if (!mIsHeadsetOn) {
+ showHeadsetPlugToast();
+ } else {
+ LayoutInflater factory = LayoutInflater.from(this);
+ final View content = factory.inflate(R.layout.audio_effects_dialog, null);
+
+ boolean enabled = mPrefs.getBoolean(Key.global_enabled.toString(), false);
+
+ mSwitch = (Switch) content.findViewById(R.id.audio_effects_switch);
+ mSwitch.setChecked(enabled);
+ mSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ mBassBoostEffect.setEnabled(isChecked);
+ mBassBoostEffect.setStrength((short)
+ mPrefs.getInt(Key.bb_strength.toString(), 0));
+ mVirtualizerEffect.setEnabled(isChecked);
+ mVirtualizerEffect.setStrength((short)
+ mPrefs.getInt(Key.virt_strength.toString(), 0));
+ mBassBoostKnob.setEnabled(isChecked);
+ mVirtualizerKnob.setEnabled(isChecked);
+ }
+ });
+
+ mBassBoostKnob = (Knob) content.findViewById(R.id.bBStrengthKnob);
+ mBassBoostKnob.setEnabled(enabled);
+ mBassBoostKnob.setMax(BASSBOOST_MAX_STRENGTH);
+ mBassBoostKnob.setValue(mPrefs.getInt(Key.bb_strength.toString(), 0));
+ mBassBoostKnob.setOnKnobChangeListener(new Knob.OnKnobChangeListener() {
+ @Override
+ public void onValueChanged(Knob knob, int value, boolean fromUser) {
+ mBassBoostEffect.setStrength((short) value);
+ }
+
+ @Override
+ public boolean onSwitchChanged(Knob knob, boolean enabled) {
+ return false;
+ }
+ });
+
+ mVirtualizerKnob = (Knob) content.findViewById(R.id.vIStrengthKnob);
+ mVirtualizerKnob.setEnabled(enabled);
+ mVirtualizerKnob.setMax(VIRTUALIZER_MAX_STRENGTH);
+ mVirtualizerKnob.setValue(mPrefs.getInt(Key.virt_strength.toString(), 0));
+ mVirtualizerKnob.setOnKnobChangeListener(new Knob.OnKnobChangeListener() {
+ @Override
+ public void onValueChanged(Knob knob, int value, boolean fromUser) {
+ mVirtualizerEffect.setStrength((short) value);
+ }
+
+ @Override
+ public boolean onSwitchChanged(Knob knob, boolean enabled) {
+ return false;
+ }
+ });
+
+ mEffectDialog = new AlertDialog.Builder(MovieActivity.this,
+ AlertDialog.THEME_HOLO_DARK)
+ .setTitle(R.string.audio_effects_dialog_title)
+ .setView(content)
+ .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ SharedPreferences.Editor editor = mPrefs.edit();
+ editor.putBoolean(Key.global_enabled.toString(), mSwitch.isChecked());
+ editor.putInt(Key.bb_strength.toString(), mBassBoostKnob.getValue());
+ editor.putInt(Key.virt_strength.toString(),
+ mVirtualizerKnob.getValue());
+ editor.commit();
+ }
+ })
+ .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ boolean enabled = mPrefs.getBoolean(Key.global_enabled.toString(), false);
+ mBassBoostEffect.setStrength((short)
+ mPrefs.getInt(Key.bb_strength.toString(), 0));
+ mBassBoostEffect.setEnabled(enabled);
+ mVirtualizerEffect.setStrength((short)
+ mPrefs.getInt(Key.virt_strength.toString(), 0));
+ mVirtualizerEffect.setEnabled(enabled);
+ }
+ })
+ .create();
+ mEffectDialog.show();
+ }
+ }
+
private Intent createShareIntent() {
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("video/*");
@@ -210,6 +417,13 @@ public class MovieActivity extends Activity {
return false;
}
+ public void showHeadsetPlugToast() {
+ final Toast toast = Toast.makeText(getApplicationContext(), R.string.headset_plug,
+ Toast.LENGTH_LONG);
+ toast.setGravity(Gravity.CENTER, toast.getXOffset() / 2, toast.getYOffset() / 2);
+ toast.show();
+ }
+
@Override
public void onStart() {
((AudioManager) getSystemService(AUDIO_SERVICE))
@@ -228,12 +442,24 @@ public class MovieActivity extends Activity {
@Override
public void onPause() {
mPlayer.onPause();
+ try {
+ unregisterReceiver(mReceiver);
+ } catch (IllegalArgumentException e) {
+ // Do nothing
+ }
super.onPause();
}
@Override
public void onResume() {
mPlayer.onResume();
+ if ((mVirtualizerSupported) || (mBassBoostSupported)) {
+ final IntentFilter intentFilter = new IntentFilter(Intent.ACTION_HEADSET_PLUG);
+ intentFilter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
+ intentFilter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
+ intentFilter.addAction(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
+ registerReceiver(mReceiver, intentFilter);
+ }
super.onResume();
}
@@ -246,6 +472,14 @@ public class MovieActivity extends Activity {
@Override
public void onDestroy() {
mPlayer.onDestroy();
+ if (mBassBoostEffect != null) {
+ mBassBoostEffect.setEnabled(false);
+ mBassBoostEffect.release();
+ }
+ if (mVirtualizerEffect != null) {
+ mVirtualizerEffect.setEnabled(false);
+ mVirtualizerEffect.release();
+ }
super.onDestroy();
}
diff --git a/src/com/android/gallery3d/app/MoviePlayer.java b/src/com/android/gallery3d/app/MoviePlayer.java
index f6bd36725..962afeafb 100644
--- a/src/com/android/gallery3d/app/MoviePlayer.java
+++ b/src/com/android/gallery3d/app/MoviePlayer.java
@@ -476,6 +476,14 @@ public class MoviePlayer implements
if (mVideoView.isPlaying()) pauseVideo();
}
}
+
+ public int getAudioSessionId() {
+ return mVideoView.getAudioSessionId();
+ }
+
+ public void setOnPreparedListener(MediaPlayer.OnPreparedListener listener) {
+ mVideoView.setOnPreparedListener(listener);
+ }
}
class Bookmarker {
diff --git a/src/com/android/gallery3d/ui/Knob.java b/src/com/android/gallery3d/ui/Knob.java
new file mode 100644
index 000000000..179023e02
--- /dev/null
+++ b/src/com/android/gallery3d/ui/Knob.java
@@ -0,0 +1,337 @@
+/*
+ * Copyright (c) 2013, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of The Linux Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.android.gallery3d.ui;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.RectF;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import java.lang.Math;
+
+import com.android.gallery3d.R;
+
+public class Knob extends FrameLayout {
+ private static final int STROKE_WIDTH = 6;
+ private static final float TEXT_SIZE = 0.20f;
+ private static final float TEXT_PADDING = 0.31f;
+ private static final float LABEL_PADDING = 0.05f;
+ private static final float LABEL_SIZE = 0.09f;
+ private static final float LABEL_WIDTH = 0.80f;
+ private static final float INDICATOR_RADIUS = 0.38f;
+
+ public interface OnKnobChangeListener {
+ void onValueChanged(Knob knob, int value, boolean fromUser);
+ boolean onSwitchChanged(Knob knob, boolean on);
+ }
+
+ private OnKnobChangeListener mOnKnobChangeListener = null;
+
+ private float mProgress = 0.0f;
+ private int mMax = 100;
+ private boolean mOn = false;
+ private boolean mEnabled = false;
+
+ private int mHighlightColor;
+ private int mLowlightColor;
+ private int mDisabledColor;
+
+ private final Paint mPaint;
+
+ private final TextView mLabelTV;
+ private final TextView mProgressTV;
+
+ private final ImageView mKnobOn;
+ private final ImageView mKnobOff;
+
+ private float mLastX;
+ private float mLastY;
+ private boolean mMoved;
+
+ private int mWidth = 0;
+ private int mIndicatorWidth = 0;
+
+ private RectF mRectF;
+
+ public Knob(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Knob, 0, 0);
+
+ String label;
+ int foreground;
+ try {
+ label = a.getString(R.styleable.Knob_label);
+ foreground = a.getResourceId(R.styleable.Knob_foreground, R.drawable.knob);
+ } finally {
+ a.recycle();
+ }
+
+ LayoutInflater li = (LayoutInflater)
+ context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ li.inflate(R.layout.knob, this, true);
+
+ Resources res = getResources();
+ mHighlightColor = res.getColor(R.color.highlight);
+ mLowlightColor = res.getColor(R.color.lowlight);
+ mDisabledColor = res.getColor(R.color.disabled_knob);
+
+ ((ImageView) findViewById(R.id.knob_foreground)).setImageResource(foreground);
+
+ mLabelTV = (TextView) findViewById(R.id.knob_label);
+ mLabelTV.setText(label);
+ mProgressTV = (TextView) findViewById(R.id.knob_value);
+
+ mKnobOn = (ImageView) findViewById(R.id.knob_toggle_on);
+ mKnobOff = (ImageView) findViewById(R.id.knob_toggle_off);
+
+ mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ mPaint.setColor(mHighlightColor);
+ mPaint.setStrokeWidth(STROKE_WIDTH);
+ mPaint.setStrokeCap(Paint.Cap.ROUND);
+ mPaint.setStyle(Paint.Style.STROKE);
+
+ setWillNotDraw(false);
+ }
+
+ public Knob(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public Knob(Context context) {
+ this(context, null);
+ }
+
+ public void setOnKnobChangeListener(OnKnobChangeListener l) {
+ mOnKnobChangeListener = l;
+ }
+
+ public void setValue(int value) {
+ if (mMax != 0) {
+ setProgress(((float) value) / mMax);
+ }
+ }
+
+ public int getValue() {
+ return (int) (mProgress * mMax);
+ }
+
+ public void setProgress(float progress) {
+ setProgress(progress, false);
+ }
+
+ private void setProgressText(boolean on) {
+ if (on) {
+ mProgressTV.setText((int) (mProgress * 100) + "%");
+ } else {
+ mProgressTV.setText("--%");
+ }
+ }
+
+ private void setProgress(float progress, boolean fromUser) {
+ if (progress > 1.0f) {
+ progress = 1.0f;
+ }
+ if (progress < 0.0f) {
+ progress = 0.0f;
+ }
+ mProgress = progress;
+ setProgressText(mOn && mEnabled);
+
+ invalidate();
+
+ if (mOnKnobChangeListener != null) {
+ mOnKnobChangeListener.onValueChanged(this, (int) (progress * mMax), fromUser);
+ }
+ }
+
+ public void setMax(int max) {
+ mMax = max;
+ }
+
+ public float getProgress() {
+ return mProgress;
+ }
+
+ private void drawIndicator() {
+ float r = mWidth * INDICATOR_RADIUS;
+ ImageView view = mOn ? mKnobOn : mKnobOff;
+ view.setTranslationX((float) Math.sin(mProgress * 2 * Math.PI) * r - mIndicatorWidth / 2);
+ view.setTranslationY((float) -Math.cos(mProgress * 2 * Math.PI) * r - mIndicatorWidth / 2);
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ mEnabled = enabled;
+ setOn(enabled);
+ }
+
+ public void setOn(boolean on) {
+ if (on != mOn) {
+ mOn = on;
+ }
+ on = on && mEnabled;
+ mLabelTV.setTextColor(on ? mHighlightColor : mDisabledColor);
+ mProgressTV.setTextColor(on ? mHighlightColor : mDisabledColor);
+ setProgressText(on);
+ mPaint.setColor(on ? mHighlightColor : mDisabledColor);
+ mKnobOn.setVisibility(on ? View.VISIBLE : View.GONE);
+ mKnobOff.setVisibility(on ? View.GONE : View.VISIBLE);
+ invalidate();
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ drawIndicator();
+ if (mOn && mEnabled) {
+ canvas.drawArc(mRectF, -90, mProgress * 360, false, mPaint);
+ }
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldW, int oldH) {
+ int size = w > h ? h : w;
+ mWidth = size;
+ mIndicatorWidth = mKnobOn.getWidth();
+
+ int diff;
+ if (w > h) {
+ diff = (w - h) / 2;
+ mRectF = new RectF(STROKE_WIDTH + diff, STROKE_WIDTH,
+ w - STROKE_WIDTH - diff, h - STROKE_WIDTH);
+ } else {
+ diff = (h - w) / 2;
+ mRectF = new RectF(STROKE_WIDTH, STROKE_WIDTH + diff,
+ w - STROKE_WIDTH, h - STROKE_WIDTH - diff);
+ }
+
+ mProgressTV.setTextSize(TypedValue.COMPLEX_UNIT_PX, size * TEXT_SIZE);
+ mProgressTV.setPadding(0, (int) (size * TEXT_PADDING), 0, 0);
+ mProgressTV.setVisibility(View.VISIBLE);
+ mLabelTV.setTextSize(TypedValue.COMPLEX_UNIT_PX, size * LABEL_SIZE);
+ mLabelTV.setPadding(0, (int) (size * LABEL_PADDING), 0, 0);
+ mLabelTV.setLayoutParams(new LinearLayout.LayoutParams((int) (w * LABEL_WIDTH),
+ LayoutParams.WRAP_CONTENT));
+ mLabelTV.setVisibility(View.VISIBLE);
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ return true;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ if (mOn) {
+ mLastX = event.getX();
+ mLastY = event.getY();
+ getParent().requestDisallowInterceptTouchEvent(true);
+ }
+ break;
+ case MotionEvent.ACTION_MOVE:
+ if (mOn) {
+ float x = event.getX();
+ float y = event.getY();
+ float center = mWidth / 2;
+ if (mMoved || (x - center) * (x - center) + (y - center) * (y - center)
+ > center * center / 4) {
+ float delta = getDelta(x, y);
+ setProgress(mProgress + delta / 360, true);
+ mMoved = true;
+ }
+ mLastX = x;
+ mLastY = y;
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+ if (!mMoved) {
+ if (mOnKnobChangeListener == null
+ || mOnKnobChangeListener.onSwitchChanged(this, !mOn)) {
+ if (mEnabled) {
+ setOn(!mOn);
+ invalidate();
+ }
+ }
+ }
+ mMoved = false;
+ break;
+ default:
+ break;
+ }
+ return true;
+ }
+
+ private float getDelta(float x, float y) {
+ float angle = angle(x, y);
+ float oldAngle = angle(mLastX, mLastY);
+ float delta = angle - oldAngle;
+ if (delta >= 180.0f) {
+ delta = -oldAngle;
+ } else if (delta <= -180.0f) {
+ delta = 360 - oldAngle;
+ }
+ return delta;
+ }
+
+ private float angle(float x, float y) {
+ float center = mWidth / 2.0f;
+ x -= center;
+ y -= center;
+
+ if (x == 0.0f) {
+ if (y > 0.0f) {
+ return 180.0f;
+ } else {
+ return 0.0f;
+ }
+ }
+
+ float angle = (float) (Math.atan(y / x) / Math.PI * 180.0);
+ if (x > 0.0f) {
+ angle += 90;
+ } else {
+ angle += 270;
+ }
+ return angle;
+ }
+}