Gallery2: Add audio output switcher between headset/speaker

There is no audio output switcher between headset/speaker.

Add the audio output switcher button to menu list in order to
let user use this function.

CRs-Fixed: 650867

Change-Id: I16f5fdc74a76ee55557ba87b0145e51165f0408a
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 679ca04..6136da9 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -1,4 +1,21 @@
 <?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (c) 2010-2014, The Linux Foundation. All rights reserved.
+     Not a Contribution.
+
+     Copyright (C) 2007 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
 
 <manifest android:versionCode="40030"
         android:versionName="1.1.40030"
@@ -19,6 +36,7 @@
     <uses-permission android:name="android.permission.GET_ACCOUNTS" />
     <uses-permission android:name="android.permission.INTERNET" />
     <uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
+    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
     <uses-permission android:name="android.permission.NFC" />
     <uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
     <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
diff --git a/res/values-zh-rCN/codeaurora_strings.xml b/res/values-zh-rCN/codeaurora_strings.xml
index b1b2dbe..d046f3f 100644
--- a/res/values-zh-rCN/codeaurora_strings.xml
+++ b/res/values-zh-rCN/codeaurora_strings.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-Copyright (c) 2013, The Linux Foundation. All rights reserved.
+Copyright (c) 2013-2014, 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
@@ -38,6 +38,9 @@
     <string name="loop">"循环"</string>
     <string name="single">"单次"</string>
     <string name="stereo">"立体声"</string>
+    <string name="speaker_on">扬声器 开</string>
+    <string name="speaker_off">扬声器 关</string>
+    <string name="speaker_need_headset">请先插入耳机!</string>
     <string name="single_track">"单声道"</string>
     <string name="media_detail">"媒体详情"</string>
     <string name="input_url">"输入 URL"</string>
diff --git a/res/values/codeaurora_strings.xml b/res/values/codeaurora_strings.xml
index 386d2b1..9413652 100644
--- a/res/values/codeaurora_strings.xml
+++ b/res/values/codeaurora_strings.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-Copyright (c) 2013, The Linux Foundation. All rights reserved.
+Copyright (c) 2013-2014, 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
@@ -38,6 +38,9 @@
     <string name="loop">"Loop"</string>
     <string name="single">"Single"</string>
     <string name="stereo">"Stereo"</string>
+    <string name="speaker_on">Speaker ON</string>
+    <string name="speaker_off">Speaker OFF</string>
+    <string name="speaker_need_headset">Please plug in headset first!</string>
     <string name="single_track">"Single track"</string>
     <string name="media_detail">"Media details"</string>
     <string name="input_url">"Input URL"</string>
diff --git a/src/org/codeaurora/gallery3d/video/ExtensionHelper.java b/src/org/codeaurora/gallery3d/video/ExtensionHelper.java
index 2ff61a4..b515610 100755
--- a/src/org/codeaurora/gallery3d/video/ExtensionHelper.java
+++ b/src/org/codeaurora/gallery3d/video/ExtensionHelper.java
@@ -1,3 +1,31 @@
+/*
+Copyright (c) 2013-2014, 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 org.codeaurora.gallery3d.video;
 
 import android.content.Context;
@@ -20,6 +48,7 @@
         boolean stereo = context.getResources().getBoolean(R.bool.stereo);
         boolean streaming = context.getResources().getBoolean(R.bool.streaming);
         boolean playlist = context.getResources().getBoolean(R.bool.playlist);
+        boolean speaker = context.getResources().getBoolean(R.bool.speaker);
 
         if (loop == true) {
             group.addHooker(new LoopVideoHooker()); // add it for common feature.
@@ -35,6 +64,9 @@
             group.addHooker(new MovieListHooker()); // add it for common feature.
             group.addHooker(new StepOptionSettingsHooker());
         }
+        if (speaker == true) {
+            group.addHooker(new SpeakerHooker());
+        }
         return group;
     }
 }
diff --git a/src/org/codeaurora/gallery3d/video/SpeakerHooker.java b/src/org/codeaurora/gallery3d/video/SpeakerHooker.java
new file mode 100644
index 0000000..9bf5348
--- /dev/null
+++ b/src/org/codeaurora/gallery3d/video/SpeakerHooker.java
@@ -0,0 +1,210 @@
+/*
+Copyright (c) 2014, 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 org.codeaurora.gallery3d.video;
+
+import android.view.Menu;
+import android.view.MenuItem;
+import android.widget.Toast;
+
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothDevice;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.media.AudioManager;
+import android.media.AudioSystem;
+import android.os.Bundle;
+
+import com.android.gallery3d.R;
+
+public class SpeakerHooker extends MovieHooker {
+
+    private static final int MENU_SPEAKER = 1;
+
+    private AudioManager mAudioManager;
+
+    private MenuItem mMenuSpeakerButton;
+
+    private boolean mIsHeadsetOn = false;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        initAudioManager();
+    }
+
+    private void initAudioManager() {
+        if (mAudioManager == null) {
+            mAudioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
+        }
+    }
+
+    @Override
+    public void onDestroy() {
+        turnSpeakerOff();
+        super.onDestroy();
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        registerHeadSetReceiver();
+    }
+
+    private void registerHeadSetReceiver() {
+        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);
+        getContext().registerReceiver(mHeadSetReceiver, intentFilter);
+    }
+
+    @Override
+    public void onPause() {
+        unregisterHeadSetReceiver();
+        super.onPause();
+    }
+
+    private void unregisterHeadSetReceiver() {
+        try {
+            getContext().unregisterReceiver(mHeadSetReceiver);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    private final BroadcastReceiver mHeadSetReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final String action = intent.getAction();
+            if (mAudioManager == null) {
+                initAudioManager();
+            }
+            if (action.equals(Intent.ACTION_HEADSET_PLUG)) {
+                mIsHeadsetOn = (intent.getIntExtra("state", 0) == 1)
+                        || mAudioManager.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)
+                            || mAudioManager.isWiredHeadsetOn();
+                }
+            } else if (action.equals(AudioManager.ACTION_AUDIO_BECOMING_NOISY)) {
+                mIsHeadsetOn = false;
+            }
+            updateSpeakerButton();
+            if (!mIsHeadsetOn) {
+                turnSpeakerOff();
+            }
+        }
+    };
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        super.onCreateOptionsMenu(menu);
+        mMenuSpeakerButton = menu.add(0, getMenuActivityId(MENU_SPEAKER), 0, R.string.speaker_on);
+        return true;
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        super.onOptionsItemSelected(item);
+        switch (getMenuOriginalId(item.getItemId())) {
+            case MENU_SPEAKER:
+                changeSpeakerState();
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    private void changeSpeakerState() {
+        if (isSpeakerOn()) {
+            turnSpeakerOff();
+        } else {
+            if (mIsHeadsetOn) {
+                turnSpeakerOn();
+            } else {
+                Toast.makeText(getContext(), getContext().getString(R.string.speaker_need_headset),
+                        Toast.LENGTH_SHORT).show();
+            }
+        }
+        updateSpeakerButton();
+    }
+
+    private void turnSpeakerOn() {
+        if (mAudioManager == null) {
+            initAudioManager();
+        }
+        mAudioManager.setSpeakerphoneOn(true);
+        AudioSystem.setForceUse(AudioSystem.FOR_MEDIA,
+                AudioSystem.FORCE_SPEAKER);
+    }
+
+    private void turnSpeakerOff() {
+        if (mAudioManager == null) {
+            initAudioManager();
+        }
+        AudioSystem.setForceUse(AudioSystem.FOR_MEDIA,
+                AudioSystem.FORCE_NONE);
+        mAudioManager.setSpeakerphoneOn(false);
+    }
+
+    @Override
+    public boolean onPrepareOptionsMenu(Menu menu) {
+        super.onPrepareOptionsMenu(menu);
+        updateSpeakerButton();
+        return true;
+    }
+
+    private void updateSpeakerButton() {
+        if (mMenuSpeakerButton != null) {
+            if (isSpeakerOn()) {
+                mMenuSpeakerButton.setTitle(R.string.speaker_off);
+            } else {
+                mMenuSpeakerButton.setTitle(R.string.speaker_on);
+            }
+        }
+    }
+
+    private boolean isSpeakerOn() {
+        boolean isSpeakerOn = false;
+        if (mAudioManager == null) {
+            initAudioManager();
+        }
+        isSpeakerOn = mAudioManager.isSpeakerphoneOn();
+        return isSpeakerOn;
+    }
+
+}