diff options
author | Likai Ding <likaid@codeaurora.org> | 2013-07-22 10:36:14 +0800 |
---|---|---|
committer | Xiaojing Zhang <zhangx@codeaurora.org> | 2014-11-04 20:37:44 -0800 |
commit | 5167fdf5b42e471a51a3bc44ca1a5adb69e676e2 (patch) | |
tree | 21aed1e0b793b3093a9f8f1a13d167d3d4ec2113 /src/com/android/gallery3d | |
parent | 65b4d0c1ded85105802bf69ddb5646eb72839e17 (diff) | |
download | android_packages_apps_Gallery2-5167fdf5b42e471a51a3bc44ca1a5adb69e676e2.tar.gz android_packages_apps_Gallery2-5167fdf5b42e471a51a3bc44ca1a5adb69e676e2.tar.bz2 android_packages_apps_Gallery2-5167fdf5b42e471a51a3bc44ca1a5adb69e676e2.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/com/android/gallery3d')
-rw-r--r-- | src/com/android/gallery3d/app/MovieActivity.java | 234 | ||||
-rw-r--r-- | src/com/android/gallery3d/app/MoviePlayer.java | 8 | ||||
-rw-r--r-- | src/com/android/gallery3d/ui/Knob.java | 337 |
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; + } +} |