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>
diff --git a/res/drawable-hdpi/knob.png b/res/drawable-hdpi/knob.png
new file mode 100644
index 0000000..a6871ad
--- /dev/null
+++ b/res/drawable-hdpi/knob.png
Binary files differ
diff --git a/res/drawable-hdpi/knob_bg.png b/res/drawable-hdpi/knob_bg.png
new file mode 100644
index 0000000..3423509
--- /dev/null
+++ b/res/drawable-hdpi/knob_bg.png
Binary files differ
diff --git a/res/drawable-hdpi/knob_toggle_off.png b/res/drawable-hdpi/knob_toggle_off.png
new file mode 100644
index 0000000..a89595e
--- /dev/null
+++ b/res/drawable-hdpi/knob_toggle_off.png
Binary files differ
diff --git a/res/drawable-hdpi/knob_toggle_on.png b/res/drawable-hdpi/knob_toggle_on.png
new file mode 100644
index 0000000..ab8d3d7
--- /dev/null
+++ b/res/drawable-hdpi/knob_toggle_on.png
Binary files differ
diff --git a/res/layout/audio_effects_dialog.xml b/res/layout/audio_effects_dialog.xml
new file mode 100644
index 0000000..83892c2
--- /dev/null
+++ b/res/layout/audio_effects_dialog.xml
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:custom="http://schemas.android.com/apk/res/com.android.gallery3d"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:gravity="center">
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:gravity="center_horizontal"
+ android:paddingBottom="10dp">
+
+ <Switch
+ android:id="@+id/audio_effects_switch"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/audio_effects"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:layout_marginTop="10dp"
+ android:layout_marginLeft="25dp"
+ android:layout_marginRight="30dp" />
+
+ <LinearLayout
+ android:id="@+id/aEffectsPanel"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:layout_marginTop="5dp"
+ android:layout_marginBottom="10dp"
+ android:gravity="center_vertical">
+
+ <com.android.gallery3d.ui.Knob
+ android:id="@+id/bBStrengthKnob"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ custom:label="@string/bass_boost_strength"
+ custom:background="@drawable/knob_bg"
+ custom:foreground="@drawable/knob" />
+
+ <com.android.gallery3d.ui.Knob
+ android:id="@+id/vIStrengthKnob"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ custom:label="@string/virtualizer_strength"
+ custom:background="@drawable/knob_bg"
+ custom:foreground="@drawable/knob" />
+
+ </LinearLayout>
+ </LinearLayout>
+</LinearLayout>
diff --git a/res/layout/knob.xml b/res/layout/knob.xml
new file mode 100644
index 0000000..41c982e
--- /dev/null
+++ b/res/layout/knob.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+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.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+
+ <ImageView
+ android:id="@+id/knob_foreground"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+ <ImageView
+ android:id="@+id/knob_toggle_on"
+ android:layout_gravity="center_horizontal|center_vertical"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/knob_toggle_on"
+ android:visibility="gone" />
+ <ImageView
+ android:id="@+id/knob_toggle_off"
+ android:layout_gravity="center_horizontal|center_vertical"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/knob_toggle_off"
+ android:visibility="gone" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center_horizontal"
+ android:orientation="vertical"
+ android:gravity="center_horizontal">
+
+ <TextView
+ android:id="@+id/knob_value"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:visibility="gone" />
+ <TextView
+ android:id="@+id/knob_label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:ellipsize="marquee"
+ android:visibility="gone" />
+ </LinearLayout>
+
+</FrameLayout>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index 91a6358..91dc8ef 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -436,4 +436,8 @@
<item quantity="one" msgid="6949174783125614798">"%1$d张照片"</item>
<item quantity="other" msgid="3813306834113858135">"%1$d张照片"</item>
</plurals>
+
+ <string name="bass_boost_strength" msgid="882301530007752270">"低音增强"</string>
+ <string name="virtualizer_strength" msgid="5035111173763913313">"3D 音效"</string>
+ <string name="audio_effects" msgid="612896145300512593">"音效"</string>
</resources>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index 88fa970..edb7f69 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -436,4 +436,8 @@
<item quantity="one" msgid="6949174783125614798">"%1$d 張相片"</item>
<item quantity="other" msgid="3813306834113858135">"%1$d 張相片"</item>
</plurals>
+
+ <string name="bass_boost_strength">"低音加強"</string>
+ <string name="virtualizer_strength">"3D 效果"</string>
+ <string name="audio_effects">"音效"</string>
</resources>
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 5a00a69..eadd4f2 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -42,5 +42,9 @@
<attr name="largeIcons" format="reference" />
<attr name="images" format="reference" />
</declare-styleable>
-
+ <declare-styleable name="Knob">
+ <attr name="label" format="string" />
+ <attr name="background" format="integer" />
+ <attr name="foreground" format="integer" />
+ </declare-styleable>
</resources>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 4fe9180..1f1d6ec 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -70,4 +70,8 @@
<color name="face_detect_fail">#80d05060</color>
<color name="gray">#FFAAAAAA</color>
+ <color name="highlight">#00a8ff</color>
+ <color name="lowlight">#00527c</color>
+ <color name="grey">#999999</color>
+ <color name="disabled_knob">#505050</color>
</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index e19f950..bb9bf18 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1119,6 +1119,21 @@
<item quantity="other">%1$d photos</item>
</plurals>
+ <!-- The label for the bass boost knob of the audio effects dialog. -->
+ <string name="bass_boost_strength">Bass boost</string>
+
+ <!-- The label for the 3d effect knob of the audio effects dialog. -->
+ <string name="virtualizer_strength">3D effect</string>
+
+ <!-- The label for the audio effects menu. -->
+ <string name="audio_effects">Audio effects</string>
+
+ <!-- Toast if user attempts to control audio effects without headphones plugged in. -->
+ <string name="headset_plug">Plug in headphones for these effects.</string>
+
+ <!-- The title of the audio effects dialog. -->
+ <string name="audio_effects_dialog_title">Snapdragon Audio+</string>
+
<!-- The tips of trimming video -->
<string name="fail_trim">Sorry, this video file can not be trimmed</string>
</resources>
diff --git a/src/com/android/gallery3d/app/MovieActivity.java b/src/com/android/gallery3d/app/MovieActivity.java
index 1547f6f..30b1224 100644
--- a/src/com/android/gallery3d/app/MovieActivity.java
+++ b/src/com/android/gallery3d/app/MovieActivity.java
@@ -19,30 +19,49 @@
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 @@
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 @@
// 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 @@
} 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 @@
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 @@
@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 @@
@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 f6bd367..962afea 100644
--- a/src/com/android/gallery3d/app/MoviePlayer.java
+++ b/src/com/android/gallery3d/app/MoviePlayer.java
@@ -476,6 +476,14 @@
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 0000000..179023e
--- /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;
+ }
+}