Update RandomMusicPlayer sample for new RemoteControlClient APIs, also add media button support

Change-Id: Ia6ad08dd0ec1e67f1cffa2d6cfca2120ee0a96c7
This commit is contained in:
Roman Nurik
2011-09-30 18:02:10 -07:00
parent 8fbf284cc8
commit 5986e12034
61 changed files with 1075 additions and 523 deletions

View File

@@ -0,0 +1,16 @@
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := samples
# Only compile source java files in this apk.
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_PACKAGE_NAME := RandomMusicPlayer
LOCAL_SDK_VERSION := current
include $(BUILD_PACKAGE)
# Use the following include to make our test apk.
include $(call all-makefiles-under,$(LOCAL_PATH))

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2011 The Android Open Source Project
<!--
Copyright (C) 2011 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.
@@ -11,19 +11,23 @@
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. -->
limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.android.musicplayer"
android:versionCode="1"
android:versionName="1.0">
<uses-sdk android:minSdkVersion="7" android:targetSdkVersion="8" />
<uses-sdk android:minSdkVersion="7" android:targetSdkVersion="14" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<application android:icon="@drawable/ic_launcher" android:label="Random Music Player">
<application android:icon="@drawable/ic_launcher" android:label="@string/app_title">
<activity android:name=".MainActivity"
android:label="Random Music Player"
android:label="@string/app_title"
android:theme="@android:style/Theme.Black.NoTitleBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -33,6 +37,7 @@
<service android:exported="false" android:name=".MusicService">
<intent-filter>
<action android:name="com.example.android.musicplayer.action.TOGGLE_PLAYBACK" />
<action android:name="com.example.android.musicplayer.action.PLAY" />
<action android:name="com.example.android.musicplayer.action.PAUSE" />
<action android:name="com.example.android.musicplayer.action.SKIP" />
@@ -49,6 +54,10 @@
<intent-filter>
<action android:name="android.media.AUDIO_BECOMING_NOISY" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>
</application>
</manifest>

View File

@@ -1,11 +0,0 @@
# This file is automatically generated by Android Tools.
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
#
# This file must be checked in Version Control Systems.
#
# To customize properties used by the Ant build system use,
# "build.properties", and override values to adapt the script to your
# project structure.
# Project target.
target=android-8

Binary file not shown.

After

Width:  |  Height:  |  Size: 740 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 392 B

After

Width:  |  Height:  |  Size: 834 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2011 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. -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true"
android:drawable="@drawable/eject_pressed" /> <!-- pressed -->
<item android:state_focused="true"
android:drawable="@drawable/eject_pressed" /> <!-- focused -->
<item android:drawable="@drawable/eject" /> <!-- default -->
</selector>

View File

@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2011 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. -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true"
android:drawable="@drawable/ff_pressed" /> <!-- pressed -->
<item android:state_focused="true"
android:drawable="@drawable/ff_pressed" /> <!-- focused -->
<item android:drawable="@drawable/ff" /> <!-- default -->
</selector>

View File

@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2011 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. -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true"
android:drawable="@drawable/pause_pressed" /> <!-- pressed -->
<item android:state_focused="true"
android:drawable="@drawable/pause_pressed" /> <!-- focused -->
<item android:drawable="@drawable/pause" /> <!-- default -->
</selector>

View File

@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2011 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. -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true"
android:drawable="@drawable/play_pressed" /> <!-- pressed -->
<item android:state_focused="true"
android:drawable="@drawable/play_pressed" /> <!-- focused -->
<item android:drawable="@drawable/play" /> <!-- default -->
</selector>

View File

@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2011 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. -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true"
android:drawable="@drawable/rew_pressed" /> <!-- pressed -->
<item android:state_focused="true"
android:drawable="@drawable/rew_pressed" /> <!-- focused -->
<item android:drawable="@drawable/rew" /> <!-- default -->
</selector>

View File

@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2011 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. -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true"
android:drawable="@drawable/stop_pressed" /> <!-- pressed -->
<item android:state_focused="true"
android:drawable="@drawable/stop_pressed" /> <!-- focused -->
<item android:drawable="@drawable/stop" /> <!-- default -->
</selector>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 229 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 502 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 499 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 547 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 297 B

After

Width:  |  Height:  |  Size: 548 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 779 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

View File

@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2011 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. -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true"
android:drawable="@drawable/eject_pressed" /> <!-- pressed -->
<item android:state_focused="true"
android:drawable="@drawable/eject_pressed" /> <!-- focused -->
<item android:drawable="@drawable/eject" /> <!-- default -->
</selector>

View File

@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2011 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. -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true"
android:drawable="@drawable/ff_pressed" /> <!-- pressed -->
<item android:state_focused="true"
android:drawable="@drawable/ff_pressed" /> <!-- focused -->
<item android:drawable="@drawable/ff" /> <!-- default -->
</selector>

View File

@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2011 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. -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true"
android:drawable="@drawable/pause_pressed" /> <!-- pressed -->
<item android:state_focused="true"
android:drawable="@drawable/pause_pressed" /> <!-- focused -->
<item android:drawable="@drawable/pause" /> <!-- default -->
</selector>

View File

@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2011 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. -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true"
android:drawable="@drawable/play_pressed" /> <!-- pressed -->
<item android:state_focused="true"
android:drawable="@drawable/play_pressed" /> <!-- focused -->
<item android:drawable="@drawable/play" /> <!-- default -->
</selector>

View File

@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2011 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. -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true"
android:drawable="@drawable/rew_pressed" /> <!-- pressed -->
<item android:state_focused="true"
android:drawable="@drawable/rew_pressed" /> <!-- focused -->
<item android:drawable="@drawable/rew" /> <!-- default -->
</selector>

View File

@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2011 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. -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true"
android:drawable="@drawable/stop_pressed" /> <!-- pressed -->
<item android:state_focused="true"
android:drawable="@drawable/stop_pressed" /> <!-- focused -->
<item android:drawable="@drawable/stop" /> <!-- default -->
</selector>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 985 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -0,0 +1,21 @@
<!--
Copyright (C) 2011 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.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:drawable="@drawable/eject_pressed" />
<item android:state_focused="true" android:drawable="@drawable/eject_pressed" />
<item android:drawable="@drawable/eject" />
</selector>

View File

@@ -0,0 +1,21 @@
<!--
Copyright (C) 2011 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.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:drawable="@drawable/ff_pressed" />
<item android:state_focused="true" android:drawable="@drawable/ff_pressed" />
<item android:drawable="@drawable/ff" />
</selector>

View File

@@ -0,0 +1,21 @@
<!--
Copyright (C) 2011 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.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:drawable="@drawable/pause_pressed" />
<item android:state_focused="true" android:drawable="@drawable/pause_pressed" />
<item android:drawable="@drawable/pause" />
</selector>

View File

@@ -0,0 +1,21 @@
<!--
Copyright (C) 2011 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.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:drawable="@drawable/play_pressed" />
<item android:state_focused="true" android:drawable="@drawable/play_pressed" />
<item android:drawable="@drawable/play" />
</selector>

View File

@@ -0,0 +1,21 @@
<!--
Copyright (C) 2011 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.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:drawable="@drawable/rew_pressed" />
<item android:state_focused="true" android:drawable="@drawable/rew_pressed" />
<item android:drawable="@drawable/rew" />
</selector>

View File

@@ -0,0 +1,21 @@
<!--
Copyright (C) 2011 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.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:drawable="@drawable/stop_pressed" />
<item android:state_focused="true" android:drawable="@drawable/stop_pressed" />
<item android:drawable="@drawable/stop" />
</selector>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2011 The Android Open Source Project
<!--
Copyright (C) 2011 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.
@@ -11,73 +11,58 @@
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. -->
limitations under the License.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="center"
android:background="#000040"
>
android:background="#000040">
<TextView android:text="Random Music Player"
<TextView android:text="@string/app_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="20dp"
android:textColor="#ffffff"
android:textSize="20sp"
android:textStyle="bold"
/>
android:textStyle="bold" />
<LinearLayout
android:orientation="horizontal"
<LinearLayout android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
>
<Button
android:id="@+id/rewindbutton"
android:background="@drawable/selector_rew"
android:gravity="center">
<Button android:id="@+id/rewindbutton"
android:background="@drawable/btn_rew"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_margin="5dp"
/>
<Button
android:id="@+id/playbutton"
android:background="@drawable/selector_play"
android:layout_margin="5dp" />
<Button android:id="@+id/playbutton"
android:background="@drawable/btn_play"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_margin="5dp"
/>
<Button
android:id="@+id/pausebutton"
android:background="@drawable/selector_pause"
android:layout_margin="5dp" />
<Button android:id="@+id/pausebutton"
android:background="@drawable/btn_pause"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_margin="5dp"
/>
<Button
android:id="@+id/skipbutton"
android:background="@drawable/selector_ff"
android:layout_margin="5dp" />
<Button android:id="@+id/skipbutton"
android:background="@drawable/btn_ff"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_margin="5dp"
/>
<Button
android:id="@+id/stopbutton"
android:background="@drawable/selector_stop"
android:layout_margin="5dp" />
<Button android:id="@+id/stopbutton"
android:background="@drawable/btn_stop"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_margin="5dp"
/>
<Button
android:id="@+id/ejectbutton"
android:background="@drawable/selector_eject"
android:layout_margin="5dp" />
<Button android:id="@+id/ejectbutton"
android:background="@drawable/btn_eject"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_margin="5dp"
/>
android:layout_margin="5dp" />
</LinearLayout>
</LinearLayout>

View File

@@ -1,93 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2011 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. -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="center"
android:background="#000040"
>
<TextView android:text="Random Music Player"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="20dp"
android:textColor="#ffffff"
android:textSize="20sp"
android:textStyle="bold"
/>
<LinearLayout
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_margin="10dp"
>
<Button
android:id="@+id/rewindbutton"
android:background="@drawable/selector_rew"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_margin="5dp"
/>
<Button
android:id="@+id/playbutton"
android:background="@drawable/selector_play"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_margin="5dp"
/>
<Button
android:id="@+id/pausebutton"
android:background="@drawable/selector_pause"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_margin="5dp"
/>
<Button
android:id="@+id/skipbutton"
android:background="@drawable/selector_ff"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_margin="5dp"
/>
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_margin="10dp"
>
<Button
android:id="@+id/stopbutton"
android:background="@drawable/selector_stop"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_margin="5dp"
/>
<Button
android:id="@+id/ejectbutton"
android:background="@drawable/selector_eject"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_margin="5dp"
/>
</LinearLayout>
</LinearLayout>

View File

@@ -0,0 +1,76 @@
<!--
Copyright (C) 2011 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.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="center"
android:background="#000040">
<TextView android:text="@string/app_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="20dp"
android:textColor="#ffffff"
android:textSize="20sp"
android:textStyle="bold" />
<LinearLayout android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_margin="10dp">
<Button android:id="@+id/rewindbutton"
android:background="@drawable/btn_rew"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_margin="5dp" />
<Button android:id="@+id/playbutton"
android:background="@drawable/btn_play"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_margin="5dp" />
<Button android:id="@+id/pausebutton"
android:background="@drawable/btn_pause"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_margin="5dp" />
<Button android:id="@+id/skipbutton"
android:background="@drawable/btn_ff"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_margin="5dp" />
</LinearLayout>
<LinearLayout android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_margin="10dp">
<Button android:id="@+id/stopbutton"
android:background="@drawable/btn_stop"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_margin="5dp" />
<Button android:id="@+id/ejectbutton"
android:background="@drawable/btn_eject"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_margin="5dp" />
</LinearLayout>
</LinearLayout>

View File

@@ -0,0 +1,19 @@
<!--
Copyright (C) 2011 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.
-->
<resources>
<string name="app_title">Random Music Player</string>
</resources>

View File

@@ -51,7 +51,6 @@ public class AudioFocusHelper implements AudioManager.OnAudioFocusChangeListener
* Called by AudioManager on audio focus changes. We implement this by calling our
* MusicFocusable appropriately to relay the message.
*/
@Override
public void onAudioFocusChange(int focusChange) {
if (mFocusable == null) return;
switch (focusChange) {

View File

@@ -22,6 +22,7 @@ import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
@@ -71,7 +72,6 @@ public class MainActivity extends Activity implements OnClickListener {
mEjectButton.setOnClickListener(this);
}
@Override
public void onClick(View target) {
// Send the correct intent to the MusicService, according to the button that was clicked
if (target == mPlayButton)
@@ -119,4 +119,15 @@ public class MainActivity extends Activity implements OnClickListener {
alertBuilder.show();
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
case KeyEvent.KEYCODE_HEADSETHOOK:
startService(new Intent(MusicService.ACTION_TOGGLE_PLAYBACK));
return true;
}
return super.onKeyDown(keyCode, event);
}
}

View File

@@ -0,0 +1,85 @@
package com.example.android.musicplayer;
import android.content.ComponentName;
import android.media.AudioManager;
import android.util.Log;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* Class that assists with handling new media button APIs available in API level 8.
*/
public class MediaButtonHelper {
// Backwards compatibility code (methods available as of API Level 8)
private static final String TAG = "MediaButtonHelper";
static {
initializeStaticCompatMethods();
}
static Method sMethodRegisterMediaButtonEventReceiver;
static Method sMethodUnregisterMediaButtonEventReceiver;
static void initializeStaticCompatMethods() {
try {
sMethodRegisterMediaButtonEventReceiver = AudioManager.class.getMethod(
"registerMediaButtonEventReceiver",
new Class[] { ComponentName.class });
sMethodUnregisterMediaButtonEventReceiver = AudioManager.class.getMethod(
"unregisterMediaButtonEventReceiver",
new Class[] { ComponentName.class });
} catch (NoSuchMethodException e) {
// Silently fail when running on an OS before API level 8.
}
}
public static void registerMediaButtonEventReceiverCompat(AudioManager audioManager,
ComponentName receiver) {
if (sMethodRegisterMediaButtonEventReceiver == null)
return;
try {
sMethodRegisterMediaButtonEventReceiver.invoke(audioManager, receiver);
} catch (InvocationTargetException e) {
// Unpack original exception when possible
Throwable cause = e.getCause();
if (cause instanceof RuntimeException) {
throw (RuntimeException) cause;
} else if (cause instanceof Error) {
throw (Error) cause;
} else {
// Unexpected checked exception; wrap and re-throw
throw new RuntimeException(e);
}
} catch (IllegalAccessException e) {
Log.e(TAG, "IllegalAccessException invoking registerMediaButtonEventReceiver.");
e.printStackTrace();
}
}
@SuppressWarnings("unused")
public static void unregisterMediaButtonEventReceiverCompat(AudioManager audioManager,
ComponentName receiver) {
if (sMethodUnregisterMediaButtonEventReceiver == null)
return;
try {
sMethodUnregisterMediaButtonEventReceiver.invoke(audioManager, receiver);
} catch (InvocationTargetException e) {
// Unpack original exception when possible
Throwable cause = e.getCause();
if (cause instanceof RuntimeException) {
throw (RuntimeException) cause;
} else if (cause instanceof Error) {
throw (Error) cause;
} else {
// Unexpected checked exception; wrap and re-throw
throw new RuntimeException(e);
}
} catch (IllegalAccessException e) {
Log.e(TAG, "IllegalAccessException invoking unregisterMediaButtonEventReceiver.");
e.printStackTrace();
}
}
}

View File

@@ -19,22 +19,53 @@ package com.example.android.musicplayer;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import android.view.KeyEvent;
import android.widget.Toast;
/**
* Receives broadcasted intents. In particular, we are interested in the
* android.media.AUDIO_BECOMING_NOISY intent, which is broadcast, for example, when the user
* disconnects the headphones. This class works because we are declaring it in a &lt;receiver&gt;
* tag in AndroidManifest.xml.
* android.media.AUDIO_BECOMING_NOISY and android.intent.action.MEDIA_BUTTON intents, which is
* broadcast, for example, when the user disconnects the headphones. This class works because we are
* declaring it in a &lt;receiver&gt; tag in AndroidManifest.xml.
*/
public class MusicIntentReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context ctx, Intent intent) {
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(android.media.AudioManager.ACTION_AUDIO_BECOMING_NOISY)) {
Toast.makeText(ctx, "Headphones disconnected.", Toast.LENGTH_SHORT).show();
Toast.makeText(context, "Headphones disconnected.", Toast.LENGTH_SHORT).show();
// send an intent to our MusicService to telling it to pause the audio
ctx.startService(new Intent(MusicService.ACTION_PAUSE));
context.startService(new Intent(MusicService.ACTION_PAUSE));
} else if (intent.getAction().equals(Intent.ACTION_MEDIA_BUTTON)) {
KeyEvent keyEvent = (KeyEvent) intent.getExtras().get(Intent.EXTRA_KEY_EVENT);
if (keyEvent.getAction() != KeyEvent.ACTION_DOWN)
return;
switch (keyEvent.getKeyCode()) {
case KeyEvent.KEYCODE_HEADSETHOOK:
case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
context.startService(new Intent(MusicService.ACTION_TOGGLE_PLAYBACK));
break;
case KeyEvent.KEYCODE_MEDIA_PLAY:
context.startService(new Intent(MusicService.ACTION_PLAY));
break;
case KeyEvent.KEYCODE_MEDIA_PAUSE:
context.startService(new Intent(MusicService.ACTION_PAUSE));
break;
case KeyEvent.KEYCODE_MEDIA_STOP:
context.startService(new Intent(MusicService.ACTION_STOP));
break;
case KeyEvent.KEYCODE_MEDIA_NEXT:
context.startService(new Intent(MusicService.ACTION_SKIP));
break;
case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
// TODO: ensure that doing this in rapid succession actually plays the
// previous song
context.startService(new Intent(MusicService.ACTION_REWIND));
break;
}
}
}
}

View File

@@ -16,16 +16,17 @@
package com.example.android.musicplayer;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.database.Cursor;
import android.net.Uri;
import android.provider.MediaStore;
import android.util.Log;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
/**
* Retrieves and organizes media to play. Before being used, you must call {@link #prepare()},
* which will retrieve all of the music on the user's device (by performing a query on a content
@@ -57,7 +58,8 @@ public class MusicRetriever {
// Perform a query on the content resolver. The URI we're passing specifies that we
// want to query for all audio media on external storage (e.g. SD card)
Cursor cur = mContentResolver.query(uri, null, null, null, null);
Cursor cur = mContentResolver.query(uri, null,
MediaStore.Audio.Media.IS_MUSIC + " = 1", null, null);
Log.i(TAG, "Query finished. " + (cur == null ? "Returned NULL." : "Returned a cursor."));
if (cur == null) {
@@ -73,9 +75,12 @@ public class MusicRetriever {
Log.i(TAG, "Listing...");
// retrieve the indices of the columns where the ID and title of the song are
int titleColumn = cur.getColumnIndex(android.provider.MediaStore.Audio.Media.TITLE);
int idColumn = cur.getColumnIndex(android.provider.MediaStore.Audio.Media._ID);
// retrieve the indices of the columns where the ID, title, etc. of the song are
int artistColumn = cur.getColumnIndex(MediaStore.Audio.Media.ARTIST);
int titleColumn = cur.getColumnIndex(MediaStore.Audio.Media.TITLE);
int albumColumn = cur.getColumnIndex(MediaStore.Audio.Media.ALBUM);
int durationColumn = cur.getColumnIndex(MediaStore.Audio.Media.DURATION);
int idColumn = cur.getColumnIndex(MediaStore.Audio.Media._ID);
Log.i(TAG, "Title column index: " + String.valueOf(titleColumn));
Log.i(TAG, "ID column index: " + String.valueOf(titleColumn));
@@ -83,7 +88,12 @@ public class MusicRetriever {
// add each song to mItems
do {
Log.i(TAG, "ID: " + cur.getString(idColumn) + " Title: " + cur.getString(titleColumn));
mItems.add(new Item(cur.getLong(idColumn), cur.getString(titleColumn)));
mItems.add(new Item(
cur.getLong(idColumn),
cur.getString(artistColumn),
cur.getString(titleColumn),
cur.getString(albumColumn),
cur.getLong(durationColumn)));
} while (cur.moveToNext());
Log.i(TAG, "Done querying media. MusicRetriever is ready.");
@@ -99,17 +109,41 @@ public class MusicRetriever {
return mItems.get(mRandom.nextInt(mItems.size()));
}
public class Item {
public static class Item {
long id;
String artist;
String title;
String album;
long duration;
public Item(long id, String title) {
public Item(long id, String artist, String title, String album, long duration) {
this.id = id;
this.artist = artist;
this.title = title;
this.album = album;
this.duration = duration;
}
public long getId() {
return id;
}
public String getArtist() {
return artist;
}
public String getTitle() {
return title;
}
public String getAlbum() {
return album;
}
public long getDuration() {
return duration;
}
public long getId() { return id; }
public String getTitle() { return title; }
public Uri getURI() {
return ContentUris.withAppendedId(
android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, id);

View File

@@ -16,19 +16,22 @@
package com.example.android.musicplayer;
import java.io.IOException;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.AudioManager;
import android.media.MediaMetadataRetriever;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnCompletionListener;
import android.media.MediaPlayer.OnErrorListener;
import android.media.MediaPlayer.OnPreparedListener;
import android.media.RemoteControlClient;
import android.net.Uri;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiManager.WifiLock;
@@ -37,9 +40,11 @@ import android.os.PowerManager;
import android.util.Log;
import android.widget.Toast;
import java.io.IOException;
/**
* Service that handles media playback. This is the Service through which we perform all the media
* handling in our application. Upon initialization, it starts a {@link MediaRetriever} to scan
* handling in our application. Upon initialization, it starts a {@link MusicRetriever} to scan
* the user's media. Then, it waits for Intents (which come from our main activity,
* {@link MainActivity}, which signal the service to perform specific operations: Play, Pause,
* Rewind, Skip, etc.
@@ -48,7 +53,25 @@ public class MusicService extends Service implements OnCompletionListener, OnPre
OnErrorListener, MusicFocusable,
PrepareMusicRetrieverTask.MusicRetrieverPreparedListener {
NotificationManager mNotificationManager;
// The tag we put on debug messages
final static String TAG = "RandomMusicPlayer";
// These are the Intent actions that we are prepared to handle. Notice that the fact these
// constants exist in our class is a mere convenience: what really defines the actions our
// service can handle are the <action> tags in the <intent-filters> tag for our service in
// AndroidManifest.xml.
public static final String ACTION_TOGGLE_PLAYBACK =
"com.example.android.musicplayer.action.TOGGLE_PLAYBACK";
public static final String ACTION_PLAY = "com.example.android.musicplayer.action.PLAY";
public static final String ACTION_PAUSE = "com.example.android.musicplayer.action.PAUSE";
public static final String ACTION_STOP = "com.example.android.musicplayer.action.STOP";
public static final String ACTION_SKIP = "com.example.android.musicplayer.action.SKIP";
public static final String ACTION_REWIND = "com.example.android.musicplayer.action.REWIND";
public static final String ACTION_URL = "com.example.android.musicplayer.action.URL";
// The volume we set the media player to when we lose audio focus, but are allowed to reduce
// the volume instead of stopping playback.
public static final float DUCK_VOLUME = 0.1f;
// our media player
MediaPlayer mPlayer = null;
@@ -104,24 +127,6 @@ public class MusicService extends Service implements OnCompletionListener, OnPre
// device from shutting off the Wifi radio
WifiLock mWifiLock;
// The tag we put on debug messages
final static String TAG = "RandomMusicPlayer";
// These are the Intent actions that we are prepared to handle. Notice that the fact these
// constants exist in our class is a mere convenience: what really defines the actions our
// service can handle are the <action> tags in the <intent-filters> tag for our service in
// AndroidManifest.xml.
public static final String ACTION_PLAY = "com.example.android.musicplayer.action.PLAY";
public static final String ACTION_PAUSE = "com.example.android.musicplayer.action.PAUSE";
public static final String ACTION_STOP = "com.example.android.musicplayer.action.STOP";
public static final String ACTION_SKIP = "com.example.android.musicplayer.action.SKIP";
public static final String ACTION_REWIND = "com.example.android.musicplayer.action.REWIND";
public static final String ACTION_URL = "com.example.android.musicplayer.action.URL";
// The volume we set the media player to when we lose audio focus, but are allowed to reduce
// the volume instead of stopping playback.
public final float DUCK_VOLUME = 0.1f;
// The ID we use for the notification (the onscreen alert that appears at the notification
// area at the top of the screen as an icon -- and as text as well if the user expands the
// notification area).
@@ -131,6 +136,20 @@ public class MusicService extends Service implements OnCompletionListener, OnPre
// providing titles and URIs as we need.
MusicRetriever mRetriever;
// our RemoteControlClient object, which will use remote control APIs available in
// SDK level >= 14, if they're available.
RemoteControlClientCompat mRemoteControlClientCompat;
// Dummy album art we will pass to the remote control (if the APIs are available).
Bitmap mDummyAlbumArt;
// The component name of MusicIntentReceiver, for use with media button and remote control
// APIs
ComponentName mMediaButtonReceiverComponent;
AudioManager mAudioManager;
NotificationManager mNotificationManager;
Notification mNotification = null;
/**
@@ -167,6 +186,7 @@ public class MusicService extends Service implements OnCompletionListener, OnPre
.createWifiLock(WifiManager.WIFI_MODE_FULL, "mylock");
mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
mAudioManager = (AudioManager) getSystemService(AUDIO_SERVICE);
// Create the retriever and start an asynchronous task that will prepare it.
mRetriever = new MusicRetriever(getContentResolver());
@@ -177,6 +197,10 @@ public class MusicService extends Service implements OnCompletionListener, OnPre
mAudioFocusHelper = new AudioFocusHelper(getApplicationContext(), this);
else
mAudioFocus = AudioFocus.Focused; // no focus feature, so we always "have" audio focus
mDummyAlbumArt = BitmapFactory.decodeResource(getResources(), R.drawable.dummy_album_art);
mMediaButtonReceiverComponent = new ComponentName(this, MusicIntentReceiver.class);
}
/**
@@ -187,7 +211,8 @@ public class MusicService extends Service implements OnCompletionListener, OnPre
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
String action = intent.getAction();
if (action.equals(ACTION_PLAY)) processPlayRequest();
if (action.equals(ACTION_TOGGLE_PLAYBACK)) processTogglePlaybackRequest();
else if (action.equals(ACTION_PLAY)) processPlayRequest();
else if (action.equals(ACTION_PAUSE)) processPauseRequest();
else if (action.equals(ACTION_SKIP)) processSkipRequest();
else if (action.equals(ACTION_STOP)) processStopRequest();
@@ -198,6 +223,14 @@ public class MusicService extends Service implements OnCompletionListener, OnPre
// restart in case it's killed.
}
void processTogglePlaybackRequest() {
if (mState == State.Paused || mState == State.Stopped) {
processPlayRequest();
} else {
processPauseRequest();
}
}
void processPlayRequest() {
if (mState == State.Retrieving) {
// If we are still retrieving media, just set the flag to start playing when we're
@@ -209,6 +242,8 @@ public class MusicService extends Service implements OnCompletionListener, OnPre
tryToGetAudioFocus();
// actually play the song
if (mState == State.Stopped) {
// If we're stopped, just go ahead to the next song and start playing
playNextSong(null);
@@ -219,6 +254,12 @@ public class MusicService extends Service implements OnCompletionListener, OnPre
setUpAsForeground(mSongTitle + " (playing)");
configAndStartMediaPlayer();
}
// Tell any remote controls that our playback state is 'playing'.
if (mRemoteControlClientCompat != null) {
mRemoteControlClientCompat
.setPlaybackState(RemoteControlClient.PLAYSTATE_PLAYING);
}
}
void processPauseRequest() {
@@ -234,7 +275,13 @@ public class MusicService extends Service implements OnCompletionListener, OnPre
mState = State.Paused;
mPlayer.pause();
relaxResources(false); // while paused, we always retain the MediaPlayer
giveUpAudioFocus();
// do not give up audio focus
}
// Tell any remote controls that our playback state is 'paused'.
if (mRemoteControlClientCompat != null) {
mRemoteControlClientCompat
.setPlaybackState(RemoteControlClient.PLAYSTATE_PAUSED);
}
}
@@ -251,13 +298,23 @@ public class MusicService extends Service implements OnCompletionListener, OnPre
}
void processStopRequest() {
if (mState == State.Playing || mState == State.Paused) {
processStopRequest(false);
}
void processStopRequest(boolean force) {
if (mState == State.Playing || mState == State.Paused || force) {
mState = State.Stopped;
// let go of all resources...
relaxResources(true);
giveUpAudioFocus();
// Tell any remote controls that our playback state is 'paused'.
if (mRemoteControlClientCompat != null) {
mRemoteControlClientCompat
.setPlaybackState(RemoteControlClient.PLAYSTATE_STOPPED);
}
// service is no longer necessary. Will be started again if needed.
stopSelf();
}
@@ -330,14 +387,6 @@ public class MusicService extends Service implements OnCompletionListener, OnPre
}
}
/**
* Shortcut to making and displaying a toast. Seemed cleaner than repeating
* this code everywhere, at least for this sample.
*/
void say(String message) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
}
void tryToGetAudioFocus() {
if (mAudioFocus != AudioFocus.Focused && mAudioFocusHelper != null
&& mAudioFocusHelper.requestFocus())
@@ -355,34 +404,80 @@ public class MusicService extends Service implements OnCompletionListener, OnPre
relaxResources(false); // release everything except MediaPlayer
try {
MusicRetriever.Item playingItem = null;
if (manualUrl != null) {
// set the source of the media player to a manual URL or path
createMediaPlayerIfNeeded();
mPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mPlayer.setDataSource(manualUrl);
mSongTitle = manualUrl;
mIsStreaming = manualUrl.startsWith("http:") || manualUrl.startsWith("https:");
playingItem = new MusicRetriever.Item(0, null, manualUrl, null, 0);
}
else {
mIsStreaming = false; // playing a locally available song
MusicRetriever.Item item = mRetriever.getRandomItem();
if (item == null) {
say("No song to play :-(");
playingItem = mRetriever.getRandomItem();
if (playingItem == null) {
Toast.makeText(this,
"No available music to play. Place some music on your external storage "
+ "device (e.g. your SD card) and try again.",
Toast.LENGTH_LONG).show();
processStopRequest(true); // stop everything!
return;
}
// set the source of the media player a a content URI
createMediaPlayerIfNeeded();
mPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mPlayer.setDataSource(getApplicationContext(), item.getURI());
mSongTitle = item.getTitle();
mPlayer.setDataSource(getApplicationContext(), playingItem.getURI());
}
mSongTitle = playingItem.getTitle();
mState = State.Preparing;
setUpAsForeground(mSongTitle + " (loading)");
// Use the media button APIs (if available) to register ourselves for media button
// events
MediaButtonHelper.registerMediaButtonEventReceiverCompat(
mAudioManager, mMediaButtonReceiverComponent);
// Use the remote control APIs (if available) to set the playback state
if (mRemoteControlClientCompat == null) {
Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON);
intent.setComponent(mMediaButtonReceiverComponent);
mRemoteControlClientCompat = new RemoteControlClientCompat(
PendingIntent.getBroadcast(this /*context*/,
0 /*requestCode, ignored*/, intent /*intent*/, 0 /*flags*/));
RemoteControlHelper.registerRemoteControlClient(mAudioManager,
mRemoteControlClientCompat);
}
mRemoteControlClientCompat.setPlaybackState(
RemoteControlClient.PLAYSTATE_PLAYING);
mRemoteControlClientCompat.setTransportControlFlags(
RemoteControlClient.FLAG_KEY_MEDIA_PLAY |
RemoteControlClient.FLAG_KEY_MEDIA_PAUSE |
RemoteControlClient.FLAG_KEY_MEDIA_NEXT |
RemoteControlClient.FLAG_KEY_MEDIA_STOP);
// Update the remote controls
mRemoteControlClientCompat.editMetadata(true)
.putString(MediaMetadataRetriever.METADATA_KEY_ARTIST, playingItem.getArtist())
.putString(MediaMetadataRetriever.METADATA_KEY_ALBUM, playingItem.getAlbum())
.putString(MediaMetadataRetriever.METADATA_KEY_TITLE, playingItem.getTitle())
.putLong(MediaMetadataRetriever.METADATA_KEY_DURATION,
playingItem.getDuration())
// TODO: fetch real item artwork
.putBitmap(
RemoteControlClientCompat.MetadataEditorCompat.METADATA_KEY_ARTWORK,
mDummyAlbumArt)
.apply();
// starts preparing the media player in the background. When it's done, it will call
// our OnPreparedListener (that is, the onPrepared() method on this class, since we set
// the listener to 'this').
@@ -403,14 +498,12 @@ public class MusicService extends Service implements OnCompletionListener, OnPre
}
/** Called when media player is done playing current song. */
@Override
public void onCompletion(MediaPlayer player) {
// The media player finished playing the current song, so we go ahead and start the next.
playNextSong(null);
}
/** Called when media player is done preparing. */
@Override
public void onPrepared(MediaPlayer player) {
// The media player is done preparing. That means we can start playing!
mState = State.Playing;
@@ -449,7 +542,6 @@ public class MusicService extends Service implements OnCompletionListener, OnPre
* Called when there's an error playing media. When this happens, the media player goes to
* the Error state. We warn the user about the error and reset the media player.
*/
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
Toast.makeText(getApplicationContext(), "Media player error! Resetting.",
Toast.LENGTH_SHORT).show();
@@ -461,7 +553,6 @@ public class MusicService extends Service implements OnCompletionListener, OnPre
return true; // true indicates we handled the error
}
@Override
public void onGainedAudioFocus() {
Toast.makeText(getApplicationContext(), "gained audio focus.", Toast.LENGTH_SHORT).show();
mAudioFocus = AudioFocus.Focused;
@@ -471,7 +562,6 @@ public class MusicService extends Service implements OnCompletionListener, OnPre
configAndStartMediaPlayer();
}
@Override
public void onLostAudioFocus(boolean canDuck) {
Toast.makeText(getApplicationContext(), "lost audio focus." + (canDuck ? "can duck" :
"no duck"), Toast.LENGTH_SHORT).show();
@@ -482,7 +572,6 @@ public class MusicService extends Service implements OnCompletionListener, OnPre
configAndStartMediaPlayer();
}
@Override
public void onMusicRetrieverPrepared() {
// Done retrieving!
mState = State.Stopped;

View File

@@ -0,0 +1,353 @@
/*
* Copyright (C) 2011 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.
*/
package com.example.android.musicplayer;
import android.app.PendingIntent;
import android.graphics.Bitmap;
import android.os.Looper;
import android.util.Log;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* RemoteControlClient enables exposing information meant to be consumed by remote controls capable
* of displaying metadata, artwork and media transport control buttons. A remote control client
* object is associated with a media button event receiver. This event receiver must have been
* previously registered with
* {@link android.media.AudioManager#registerMediaButtonEventReceiver(android.content.ComponentName)}
* before the RemoteControlClient can be registered through
* {@link android.media.AudioManager#registerRemoteControlClient(android.media.RemoteControlClient)}.
*/
@SuppressWarnings({"rawtypes", "unchecked"})
public class RemoteControlClientCompat {
private static final String TAG = "RemoteControlCompat";
private static Class sRemoteControlClientClass;
// RCC short for RemoteControlClient
private static Method sRCCEditMetadataMethod;
private static Method sRCCSetPlayStateMethod;
private static Method sRCCSetTransportControlFlags;
private static boolean sHasRemoteControlAPIs = false;
static {
try {
ClassLoader classLoader = RemoteControlClientCompat.class.getClassLoader();
sRemoteControlClientClass = getActualRemoteControlClientClass(classLoader);
// dynamically populate the playstate and flag values in case they change
// in future versions.
for (Field field : RemoteControlClientCompat.class.getFields()) {
try {
Field realField = sRemoteControlClientClass.getField(field.getName());
Object realValue = realField.get(null);
field.set(null, realValue);
} catch (NoSuchFieldException e) {
Log.w(TAG, "Could not get real field: " + field.getName());
} catch (IllegalArgumentException e) {
Log.w(TAG, "Error trying to pull field value for: " + field.getName()
+ " " + e.getMessage());
} catch (IllegalAccessException e) {
Log.w(TAG, "Error trying to pull field value for: " + field.getName()
+ " " + e.getMessage());
}
}
// get the required public methods on RemoteControlClient
sRCCEditMetadataMethod = sRemoteControlClientClass.getMethod("editMetadata",
boolean.class);
sRCCSetPlayStateMethod = sRemoteControlClientClass.getMethod("setPlaybackState",
int.class);
sRCCSetTransportControlFlags = sRemoteControlClientClass.getMethod(
"setTransportControlFlags", int.class);
sHasRemoteControlAPIs = true;
} catch (ClassNotFoundException e) {
// Silently fail when running on an OS before ICS.
} catch (NoSuchMethodException e) {
// Silently fail when running on an OS before ICS.
} catch (IllegalArgumentException e) {
// Silently fail when running on an OS before ICS.
} catch (SecurityException e) {
// Silently fail when running on an OS before ICS.
}
}
public static Class getActualRemoteControlClientClass(ClassLoader classLoader)
throws ClassNotFoundException {
return classLoader.loadClass("android.media.RemoteControlClient");
}
private Object mActualRemoteControlClient;
public RemoteControlClientCompat(PendingIntent pendingIntent) {
if (!sHasRemoteControlAPIs) {
return;
}
try {
mActualRemoteControlClient =
sRemoteControlClientClass.getConstructor(PendingIntent.class)
.newInstance(pendingIntent);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public RemoteControlClientCompat(PendingIntent pendingIntent, Looper looper) {
if (!sHasRemoteControlAPIs) {
return;
}
try {
mActualRemoteControlClient =
sRemoteControlClientClass.getConstructor(PendingIntent.class, Looper.class)
.newInstance(pendingIntent, looper);
} catch (Exception e) {
Log.e(TAG, "Error creating new instance of " + sRemoteControlClientClass.getName(), e);
}
}
/**
* Class used to modify metadata in a {@link android.media.RemoteControlClient} object. Use
* {@link android.media.RemoteControlClient#editMetadata(boolean)} to create an instance of an
* editor, on which you set the metadata for the RemoteControlClient instance. Once all the
* information has been set, use {@link #apply()} to make it the new metadata that should be
* displayed for the associated client. Once the metadata has been "applied", you cannot reuse
* this instance of the MetadataEditor.
*/
public class MetadataEditorCompat {
private Method mPutStringMethod;
private Method mPutBitmapMethod;
private Method mPutLongMethod;
private Method mClearMethod;
private Method mApplyMethod;
private Object mActualMetadataEditor;
/**
* The metadata key for the content artwork / album art.
*/
public final static int METADATA_KEY_ARTWORK = 100;
private MetadataEditorCompat(Object actualMetadataEditor) {
if (sHasRemoteControlAPIs && actualMetadataEditor == null) {
throw new IllegalArgumentException("Remote Control API's exist, " +
"should not be given a null MetadataEditor");
}
if (sHasRemoteControlAPIs) {
Class metadataEditorClass = actualMetadataEditor.getClass();
try {
mPutStringMethod = metadataEditorClass.getMethod("putString",
int.class, String.class);
mPutBitmapMethod = metadataEditorClass.getMethod("putBitmap",
int.class, Bitmap.class);
mPutLongMethod = metadataEditorClass.getMethod("putLong",
int.class, long.class);
mClearMethod = metadataEditorClass.getMethod("clear", new Class[]{});
mApplyMethod = metadataEditorClass.getMethod("apply", new Class[]{});
} catch (Exception e) {
throw new RuntimeException(e.getMessage(), e);
}
}
mActualMetadataEditor = actualMetadataEditor;
}
/**
* Adds textual information to be displayed.
* Note that none of the information added after {@link #apply()} has been called,
* will be displayed.
* @param key The identifier of a the metadata field to set. Valid values are
* {@link android.media.MediaMetadataRetriever#METADATA_KEY_ALBUM},
* {@link android.media.MediaMetadataRetriever#METADATA_KEY_ALBUMARTIST},
* {@link android.media.MediaMetadataRetriever#METADATA_KEY_TITLE},
* {@link android.media.MediaMetadataRetriever#METADATA_KEY_ARTIST},
* {@link android.media.MediaMetadataRetriever#METADATA_KEY_AUTHOR},
* {@link android.media.MediaMetadataRetriever#METADATA_KEY_COMPILATION},
* {@link android.media.MediaMetadataRetriever#METADATA_KEY_COMPOSER},
* {@link android.media.MediaMetadataRetriever#METADATA_KEY_DATE},
* {@link android.media.MediaMetadataRetriever#METADATA_KEY_GENRE},
* {@link android.media.MediaMetadataRetriever#METADATA_KEY_TITLE},
* {@link android.media.MediaMetadataRetriever#METADATA_KEY_WRITER}.
* @param value The text for the given key, or {@code null} to signify there is no valid
* information for the field.
* @return Returns a reference to the same MetadataEditor object, so you can chain put
* calls together.
*/
public MetadataEditorCompat putString(int key, String value) {
if (sHasRemoteControlAPIs) {
try {
mPutStringMethod.invoke(mActualMetadataEditor, key, value);
} catch (Exception e) {
throw new RuntimeException(e.getMessage(), e);
}
}
return this;
}
/**
* Sets the album / artwork picture to be displayed on the remote control.
* @param key the identifier of the bitmap to set. The only valid value is
* {@link #METADATA_KEY_ARTWORK}
* @param bitmap The bitmap for the artwork, or null if there isn't any.
* @return Returns a reference to the same MetadataEditor object, so you can chain put
* calls together.
* @throws IllegalArgumentException
* @see android.graphics.Bitmap
*/
public MetadataEditorCompat putBitmap(int key, Bitmap bitmap) {
if (sHasRemoteControlAPIs) {
try {
mPutBitmapMethod.invoke(mActualMetadataEditor, key, bitmap);
} catch (Exception e) {
throw new RuntimeException(e.getMessage(), e);
}
}
return this;
}
/**
* Adds numerical information to be displayed.
* Note that none of the information added after {@link #apply()} has been called,
* will be displayed.
* @param key the identifier of a the metadata field to set. Valid values are
* {@link android.media.MediaMetadataRetriever#METADATA_KEY_CD_TRACK_NUMBER},
* {@link android.media.MediaMetadataRetriever#METADATA_KEY_DISC_NUMBER},
* {@link android.media.MediaMetadataRetriever#METADATA_KEY_DURATION} (with a value
* expressed in milliseconds),
* {@link android.media.MediaMetadataRetriever#METADATA_KEY_YEAR}.
* @param value The long value for the given key
* @return Returns a reference to the same MetadataEditor object, so you can chain put
* calls together.
* @throws IllegalArgumentException
*/
public MetadataEditorCompat putLong(int key, long value) {
if (sHasRemoteControlAPIs) {
try {
mPutLongMethod.invoke(mActualMetadataEditor, key, value);
} catch (Exception e) {
throw new RuntimeException(e.getMessage(), e);
}
}
return this;
}
/**
* Clears all the metadata that has been set since the MetadataEditor instance was
* created with {@link android.media.RemoteControlClient#editMetadata(boolean)}.
*/
public void clear() {
if (sHasRemoteControlAPIs) {
try {
mClearMethod.invoke(mActualMetadataEditor, (Object[]) null);
} catch (Exception e) {
throw new RuntimeException(e.getMessage(), e);
}
}
}
/**
* Associates all the metadata that has been set since the MetadataEditor instance was
* created with {@link android.media.RemoteControlClient#editMetadata(boolean)}, or since
* {@link #clear()} was called, with the RemoteControlClient. Once "applied", this
* MetadataEditor cannot be reused to edit the RemoteControlClient's metadata.
*/
public void apply() {
if (sHasRemoteControlAPIs) {
try {
mApplyMethod.invoke(mActualMetadataEditor, (Object[]) null);
} catch (Exception e) {
throw new RuntimeException(e.getMessage(), e);
}
}
}
}
/**
* Creates a {@link android.media.RemoteControlClient.MetadataEditor}.
* @param startEmpty Set to false if you want the MetadataEditor to contain the metadata that
* was previously applied to the RemoteControlClient, or true if it is to be created empty.
* @return a new MetadataEditor instance.
*/
public MetadataEditorCompat editMetadata(boolean startEmpty) {
Object metadataEditor;
if (sHasRemoteControlAPIs) {
try {
metadataEditor = sRCCEditMetadataMethod.invoke(mActualRemoteControlClient,
startEmpty);
} catch (Exception e) {
throw new RuntimeException(e);
}
} else {
metadataEditor = null;
}
return new MetadataEditorCompat(metadataEditor);
}
/**
* Sets the current playback state.
* @param state The current playback state, one of the following values:
* {@link android.media.RemoteControlClient#PLAYSTATE_STOPPED},
* {@link android.media.RemoteControlClient#PLAYSTATE_PAUSED},
* {@link android.media.RemoteControlClient#PLAYSTATE_PLAYING},
* {@link android.media.RemoteControlClient#PLAYSTATE_FAST_FORWARDING},
* {@link android.media.RemoteControlClient#PLAYSTATE_REWINDING},
* {@link android.media.RemoteControlClient#PLAYSTATE_SKIPPING_FORWARDS},
* {@link android.media.RemoteControlClient#PLAYSTATE_SKIPPING_BACKWARDS},
* {@link android.media.RemoteControlClient#PLAYSTATE_BUFFERING},
* {@link android.media.RemoteControlClient#PLAYSTATE_ERROR}.
*/
public void setPlaybackState(int state) {
if (sHasRemoteControlAPIs) {
try {
sRCCSetPlayStateMethod.invoke(mActualRemoteControlClient, state);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
/**
* Sets the flags for the media transport control buttons that this client supports.
* @param transportControlFlags A combination of the following flags:
* {@link android.media.RemoteControlClient#FLAG_KEY_MEDIA_PREVIOUS},
* {@link android.media.RemoteControlClient#FLAG_KEY_MEDIA_REWIND},
* {@link android.media.RemoteControlClient#FLAG_KEY_MEDIA_PLAY},
* {@link android.media.RemoteControlClient#FLAG_KEY_MEDIA_PLAY_PAUSE},
* {@link android.media.RemoteControlClient#FLAG_KEY_MEDIA_PAUSE},
* {@link android.media.RemoteControlClient#FLAG_KEY_MEDIA_STOP},
* {@link android.media.RemoteControlClient#FLAG_KEY_MEDIA_FAST_FORWARD},
* {@link android.media.RemoteControlClient#FLAG_KEY_MEDIA_NEXT}
*/
public void setTransportControlFlags(int transportControlFlags) {
if (sHasRemoteControlAPIs) {
try {
sRCCSetTransportControlFlags.invoke(mActualRemoteControlClient,
transportControlFlags);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
public final Object getActualRemoteControlClientObject() {
return mActualRemoteControlClient;
}
}

View File

@@ -0,0 +1,87 @@
/*
* Copyright (C) 2011 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.
*/
package com.example.android.musicplayer;
import android.media.AudioManager;
import android.util.Log;
import java.lang.reflect.Method;
/**
* Contains methods to handle registering/unregistering remote control clients. These methods only
* run on ICS devices. On previous devices, all methods are no-ops.
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public class RemoteControlHelper {
private static final String TAG = "RemoteControlHelper";
private static boolean sHasRemoteControlAPIs = false;
private static Method sRegisterRemoteControlClientMethod;
private static Method sUnregisterRemoteControlClientMethod;
static {
try {
ClassLoader classLoader = RemoteControlHelper.class.getClassLoader();
Class sRemoteControlClientClass =
RemoteControlClientCompat.getActualRemoteControlClientClass(classLoader);
sRegisterRemoteControlClientMethod = AudioManager.class.getMethod(
"registerRemoteControlClient", new Class[]{sRemoteControlClientClass});
sUnregisterRemoteControlClientMethod = AudioManager.class.getMethod(
"unregisterRemoteControlClient", new Class[]{sRemoteControlClientClass});
sHasRemoteControlAPIs = true;
} catch (ClassNotFoundException e) {
// Silently fail when running on an OS before ICS.
} catch (NoSuchMethodException e) {
// Silently fail when running on an OS before ICS.
} catch (IllegalArgumentException e) {
// Silently fail when running on an OS before ICS.
} catch (SecurityException e) {
// Silently fail when running on an OS before ICS.
}
}
public static void registerRemoteControlClient(AudioManager audioManager,
RemoteControlClientCompat remoteControlClient) {
if (!sHasRemoteControlAPIs) {
return;
}
try {
sRegisterRemoteControlClientMethod.invoke(audioManager,
remoteControlClient.getActualRemoteControlClientObject());
} catch (Exception e) {
Log.e(TAG, e.getMessage(), e);
}
}
public static void unregisterRemoteControlClient(AudioManager audioManager,
RemoteControlClientCompat remoteControlClient) {
if (!sHasRemoteControlAPIs) {
return;
}
try {
sUnregisterRemoteControlClientMethod.invoke(audioManager,
remoteControlClient.getActualRemoteControlClientObject());
} catch (Exception e) {
Log.e(TAG, e.getMessage(), e);
}
}
}