Merge "Remove deprecated media sample."

This commit is contained in:
Nicole Borrelli
2018-08-21 17:21:34 +00:00
committed by Gerrit Code Review
48 changed files with 0 additions and 2026 deletions

View File

@@ -410,7 +410,6 @@ development/samples/LceDemo samples/${PLATFORM_NAME}/legacy/L
development/samples/LunarLander samples/${PLATFORM_NAME}/legacy/LunarLander
development/samples/MultiResolution samples/${PLATFORM_NAME}/legacy/MultiResolution
development/samples/NotePad samples/${PLATFORM_NAME}/legacy/NotePad
development/samples/RandomMusicPlayer samples/${PLATFORM_NAME}/legacy/RandomMusicPlayer
development/samples/SpellChecker/SampleSpellCheckerService samples/${PLATFORM_NAME}/legacy/SpellChecker/SampleSpellCheckerService
development/samples/SpellChecker/HelloSpellChecker samples/${PLATFORM_NAME}/legacy/SpellChecker/HelloSpellChecker
development/samples/SampleSyncAdapter samples/${PLATFORM_NAME}/legacy/SampleSyncAdapter

View File

@@ -1,18 +0,0 @@
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
LOCAL_DEX_PREOPT := false
include $(BUILD_PACKAGE)
# Use the following include to make our test apk.
include $(call all-makefiles-under,$(LOCAL_PATH))

View File

@@ -1,63 +0,0 @@
<!--
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.
-->
<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="14" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<application android:icon="@drawable/ic_launcher" android:label="@string/app_title">
<activity android:name=".MainActivity"
android:label="@string/app_title"
android:theme="@android:style/Theme.Black.NoTitleBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<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" />
<action android:name="com.example.android.musicplayer.action.REWIND" />
<action android:name="com.example.android.musicplayer.action.STOP" />
</intent-filter>
<intent-filter>
<action android:name="com.example.android.musicplayer.action.URL" />
<data android:scheme="http" />
</intent-filter>
</service>
<receiver android:name=".MusicIntentReceiver">
<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,13 +0,0 @@
<p>A simple music player that illustrates how to make a multimedia application
that manages media playback from a service. It allows the user to play music
available on the device or specify a URL from which the media should be
streamed. It also illustrates how to use the notification system to indicate
an ongoing task and how to deal with audio focus changes.</p>
<p class="note"><strong>Update:</strong> This sample also illustrates how to use the
<code><a href="../../../reference/android/media/RemoteControlClient.html">RemoteControlClient</a></code>
class added in API level 14 to integrate with music playback remote controls
such as those found on the lockscreen.</p>
<img alt="" src="../images/randommusicplayer.png" />

Binary file not shown.

Before

Width:  |  Height:  |  Size: 740 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 834 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: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

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

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.

Before

Width:  |  Height:  |  Size: 547 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 548 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 779 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 985 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -1,21 +0,0 @@
<!--
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

@@ -1,21 +0,0 @@
<!--
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

@@ -1,21 +0,0 @@
<!--
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

@@ -1,21 +0,0 @@
<!--
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

@@ -1,21 +0,0 @@
<!--
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

@@ -1,21 +0,0 @@
<!--
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,68 +0,0 @@
<!--
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">
<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" />
<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

@@ -1,76 +0,0 @@
<!--
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

@@ -1,19 +0,0 @@
<!--
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

@@ -1,70 +0,0 @@
/*
* 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.content.Context;
import android.media.AudioManager;
/**
* Convenience class to deal with audio focus. This class deals with everything related to audio
* focus: it can request and abandon focus, and will intercept focus change events and deliver
* them to a MusicFocusable interface (which, in our case, is implemented by {@link MusicService}).
*
* This class can only be used on SDK level 8 and above, since it uses API features that are not
* available on previous SDK's.
*/
public class AudioFocusHelper implements AudioManager.OnAudioFocusChangeListener {
AudioManager mAM;
MusicFocusable mFocusable;
public AudioFocusHelper(Context ctx, MusicFocusable focusable) {
mAM = (AudioManager) ctx.getSystemService(Context.AUDIO_SERVICE);
mFocusable = focusable;
}
/** Requests audio focus. Returns whether request was successful or not. */
public boolean requestFocus() {
return AudioManager.AUDIOFOCUS_REQUEST_GRANTED ==
mAM.requestAudioFocus(this, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
}
/** Abandons audio focus. Returns whether request was successful or not. */
public boolean abandonFocus() {
return AudioManager.AUDIOFOCUS_REQUEST_GRANTED == mAM.abandonAudioFocus(this);
}
/**
* Called by AudioManager on audio focus changes. We implement this by calling our
* MusicFocusable appropriately to relay the message.
*/
public void onAudioFocusChange(int focusChange) {
if (mFocusable == null) return;
switch (focusChange) {
case AudioManager.AUDIOFOCUS_GAIN:
mFocusable.onGainedAudioFocus();
break;
case AudioManager.AUDIOFOCUS_LOSS:
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
mFocusable.onLostAudioFocus(false);
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
mFocusable.onLostAudioFocus(true);
break;
default:
}
}
}

View File

@@ -1,133 +0,0 @@
/*
* 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.Activity;
import android.app.AlertDialog;
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;
import android.widget.EditText;
/**
* Main activity: shows media player buttons. This activity shows the media player buttons and
* lets the user click them. No media handling is done here -- everything is done by passing
* Intents to our {@link MusicService}.
* */
public class MainActivity extends Activity implements OnClickListener {
/**
* The URL we suggest as default when adding by URL. This is just so that the user doesn't
* have to find an URL to test this sample.
*/
final String SUGGESTED_URL = "http://www.vorbis.com/music/Epoq-Lepidoptera.ogg";
Button mPlayButton;
Button mPauseButton;
Button mSkipButton;
Button mRewindButton;
Button mStopButton;
Button mEjectButton;
/**
* Called when the activity is first created. Here, we simply set the event listeners and
* start the background service ({@link MusicService}) that will handle the actual media
* playback.
*/
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mPlayButton = (Button) findViewById(R.id.playbutton);
mPauseButton = (Button) findViewById(R.id.pausebutton);
mSkipButton = (Button) findViewById(R.id.skipbutton);
mRewindButton = (Button) findViewById(R.id.rewindbutton);
mStopButton = (Button) findViewById(R.id.stopbutton);
mEjectButton = (Button) findViewById(R.id.ejectbutton);
mPlayButton.setOnClickListener(this);
mPauseButton.setOnClickListener(this);
mSkipButton.setOnClickListener(this);
mRewindButton.setOnClickListener(this);
mStopButton.setOnClickListener(this);
mEjectButton.setOnClickListener(this);
}
public void onClick(View target) {
// Send the correct intent to the MusicService, according to the button that was clicked
if (target == mPlayButton)
startService(new Intent(MusicService.ACTION_PLAY));
else if (target == mPauseButton)
startService(new Intent(MusicService.ACTION_PAUSE));
else if (target == mSkipButton)
startService(new Intent(MusicService.ACTION_SKIP));
else if (target == mRewindButton)
startService(new Intent(MusicService.ACTION_REWIND));
else if (target == mStopButton)
startService(new Intent(MusicService.ACTION_STOP));
else if (target == mEjectButton) {
showUrlDialog();
}
}
/**
* Shows an alert dialog where the user can input a URL. After showing the dialog, if the user
* confirms, sends the appropriate intent to the {@link MusicService} to cause that URL to be
* played.
*/
void showUrlDialog() {
AlertDialog.Builder alertBuilder = new AlertDialog.Builder(this);
alertBuilder.setTitle("Manual Input");
alertBuilder.setMessage("Enter a URL (must be http://)");
final EditText input = new EditText(this);
alertBuilder.setView(input);
input.setText(SUGGESTED_URL);
alertBuilder.setPositiveButton("Play!", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dlg, int whichButton) {
// Send an intent with the URL of the song to play. This is expected by
// MusicService.
Intent i = new Intent(MusicService.ACTION_URL);
Uri uri = Uri.parse(input.getText().toString());
i.setData(uri);
startService(i);
}
});
alertBuilder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dlg, int whichButton) {}
});
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

@@ -1,85 +0,0 @@
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

@@ -1,35 +0,0 @@
/*
* 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;
/**
* Represents something that can react to audio focus events. We implement this instead of just
* using AudioManager.OnAudioFocusChangeListener because that interface is only available in SDK
* level 8 and above, and we want our application to work on previous SDKs.
*/
public interface MusicFocusable {
/** Signals that audio focus was gained. */
public void onGainedAudioFocus();
/**
* Signals that audio focus was lost.
*
* @param canDuck If true, audio can continue in "ducked" mode (low volume). Otherwise, all
* audio must stop.
*/
public void onLostAudioFocus(boolean canDuck);
}

View File

@@ -1,71 +0,0 @@
/*
* 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.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 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 context, Intent intent) {
if (intent.getAction().equals(android.media.AudioManager.ACTION_AUDIO_BECOMING_NOISY)) {
Toast.makeText(context, "Headphones disconnected.", Toast.LENGTH_SHORT).show();
// send an intent to our MusicService to telling it to pause the audio
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

@@ -1,152 +0,0 @@
/*
* 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.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
* resolver). After that, it's ready to retrieve a random song, with its title and URI, upon
* request.
*/
public class MusicRetriever {
final String TAG = "MusicRetriever";
ContentResolver mContentResolver;
// the items (songs) we have queried
List<Item> mItems = new ArrayList<Item>();
Random mRandom = new Random();
public MusicRetriever(ContentResolver cr) {
mContentResolver = cr;
}
/**
* Loads music data. This method may take long, so be sure to call it asynchronously without
* blocking the main thread.
*/
public void prepare() {
Uri uri = android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
Log.i(TAG, "Querying media...");
Log.i(TAG, "URI: " + uri.toString());
// 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,
MediaStore.Audio.Media.IS_MUSIC + " = 1", null, null);
Log.i(TAG, "Query finished. " + (cur == null ? "Returned NULL." : "Returned a cursor."));
if (cur == null) {
// Query failed...
Log.e(TAG, "Failed to retrieve music: cursor is null :-(");
return;
}
if (!cur.moveToFirst()) {
// Nothing to query. There is no music on the device. How boring.
Log.e(TAG, "Failed to move cursor to first row (no query results).");
return;
}
Log.i(TAG, "Listing...");
// 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));
// 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(artistColumn),
cur.getString(titleColumn),
cur.getString(albumColumn),
cur.getLong(durationColumn)));
} while (cur.moveToNext());
Log.i(TAG, "Done querying media. MusicRetriever is ready.");
}
public ContentResolver getContentResolver() {
return mContentResolver;
}
/** Returns a random Item. If there are no items available, returns null. */
public Item getRandomItem() {
if (mItems.size() <= 0) return null;
return mItems.get(mRandom.nextInt(mItems.size()));
}
public static class Item {
long id;
String artist;
String title;
String album;
long duration;
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 Uri getURI() {
return ContentUris.withAppendedId(
android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, id);
}
}
}

View File

@@ -1,606 +0,0 @@
/*
* 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.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;
import android.os.IBinder;
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 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.
*/
public class MusicService extends Service implements OnCompletionListener, OnPreparedListener,
OnErrorListener, MusicFocusable,
PrepareMusicRetrieverTask.MusicRetrieverPreparedListener {
// 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;
// our AudioFocusHelper object, if it's available (it's available on SDK level >= 8)
// If not available, this will be null. Always check for null before using!
AudioFocusHelper mAudioFocusHelper = null;
// indicates the state our service:
enum State {
Retrieving, // the MediaRetriever is retrieving music
Stopped, // media player is stopped and not prepared to play
Preparing, // media player is preparing...
Playing, // playback active (media player ready!). (but the media player may actually be
// paused in this state if we don't have audio focus. But we stay in this state
// so that we know we have to resume playback once we get focus back)
Paused // playback paused (media player ready!)
};
State mState = State.Retrieving;
// if in Retrieving mode, this flag indicates whether we should start playing immediately
// when we are ready or not.
boolean mStartPlayingAfterRetrieve = false;
// if mStartPlayingAfterRetrieve is true, this variable indicates the URL that we should
// start playing when we are ready. If null, we should play a random song from the device
Uri mWhatToPlayAfterRetrieve = null;
enum PauseReason {
UserRequest, // paused by user request
FocusLoss, // paused because of audio focus loss
};
// why did we pause? (only relevant if mState == State.Paused)
PauseReason mPauseReason = PauseReason.UserRequest;
// do we have audio focus?
enum AudioFocus {
NoFocusNoDuck, // we don't have audio focus, and can't duck
NoFocusCanDuck, // we don't have focus, but can play at a low volume ("ducking")
Focused // we have full audio focus
}
AudioFocus mAudioFocus = AudioFocus.NoFocusNoDuck;
// title of the song we are currently playing
String mSongTitle = "";
// whether the song we are playing is streaming from the network
boolean mIsStreaming = false;
// Wifi lock that we hold when streaming files from the internet, in order to prevent the
// device from shutting off the Wifi radio
WifiLock mWifiLock;
// 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).
final int NOTIFICATION_ID = 1;
// Our instance of our MusicRetriever, which handles scanning for media and
// 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.Builder mNotificationBuilder = null;
/**
* Makes sure the media player exists and has been reset. This will create the media player
* if needed, or reset the existing media player if one already exists.
*/
void createMediaPlayerIfNeeded() {
if (mPlayer == null) {
mPlayer = new MediaPlayer();
// Make sure the media player will acquire a wake-lock while playing. If we don't do
// that, the CPU might go to sleep while the song is playing, causing playback to stop.
//
// Remember that to use this, we have to declare the android.permission.WAKE_LOCK
// permission in AndroidManifest.xml.
mPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);
// we want the media player to notify us when it's ready preparing, and when it's done
// playing:
mPlayer.setOnPreparedListener(this);
mPlayer.setOnCompletionListener(this);
mPlayer.setOnErrorListener(this);
}
else
mPlayer.reset();
}
@Override
public void onCreate() {
Log.i(TAG, "debug: Creating service");
// Create the Wifi lock (this does not acquire the lock, this just creates it)
mWifiLock = ((WifiManager) getSystemService(Context.WIFI_SERVICE))
.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());
(new PrepareMusicRetrieverTask(mRetriever,this)).execute();
// create the Audio Focus Helper, if the Audio Focus feature is available (SDK 8 or above)
if (android.os.Build.VERSION.SDK_INT >= 8)
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);
}
/**
* Called when we receive an Intent. When we receive an intent sent to us via startService(),
* this is the method that gets called. So here we react appropriately depending on the
* Intent's action, which specifies what is being requested of us.
*/
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
String action = intent.getAction();
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();
else if (action.equals(ACTION_REWIND)) processRewindRequest();
else if (action.equals(ACTION_URL)) processAddRequest(intent);
return START_NOT_STICKY; // Means we started the service, but don't want it to
// 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
// ready
mWhatToPlayAfterRetrieve = null; // play a random song
mStartPlayingAfterRetrieve = true;
return;
}
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);
}
else if (mState == State.Paused) {
// If we're paused, just continue playback and restore the 'foreground service' state.
mState = State.Playing;
setUpAsForeground(mSongTitle + " (playing)");
configAndStartMediaPlayer();
}
// Tell any remote controls that our playback state is 'playing'.
if (mRemoteControlClientCompat != null) {
mRemoteControlClientCompat
.setPlaybackState(RemoteControlClient.PLAYSTATE_PLAYING);
}
}
void processPauseRequest() {
if (mState == State.Retrieving) {
// If we are still retrieving media, clear the flag that indicates we should start
// playing when we're ready
mStartPlayingAfterRetrieve = false;
return;
}
if (mState == State.Playing) {
// Pause media player and cancel the 'foreground service' state.
mState = State.Paused;
mPlayer.pause();
relaxResources(false); // while paused, we always retain the MediaPlayer
// do not give up audio focus
}
// Tell any remote controls that our playback state is 'paused'.
if (mRemoteControlClientCompat != null) {
mRemoteControlClientCompat
.setPlaybackState(RemoteControlClient.PLAYSTATE_PAUSED);
}
}
void processRewindRequest() {
if (mState == State.Playing || mState == State.Paused)
mPlayer.seekTo(0);
}
void processSkipRequest() {
if (mState == State.Playing || mState == State.Paused) {
tryToGetAudioFocus();
playNextSong(null);
}
}
void processStopRequest() {
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();
}
}
/**
* Releases resources used by the service for playback. This includes the "foreground service"
* status and notification, the wake locks and possibly the MediaPlayer.
*
* @param releaseMediaPlayer Indicates whether the Media Player should also be released or not
*/
void relaxResources(boolean releaseMediaPlayer) {
// stop being a foreground service
stopForeground(true);
// stop and release the Media Player, if it's available
if (releaseMediaPlayer && mPlayer != null) {
mPlayer.reset();
mPlayer.release();
mPlayer = null;
}
// we can also release the Wifi lock, if we're holding it
if (mWifiLock.isHeld()) mWifiLock.release();
}
void giveUpAudioFocus() {
if (mAudioFocus == AudioFocus.Focused && mAudioFocusHelper != null
&& mAudioFocusHelper.abandonFocus())
mAudioFocus = AudioFocus.NoFocusNoDuck;
}
/**
* Reconfigures MediaPlayer according to audio focus settings and starts/restarts it. This
* method starts/restarts the MediaPlayer respecting the current audio focus state. So if
* we have focus, it will play normally; if we don't have focus, it will either leave the
* MediaPlayer paused or set it to a low volume, depending on what is allowed by the
* current focus settings. This method assumes mPlayer != null, so if you are calling it,
* you have to do so from a context where you are sure this is the case.
*/
void configAndStartMediaPlayer() {
if (mAudioFocus == AudioFocus.NoFocusNoDuck) {
// If we don't have audio focus and can't duck, we have to pause, even if mState
// is State.Playing. But we stay in the Playing state so that we know we have to resume
// playback once we get the focus back.
if (mPlayer.isPlaying()) mPlayer.pause();
return;
}
else if (mAudioFocus == AudioFocus.NoFocusCanDuck)
mPlayer.setVolume(DUCK_VOLUME, DUCK_VOLUME); // we'll be relatively quiet
else
mPlayer.setVolume(1.0f, 1.0f); // we can be loud
if (!mPlayer.isPlaying()) mPlayer.start();
}
void processAddRequest(Intent intent) {
// user wants to play a song directly by URL or path. The URL or path comes in the "data"
// part of the Intent. This Intent is sent by {@link MainActivity} after the user
// specifies the URL/path via an alert box.
if (mState == State.Retrieving) {
// we'll play the requested URL right after we finish retrieving
mWhatToPlayAfterRetrieve = intent.getData();
mStartPlayingAfterRetrieve = true;
}
else if (mState == State.Playing || mState == State.Paused || mState == State.Stopped) {
Log.i(TAG, "Playing from URL/path: " + intent.getData().toString());
tryToGetAudioFocus();
playNextSong(intent.getData().toString());
}
}
void tryToGetAudioFocus() {
if (mAudioFocus != AudioFocus.Focused && mAudioFocusHelper != null
&& mAudioFocusHelper.requestFocus())
mAudioFocus = AudioFocus.Focused;
}
/**
* Starts playing the next song. If manualUrl is null, the next song will be randomly selected
* from our Media Retriever (that is, it will be a random song in the user's device). If
* manualUrl is non-null, then it specifies the URL or path to the song that will be played
* next.
*/
void playNextSong(String manualUrl) {
mState = State.Stopped;
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);
mIsStreaming = manualUrl.startsWith("http:") || manualUrl.startsWith("https:");
playingItem = new MusicRetriever.Item(0, null, manualUrl, null, 0);
}
else {
mIsStreaming = false; // playing a locally available song
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(), 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').
//
// Until the media player is prepared, we *cannot* call start() on it!
mPlayer.prepareAsync();
// If we are streaming from the internet, we want to hold a Wifi lock, which prevents
// the Wifi radio from going to sleep while the song is playing. If, on the other hand,
// we are *not* streaming, we want to release the lock if we were holding it before.
if (mIsStreaming) mWifiLock.acquire();
else if (mWifiLock.isHeld()) mWifiLock.release();
}
catch (IOException ex) {
Log.e("MusicService", "IOException playing next song: " + ex.getMessage());
ex.printStackTrace();
}
}
/** Called when media player is done playing current song. */
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. */
public void onPrepared(MediaPlayer player) {
// The media player is done preparing. That means we can start playing!
mState = State.Playing;
updateNotification(mSongTitle + " (playing)");
configAndStartMediaPlayer();
}
/** Updates the notification. */
void updateNotification(String text) {
PendingIntent pi = PendingIntent.getActivity(getApplicationContext(), 0,
new Intent(getApplicationContext(), MainActivity.class),
PendingIntent.FLAG_UPDATE_CURRENT);
mNotificationBuilder.setContentText(text)
.setContentIntent(pi);
mNotificationManager.notify(NOTIFICATION_ID, mNotificationBuilder.build());
}
/**
* Configures service as a foreground service. A foreground service is a service that's doing
* something the user is actively aware of (such as playing music), and must appear to the
* user as a notification. That's why we create the notification here.
*/
void setUpAsForeground(String text) {
PendingIntent pi = PendingIntent.getActivity(getApplicationContext(), 0,
new Intent(getApplicationContext(), MainActivity.class),
PendingIntent.FLAG_UPDATE_CURRENT);
// Build the notification object.
mNotificationBuilder = new Notification.Builder(getApplicationContext())
.setSmallIcon(R.drawable.ic_stat_playing)
.setTicker(text)
.setWhen(System.currentTimeMillis())
.setContentTitle("RandomMusicPlayer")
.setContentText(text)
.setContentIntent(pi)
.setOngoing(true);
startForeground(NOTIFICATION_ID, mNotificationBuilder.build());
}
/**
* 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.
*/
public boolean onError(MediaPlayer mp, int what, int extra) {
Toast.makeText(getApplicationContext(), "Media player error! Resetting.",
Toast.LENGTH_SHORT).show();
Log.e(TAG, "Error: what=" + String.valueOf(what) + ", extra=" + String.valueOf(extra));
mState = State.Stopped;
relaxResources(true);
giveUpAudioFocus();
return true; // true indicates we handled the error
}
public void onGainedAudioFocus() {
Toast.makeText(getApplicationContext(), "gained audio focus.", Toast.LENGTH_SHORT).show();
mAudioFocus = AudioFocus.Focused;
// restart media player with new focus settings
if (mState == State.Playing)
configAndStartMediaPlayer();
}
public void onLostAudioFocus(boolean canDuck) {
Toast.makeText(getApplicationContext(), "lost audio focus." + (canDuck ? "can duck" :
"no duck"), Toast.LENGTH_SHORT).show();
mAudioFocus = canDuck ? AudioFocus.NoFocusCanDuck : AudioFocus.NoFocusNoDuck;
// start/restart/pause media player with new focus settings
if (mPlayer != null && mPlayer.isPlaying())
configAndStartMediaPlayer();
}
public void onMusicRetrieverPrepared() {
// Done retrieving!
mState = State.Stopped;
// If the flag indicates we should start playing after retrieving, let's do that now.
if (mStartPlayingAfterRetrieve) {
tryToGetAudioFocus();
playNextSong(mWhatToPlayAfterRetrieve == null ?
null : mWhatToPlayAfterRetrieve.toString());
}
}
@Override
public void onDestroy() {
// Service is being killed, so make sure we release our resources
mState = State.Stopped;
relaxResources(true);
giveUpAudioFocus();
}
@Override
public IBinder onBind(Intent arg0) {
return null;
}
}

View File

@@ -1,50 +0,0 @@
/*
* 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.os.AsyncTask;
/**
* Asynchronous task that prepares a MusicRetriever. This asynchronous task essentially calls
* {@link MusicRetriever#prepare()} on a {@link MusicRetriever}, which may take some time to
* run. Upon finishing, it notifies the indicated {@MusicRetrieverPreparedListener}.
*/
public class PrepareMusicRetrieverTask extends AsyncTask<Void, Void, Void> {
MusicRetriever mRetriever;
MusicRetrieverPreparedListener mListener;
public PrepareMusicRetrieverTask(MusicRetriever retriever,
MusicRetrieverPreparedListener listener) {
mRetriever = retriever;
mListener = listener;
}
@Override
protected Void doInBackground(Void... arg0) {
mRetriever.prepare();
return null;
}
@Override
protected void onPostExecute(Void result) {
mListener.onMusicRetrieverPrepared();
}
public interface MusicRetrieverPreparedListener {
public void onMusicRetrieverPrepared();
}
}

View File

@@ -1,353 +0,0 @@
/*
* 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

@@ -1,87 +0,0 @@
/*
* 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);
}
}
}