diff options
15 files changed, 848 insertions, 2 deletions
diff --git a/MusicDemo/src/main/AndroidManifest.xml b/MusicDemo/src/main/AndroidManifest.xml index f84ee42..58e54c2 100644 --- a/MusicDemo/src/main/AndroidManifest.xml +++ b/MusicDemo/src/main/AndroidManifest.xml @@ -35,6 +35,15 @@ <meta-data android:name="com.google.android.gms.car.application" android:resource="@xml/automotive_app_desc"/> + + <activity android:name=".MusicPlayerActivity" + android:label="@string/app_name"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + <!-- (OPTIONAL) use this meta data to indicate which icon should be used in media notifications (for example, when the music changes and the user is looking at another app) --> diff --git a/MusicDemo/src/main/java/com/example/android/musicservicedemo/BrowseFragment.java b/MusicDemo/src/main/java/com/example/android/musicservicedemo/BrowseFragment.java new file mode 100644 index 0000000..2b98263 --- /dev/null +++ b/MusicDemo/src/main/java/com/example/android/musicservicedemo/BrowseFragment.java @@ -0,0 +1,208 @@ +/* + * Copyright (C) 2014 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.musicservicedemo; + +import android.app.Fragment; +import android.content.ComponentName; +import android.content.Context; +import android.media.browse.MediaBrowser; +import android.media.session.MediaController; +import android.os.Bundle; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.ImageView; +import android.widget.ListView; +import android.widget.TextView; +import android.widget.Toast; + +import com.example.android.musicservicedemo.utils.LogHelper; + +import java.util.ArrayList; +import java.util.List; + +/** + * A Fragment that lists all the various browsable queues available + * from a {@link android.service.media.MediaBrowserService}. + * <p/> + * It uses a {@link MediaBrowser} to connect to the {@link MusicService}. Once connected, + * the fragment subscribes to get all the children. All {@link MediaBrowser.MediaItem}'s + * that can be browsed are shown in a ListView. + */ +public class BrowseFragment extends Fragment { + + private static final String TAG = BrowseFragment.class.getSimpleName(); + + public static final String ARG_MEDIA_ID = "media_id"; + + public static interface FragmentDataHelper { + void onMediaItemSelected(MediaBrowser.MediaItem item); + } + + // The mediaId to be used for subscribing for children using the MediaBrowser. + private String mMediaId; + + private MediaBrowser mMediaBrowser; + private BrowseAdapter mBrowserAdapter; + + private MediaBrowser.SubscriptionCallback mSubscriptionCallback = new MediaBrowser.SubscriptionCallback() { + + @Override + public void onChildrenLoaded(String parentId, List<MediaBrowser.MediaItem> children) { + mBrowserAdapter.clear(); + mBrowserAdapter.notifyDataSetInvalidated(); + for (MediaBrowser.MediaItem item : children) { + LogHelper.d(TAG, "MediaItem: -> " + item.getDescription().getDescription()); + mBrowserAdapter.add(item); + } + mBrowserAdapter.notifyDataSetChanged(); + } + + @Override + public void onError(String id) { + Toast.makeText(getActivity(), R.string.error_loading_media, + Toast.LENGTH_LONG).show(); + } + }; + + private MediaBrowser.ConnectionCallback mConnectionCallback = + new MediaBrowser.ConnectionCallback() { + @Override + public void onConnected() { + LogHelper.d(TAG, "onConnected: session token " + mMediaBrowser.getSessionToken()); + + if (mMediaId == null) { + mMediaId = mMediaBrowser.getRoot(); + } + mMediaBrowser.subscribe(mMediaId, mSubscriptionCallback); + MediaController mediaController = new MediaController(getActivity(), + mMediaBrowser.getSessionToken()); + getActivity().setMediaController(mediaController); + } + + @Override + public void onConnectionFailed() { + LogHelper.d(TAG, "onConnectionFailed"); + } + + @Override + public void onConnectionSuspended() { + LogHelper.d(TAG, "onConnectionSuspended"); + getActivity().setMediaController(null); + } + }; + + public static BrowseFragment newInstance(String mediaId) { + Bundle args = new Bundle(); + args.putString(ARG_MEDIA_ID, mediaId); + BrowseFragment fragment = new BrowseFragment(); + fragment.setArguments(args); + return fragment; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View rootView = inflater.inflate(R.layout.fragment_list, container, false); + + mBrowserAdapter = new BrowseAdapter(getActivity()); + + View controls = rootView.findViewById(R.id.controls); + controls.setVisibility(View.GONE); + + ListView listView = (ListView) rootView.findViewById(R.id.list_view); + listView.setAdapter(mBrowserAdapter); + listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView<?> parent, View view, int position, long id) { + MediaBrowser.MediaItem item = mBrowserAdapter.getItem(position); + try { + FragmentDataHelper listener = (FragmentDataHelper) getActivity(); + listener.onMediaItemSelected(item); + } catch (ClassCastException ex) { + Log.e(TAG, "Exception trying to cast to FragmentDataHelper", ex); + } + } + }); + + Bundle args = getArguments(); + mMediaId = args.getString(ARG_MEDIA_ID, null); + + mMediaBrowser = new MediaBrowser(getActivity(), + new ComponentName(getActivity(), MusicService.class), + mConnectionCallback, null); + + return rootView; + } + + @Override + public void onStart() { + super.onStart(); + mMediaBrowser.connect(); + } + + @Override + public void onStop() { + super.onStop(); + mMediaBrowser.disconnect(); + } + + // An adapter for showing the list of browsed MediaItem's + private static class BrowseAdapter extends ArrayAdapter<MediaBrowser.MediaItem> { + + public BrowseAdapter(Context context) { + super(context, R.layout.list_item, new ArrayList<MediaBrowser.MediaItem>()); + } + + static class ViewHolder { + ImageView mImageView; + TextView mTitleView; + TextView mDescriptionView; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + + ViewHolder holder; + + if (convertView == null) { + convertView = LayoutInflater.from(getContext()) + .inflate(R.layout.list_item, parent, false); + holder = new ViewHolder(); + holder.mImageView = (ImageView) convertView.findViewById(R.id.play_eq); + holder.mImageView.setVisibility(View.GONE); + holder.mTitleView = (TextView) convertView.findViewById(R.id.title); + holder.mDescriptionView = (TextView) convertView.findViewById(R.id.description); + convertView.setTag(holder); + } else { + holder = (ViewHolder) convertView.getTag(); + } + + MediaBrowser.MediaItem item = getItem(position); + holder.mTitleView.setText(item.getDescription().getTitle()); + holder.mDescriptionView.setText(item.getDescription().getDescription()); + if (item.isPlayable()) { + holder.mImageView.setImageDrawable( + getContext().getDrawable(R.drawable.ic_play_arrow_white_24dp)); + holder.mImageView.setVisibility(View.VISIBLE); + } + return convertView; + } + } +} diff --git a/MusicDemo/src/main/java/com/example/android/musicservicedemo/MusicPlayerActivity.java b/MusicDemo/src/main/java/com/example/android/musicservicedemo/MusicPlayerActivity.java new file mode 100644 index 0000000..7a2f8fb --- /dev/null +++ b/MusicDemo/src/main/java/com/example/android/musicservicedemo/MusicPlayerActivity.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2014 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.musicservicedemo; + +import android.app.Activity; +import android.media.browse.MediaBrowser; +import android.media.session.MediaController; +import android.os.Bundle; + +/** + * Main activity for the music player. + */ +public class MusicPlayerActivity extends Activity + implements BrowseFragment.FragmentDataHelper { + + private static final String TAG = MusicPlayerActivity.class.getSimpleName(); + + private MediaBrowser mMediaBrowser; + private MediaController mMediaController; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_player); + if (savedInstanceState == null) { + getFragmentManager().beginTransaction() + .add(R.id.container, BrowseFragment.newInstance(null)) + .commit(); + } + } + + @Override + public void onMediaItemSelected(MediaBrowser.MediaItem item) { + if (item.isPlayable()) { + getMediaController().getTransportControls().playFromMediaId(item.getMediaId(), null); + QueueFragment queueFragment = QueueFragment.newInstance(item.getMediaId()); + getFragmentManager().beginTransaction() + .replace(R.id.container, queueFragment) + .addToBackStack(null) + .commit(); + } else if (item.isBrowsable()) { + getFragmentManager().beginTransaction() + .replace(R.id.container, BrowseFragment.newInstance(item.getMediaId())) + .addToBackStack(null) + .commit(); + } + } +} diff --git a/MusicDemo/src/main/java/com/example/android/musicservicedemo/MusicService.java b/MusicDemo/src/main/java/com/example/android/musicservicedemo/MusicService.java index e085275..c737ba4 100644 --- a/MusicDemo/src/main/java/com/example/android/musicservicedemo/MusicService.java +++ b/MusicDemo/src/main/java/com/example/android/musicservicedemo/MusicService.java @@ -847,6 +847,12 @@ public class MusicService extends MediaBrowserService implements OnPreparedListe } stateBuilder.setState(mState, position, 1.0f, SystemClock.elapsedRealtime()); + // Set the activeQueueItemId if the current index is valid. + if (QueueHelper.isIndexPlayable(mCurrentIndexOnQueue, mPlayingQueue)) { + MediaSession.QueueItem item = mPlayingQueue.get(mCurrentIndexOnQueue); + stateBuilder.setActiveQueueItemId(item.getQueueId()); + } + mSession.setPlaybackState(stateBuilder.build()); if (mState == PlaybackState.STATE_PLAYING || mState == PlaybackState.STATE_PAUSED) { diff --git a/MusicDemo/src/main/java/com/example/android/musicservicedemo/QueueAdapter.java b/MusicDemo/src/main/java/com/example/android/musicservicedemo/QueueAdapter.java new file mode 100644 index 0000000..9c7870b --- /dev/null +++ b/MusicDemo/src/main/java/com/example/android/musicservicedemo/QueueAdapter.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2014 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.musicservicedemo; + +import android.app.Activity; +import android.media.session.MediaSession; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.ImageView; +import android.widget.TextView; + +import java.util.ArrayList; + +/** + * A list adapter for items in a queue + */ +public class QueueAdapter extends ArrayAdapter<MediaSession.QueueItem> { + + // The currently selected/active queue item Id. + private long mActiveQueueItemId = MediaSession.QueueItem.UNKNOWN_ID; + + public QueueAdapter(Activity context) { + super(context, R.layout.list_item, new ArrayList<MediaSession.QueueItem>()); + } + + public void setActiveQueueItemId(long id) { + this.mActiveQueueItemId = id; + } + + private static class ViewHolder { + ImageView mImageView; + TextView mTitleView; + TextView mDescriptionView; + } + + public View getView(int position, View convertView, ViewGroup parent) { + ViewHolder holder; + + if (convertView == null) { + convertView = LayoutInflater.from(getContext()) + .inflate(R.layout.list_item, parent, false); + holder = new ViewHolder(); + holder.mImageView = (ImageView) convertView.findViewById(R.id.play_eq); + holder.mTitleView = (TextView) convertView.findViewById(R.id.title); + holder.mDescriptionView = (TextView) convertView.findViewById(R.id.description); + convertView.setTag(holder); + } else { + holder = (ViewHolder) convertView.getTag(); + } + + MediaSession.QueueItem item = getItem(position); + holder.mTitleView.setText(item.getDescription().getTitle()); + if (item.getDescription().getDescription() != null) { + holder.mDescriptionView.setText(item.getDescription().getDescription()); + } + + // If the itemId matches the active Id then use a different icon + if (mActiveQueueItemId == item.getQueueId()) { + holder.mImageView.setImageDrawable( + getContext().getDrawable(R.drawable.ic_equalizer_white_24dp)); + } else { + holder.mImageView.setImageDrawable( + getContext().getDrawable(R.drawable.ic_play_arrow_white_24dp)); + } + return convertView; + } +} diff --git a/MusicDemo/src/main/java/com/example/android/musicservicedemo/QueueFragment.java b/MusicDemo/src/main/java/com/example/android/musicservicedemo/QueueFragment.java new file mode 100644 index 0000000..233b282 --- /dev/null +++ b/MusicDemo/src/main/java/com/example/android/musicservicedemo/QueueFragment.java @@ -0,0 +1,319 @@ +/* + * Copyright (C) 2014 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.musicservicedemo; + +import android.app.Fragment; +import android.content.ComponentName; +import android.media.MediaMetadata; +import android.media.browse.MediaBrowser; +import android.media.session.MediaController; +import android.media.session.MediaSession; +import android.media.session.PlaybackState; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.ImageButton; +import android.widget.ListView; + +import com.example.android.musicservicedemo.utils.LogHelper; + +import java.util.List; + +/** + * A class that shows the queue to the user + */ +public class QueueFragment extends Fragment { + + private static final String TAG = QueueFragment.class.getSimpleName() + "BLAH"; + private static final String ARGS_MEDIA_ID = "media_id"; + + private ImageButton mSkipNext; + private ImageButton mSkipPrevious; + private ImageButton mPlayPause; + + private MediaBrowser mMediaBrowser; + private MediaController.TransportControls mTransportControls; + private MediaController mMediaController; + private PlaybackState mPlaybackState; + private boolean mConnected; + + private QueueAdapter mQueueAdapter; + private String mMediaId; + private ListView mListView; + + private MediaBrowser.ConnectionCallback mConnectionCallback = + new MediaBrowser.ConnectionCallback() { + @Override + public void onConnected() { + LogHelper.d(TAG, "onConnected: session token " + mMediaBrowser.getSessionToken()); + + mMediaController = new MediaController(getActivity(), + mMediaBrowser.getSessionToken()); + mTransportControls = mMediaController.getTransportControls(); + mMediaController.registerCallback(mSessionCallback); + + getActivity().setMediaController(mMediaController); + mPlaybackState = mMediaController.getPlaybackState(); + + long activeItemId = MediaSession.QueueItem.UNKNOWN_ID; + if (mPlaybackState != null) { + activeItemId = mPlaybackState.getActiveQueueItemId(); + } + mQueueAdapter.setActiveQueueItemId(activeItemId); + + List<MediaSession.QueueItem> queue = mMediaController.getQueue(); + if (queue != null) { + mQueueAdapter.clear(); + mQueueAdapter.notifyDataSetInvalidated(); + mQueueAdapter.addAll(queue); + mQueueAdapter.notifyDataSetChanged(); + } + + LogHelper.d(TAG, "got playback state " + mPlaybackState); + mConnected = true; + onPlaybackStateChanged(mPlaybackState); + } + + @Override + public void onConnectionFailed() { + LogHelper.d(TAG, "onConnectionFailed"); + mConnected = false; + } + + @Override + public void onConnectionSuspended() { + LogHelper.d(TAG, "onConnectionSuspended"); + mMediaController.unregisterCallback(mSessionCallback); + mTransportControls = null; + mConnected = false; + mMediaController = null; + getActivity().setMediaController(null); + } + }; + + // Receive callbacks from the MediaController. Here we update our state such as which queue + // is being shown, the current title and description and the PlaybackState. + private MediaController.Callback mSessionCallback = new MediaController.Callback() { + + @Override + public void onSessionDestroyed() { + LogHelper.d(TAG, "Session destroyed. Need to fetch a new Media Session"); + } + + @Override + public void onPlaybackStateChanged(PlaybackState state) { + if (state == null) { + return; + } + LogHelper.d(TAG, "Received playback state change to state " + state.getState()); + mPlaybackState = state; + QueueFragment.this.onPlaybackStateChanged(state); + } + + @Override + public void onQueueChanged(List<MediaSession.QueueItem> queue) { + LogHelper.d(TAG, "onQueueChanged " + queue); + if (queue != null) { + mQueueAdapter.clear(); + mQueueAdapter.notifyDataSetInvalidated(); + mQueueAdapter.addAll(queue); + mQueueAdapter.notifyDataSetChanged(); + } + } + }; + + public static QueueFragment newInstance(String mediaId) { + QueueFragment fragment = new QueueFragment(); + Bundle args = new Bundle(); + args.putString(ARGS_MEDIA_ID, mediaId); + fragment.setArguments(args); + return fragment; + } + + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View rootView = inflater.inflate(R.layout.fragment_list, container, false); + + mSkipPrevious = (ImageButton) rootView.findViewById(R.id.skip_previous); + mSkipPrevious.setEnabled(false); + mSkipPrevious.setOnClickListener(mButtonListener); + + mSkipNext = (ImageButton) rootView.findViewById(R.id.skip_next); + mSkipNext.setEnabled(false); + mSkipNext.setOnClickListener(mButtonListener); + + mPlayPause = (ImageButton) rootView.findViewById(R.id.play_pause); + mPlayPause.setEnabled(true); + mPlayPause.setOnClickListener(mButtonListener); + + mQueueAdapter = new QueueAdapter(getActivity()); + + mListView = (ListView) rootView.findViewById(R.id.list_view); + mListView.setAdapter(mQueueAdapter); + mListView.setFocusable(true); + mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView<?> parent, View view, int position, long id) { + MediaSession.QueueItem item = mQueueAdapter.getItem(position); + mTransportControls.skipToQueueItem(item.getQueueId()); + mQueueAdapter.setActiveQueueItemId(item.getQueueId()); + mQueueAdapter.notifyDataSetChanged(); + } + }); + + Bundle args = getArguments(); + mMediaId = args.getString(ARGS_MEDIA_ID); + + mMediaBrowser = new MediaBrowser(getActivity(), + new ComponentName(getActivity(), MusicService.class), + mConnectionCallback, null); + + return rootView; + } + + + @Override + public void onResume() { + super.onResume(); + if (mMediaBrowser != null) { + mMediaBrowser.connect(); + } + } + + @Override + public void onPause() { + super.onPause(); + if (mMediaController != null) { + mMediaController.unregisterCallback(mSessionCallback); + } + if (mMediaBrowser != null) { + mMediaBrowser.disconnect(); + } + } + + + private void onPlaybackStateChanged(PlaybackState state) { + LogHelper.d(TAG, "onPlaybackStateChanged " + state); + if (state == null) { + return; + } + mQueueAdapter.setActiveQueueItemId(state.getActiveQueueItemId()); + mQueueAdapter.notifyDataSetChanged(); + boolean enablePlay = false; + StringBuilder statusBuilder = new StringBuilder(); + switch (state.getState()) { + case PlaybackState.STATE_PLAYING: + statusBuilder.append("playing"); + enablePlay = false; + break; + case PlaybackState.STATE_PAUSED: + statusBuilder.append("paused"); + enablePlay = true; + break; + case PlaybackState.STATE_STOPPED: + statusBuilder.append("ended"); + enablePlay = true; + break; + case PlaybackState.STATE_ERROR: + statusBuilder.append("error: ").append(state.getErrorMessage()); + break; + case PlaybackState.STATE_BUFFERING: + statusBuilder.append("buffering"); + break; + case PlaybackState.STATE_NONE: + statusBuilder.append("none"); + enablePlay = false; + break; + case PlaybackState.STATE_CONNECTING: + statusBuilder.append("connecting"); + break; + default: + statusBuilder.append(mPlaybackState); + } + statusBuilder.append(" -- At position: ").append(state.getPosition()); + LogHelper.d(TAG, statusBuilder.toString()); + + if (enablePlay) { + mPlayPause.setImageDrawable( + getActivity().getDrawable(R.drawable.ic_play_arrow_white_24dp)); + } else { + mPlayPause.setImageDrawable(getActivity().getDrawable(R.drawable.ic_pause_white_24dp)); + } + + mSkipPrevious.setEnabled((state.getActions() & PlaybackState.ACTION_SKIP_TO_PREVIOUS) != 0); + mSkipNext.setEnabled((state.getActions() & PlaybackState.ACTION_SKIP_TO_NEXT) != 0); + + LogHelper.d(TAG, "Queue From MediaController *** Title " + + mMediaController.getQueueTitle() + "\n: Queue: " + mMediaController.getQueue() + + "\n Metadata " + mMediaController.getMetadata()); + } + + private View.OnClickListener mButtonListener = new View.OnClickListener() { + @Override + public void onClick(View v) { + final int state = mPlaybackState == null ? + PlaybackState.STATE_NONE : mPlaybackState.getState(); + switch (v.getId()) { + case R.id.play_pause: + LogHelper.d(TAG, "Play button pressed, in state " + state); + if (state == PlaybackState.STATE_PAUSED || + state == PlaybackState.STATE_STOPPED || + state == PlaybackState.STATE_NONE) { + playMedia(); + } else if (state == PlaybackState.STATE_PLAYING) { + pauseMedia(); + } + break; + case R.id.skip_previous: + LogHelper.d(TAG, "Start button pressed, in state " + state); + skipToPrevious(); + break; + case R.id.skip_next: + skipToNext(); + break; + } + + } + }; + + private void playMedia() { + if (mTransportControls != null) { + mTransportControls.play(); + } + } + + private void pauseMedia() { + if (mTransportControls != null) { + mTransportControls.pause(); + } + } + + private void skipToPrevious() { + if (mTransportControls != null) { + mTransportControls.skipToPrevious(); + } + } + + private void skipToNext() { + if (mTransportControls != null) { + mTransportControls.skipToNext(); + } + } +} diff --git a/MusicDemo/src/main/java/com/example/android/musicservicedemo/utils/QueueHelper.java b/MusicDemo/src/main/java/com/example/android/musicservicedemo/utils/QueueHelper.java index 4dc7a96..9a510fb 100644 --- a/MusicDemo/src/main/java/com/example/android/musicservicedemo/utils/QueueHelper.java +++ b/MusicDemo/src/main/java/com/example/android/musicservicedemo/utils/QueueHelper.java @@ -126,4 +126,4 @@ public class QueueHelper { public static final boolean isIndexPlayable(int index, List<MediaSession.QueueItem> queue) { return (queue != null && index >= 0 && index < queue.size()); } -}
\ No newline at end of file +} diff --git a/MusicDemo/src/main/res/drawable-xhdpi/ic_equalizer_white_24dp.png b/MusicDemo/src/main/res/drawable-xhdpi/ic_equalizer_white_24dp.png Binary files differnew file mode 100644 index 0000000..dbba844 --- /dev/null +++ b/MusicDemo/src/main/res/drawable-xhdpi/ic_equalizer_white_24dp.png diff --git a/MusicDemo/src/main/res/drawable-xxhdpi/ic_equalizer_white_24dp.png b/MusicDemo/src/main/res/drawable-xxhdpi/ic_equalizer_white_24dp.png Binary files differnew file mode 100644 index 0000000..b82a8d9 --- /dev/null +++ b/MusicDemo/src/main/res/drawable-xxhdpi/ic_equalizer_white_24dp.png diff --git a/MusicDemo/src/main/res/layout/activity_player.xml b/MusicDemo/src/main/res/layout/activity_player.xml new file mode 100644 index 0000000..21cdbbd --- /dev/null +++ b/MusicDemo/src/main/res/layout/activity_player.xml @@ -0,0 +1,22 @@ +<!-- + Copyright (C) 2014 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. + --> +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/container" + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:context=".MusicPlayerActivity" + tools:ignore="MergeRootFrame" /> diff --git a/MusicDemo/src/main/res/layout/fragment_list.xml b/MusicDemo/src/main/res/layout/fragment_list.xml new file mode 100644 index 0000000..a40240f --- /dev/null +++ b/MusicDemo/src/main/res/layout/fragment_list.xml @@ -0,0 +1,60 @@ +<!-- + Copyright (C) 2014 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="match_parent" + android:layout_height="match_parent" + android:padding="16dp"> + + <LinearLayout + android:id="@+id/controls" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal"> + + <ImageButton + android:id="@+id/skip_previous" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="1" + android:src="@drawable/ic_skip_previous_white_24dp" + android:contentDescription="@string/skip_previous"/> + + <ImageButton + android:id="@+id/play_pause" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="1" + android:src="@drawable/ic_play_arrow_white_24dp" + android:contentDescription="@string/play_pause"/> + + <ImageButton + android:id="@+id/skip_next" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="1" + android:src="@drawable/ic_skip_next_white_24dp" + android:contentDescription="@string/skip_next"/> + + </LinearLayout> + + <ListView + android:id="@+id/list_view" + android:layout_width="match_parent" + android:layout_height="match_parent"> + </ListView> + +</LinearLayout> diff --git a/MusicDemo/src/main/res/layout/list_item.xml b/MusicDemo/src/main/res/layout/list_item.xml new file mode 100644 index 0000000..729628f --- /dev/null +++ b/MusicDemo/src/main/res/layout/list_item.xml @@ -0,0 +1,55 @@ +<!-- + Copyright (C) 2014 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:layout_width="match_parent" + android:layout_height="wrap_content" + android:minHeight="?android:listPreferredItemHeight" + android:orientation="horizontal"> + + <ImageView + android:id="@+id/play_eq" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:contentDescription="@string/play_item" + android:src="@drawable/ic_play_arrow_white_24dp"/> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:minHeight="?android:attr/listPreferredItemHeight" + android:mode="twoLine" + android:padding="4dp" + android:orientation="vertical"> + + <TextView + android:id="@+id/title" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/margin_text_view" + android:layout_marginTop="@dimen/margin_text_view" + android:textAppearance="?android:attr/textAppearanceMedium"/> + + <TextView + android:id="@+id/description" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/margin_text_view" + android:layout_marginTop="@dimen/margin_text_view" + android:textAppearance="?android:attr/textAppearanceSmall"/> + + </LinearLayout> + +</LinearLayout> diff --git a/MusicDemo/src/main/res/values-v21/styles.xml b/MusicDemo/src/main/res/values-v21/styles.xml index 602ce1c..21bb211 100644 --- a/MusicDemo/src/main/res/values-v21/styles.xml +++ b/MusicDemo/src/main/res/values-v21/styles.xml @@ -16,7 +16,7 @@ --> <resources> - <style name="AppBaseTheme" parent="android:Theme.Light"> + <style name="AppBaseTheme" parent="android:Theme.Material"> <!-- colorPrimary is used for Notification icon and bottom facet bar icons and overflow actions --> <item name="android:colorPrimary">#ffff5722</item> diff --git a/MusicDemo/src/main/res/values/dimens.xml b/MusicDemo/src/main/res/values/dimens.xml new file mode 100644 index 0000000..f905809 --- /dev/null +++ b/MusicDemo/src/main/res/values/dimens.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2014 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> + <dimen name="margin_text_view">6dip</dimen> +</resources> diff --git a/MusicDemo/src/main/res/values/strings.xml b/MusicDemo/src/main/res/values/strings.xml index 82e07b0..7a012e8 100644 --- a/MusicDemo/src/main/res/values/strings.xml +++ b/MusicDemo/src/main/res/values/strings.xml @@ -24,5 +24,10 @@ <string name="browse_musics_by_genre_subtitle">%1$s songs</string> <string name="random_queue_title">Random music</string> <string name="error_cannot_skip">Cannot skip</string> + <string name="error_loading_media">Error Loading Media</string> + <string name="play_item">Play item</string> + <string name="skip_previous">Skip to previous</string> + <string name="play_pause">play or pause</string> + <string name="skip_next">Skip to next</string> </resources> |