Import new sample.

Test: Manually verified.
Bug: 66707949

Change-Id: I63bc713a0793a27a6a9ebfaa09f324411aeefd76
diff --git a/media/MediaBrowserService/Application/.gitignore b/media/MediaBrowserService/Application/.gitignore
deleted file mode 100644
index 6eb878d..0000000
--- a/media/MediaBrowserService/Application/.gitignore
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright 2013 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.
-src/template/
-src/common/
-build.gradle
diff --git a/media/MediaBrowserService/Application/build.gradle b/media/MediaBrowserService/Application/build.gradle
new file mode 100644
index 0000000..3621f95
--- /dev/null
+++ b/media/MediaBrowserService/Application/build.gradle
@@ -0,0 +1,70 @@
+
+buildscript {
+    repositories {
+        jcenter()
+        maven {
+            url 'https://maven.google.com'
+        }
+    }
+
+    dependencies {
+        classpath 'com.android.tools.build:gradle:2.3.3'
+    }
+}
+
+apply plugin: 'com.android.application'
+
+repositories {
+    jcenter()
+    maven {
+        url 'https://maven.google.com'
+    }
+}
+
+dependencies {
+
+    compile "com.android.support:support-v4:26.1.0"
+    compile "com.android.support:support-v13:26.1.0"
+    compile "com.android.support:cardview-v7:26.1.0"
+
+    compile 'com.android.support:appcompat-v7:26.1.0'
+    compile 'com.android.support.constraint:constraint-layout:1.0.2'
+}
+
+// The sample build uses multiple directories to
+// keep boilerplate and common code separate from
+// the main sample code.
+List<String> dirs = [
+    'main',     // main sample code; look here for the interesting stuff.
+    'common',   // components that are reused by multiple samples
+    'template'] // boilerplate code that is generated by the sample template process
+
+android {
+        compileSdkVersion 26
+
+    buildToolsVersion "26.0.1"
+
+    defaultConfig {
+        minSdkVersion 19
+        targetSdkVersion 26
+
+    }
+
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_7
+        targetCompatibility JavaVersion.VERSION_1_7
+    }
+
+    sourceSets {
+        main {
+            dirs.each { dir ->
+                java.srcDirs "src/${dir}/java"
+                res.srcDirs "src/${dir}/res"
+            }
+        }
+        androidTest.setRoot('tests')
+        androidTest.java.srcDirs = ['tests/src']
+
+    }
+
+}
diff --git a/media/MediaBrowserService/Application/proguard-project.txt b/media/MediaBrowserService/Application/proguard-project.txt
deleted file mode 100644
index 0d8f171..0000000
--- a/media/MediaBrowserService/Application/proguard-project.txt
+++ /dev/null
@@ -1,20 +0,0 @@
- To enable ProGuard in your project, edit project.properties
-# to define the proguard.config property as described in that file.
-#
-# Add project specific ProGuard rules here.
-# By default, the flags in this file are appended to flags specified
-# in ${sdk.dir}/tools/proguard/proguard-android.txt
-# You can edit the include path and order by changing the ProGuard
-# include property in project.properties.
-#
-# For more details, see
-#   http://developer.android.com/guide/developing/tools/proguard.html
-
-# Add any project specific keep options here:
-
-# If your project uses WebView with JS, uncomment the following
-# and specify the fully qualified class name to the JavaScript interface
-# class:
-#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
-#   public *;
-#}
diff --git a/media/MediaBrowserService/Application/proguard-rules.pro b/media/MediaBrowserService/Application/proguard-rules.pro
new file mode 100644
index 0000000..a16aac3
--- /dev/null
+++ b/media/MediaBrowserService/Application/proguard-rules.pro
@@ -0,0 +1,25 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /Users/nazmul/Library/Android/sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/media/MediaBrowserService/Application/src/androidTest/java/com/example/android/mediabrowserservice/test/SampleTests.java b/media/MediaBrowserService/Application/src/androidTest/java/com/example/android/mediabrowserservice/test/SampleTests.java
deleted file mode 100644
index 665170d..0000000
--- a/media/MediaBrowserService/Application/src/androidTest/java/com/example/android/mediabrowserservice/test/SampleTests.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
-* Copyright 2013 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.
-*/
-/*
-* 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.mediabrowserservice.test;
-
-import android.test.ActivityInstrumentationTestCase2;
-
-/**
-* Tests for MediaBrowserService sample.
-*/
-public class SampleTests extends ActivityInstrumentationTestCase2<MainActivity> {
-
-    private MainActivity mTestActivity;
-    private MediaBrowserServiceFragment mTestFragment;
-
-    public SampleTests() {
-        super(MainActivity.class);
-    }
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-
-        // Starts the activity under test using the default Intent with:
-        // action = {@link Intent#ACTION_MAIN}
-        // flags = {@link Intent#FLAG_ACTIVITY_NEW_TASK}
-        // All other fields are null or empty.
-        mTestActivity = getActivity();
-        mTestFragment = (MediaBrowserServiceFragment)
-            mTestActivity.getSupportFragmentManager().getFragments().get(1);
-    }
-
-    /**
-    * Test if the test fixture has been set up correctly.
-    */
-    public void testPreconditions() {
-        //Try to add a message to add context to your assertions. These messages will be shown if
-        //a tests fails and make it easy to understand why a test failed
-        assertNotNull("mTestActivity is null", mTestActivity);
-        assertNotNull("mTestFragment is null", mTestFragment);
-    }
-
-    /**
-    * Add more tests below.
-    */
-
-}
diff --git a/media/MediaBrowserService/Application/src/main/AndroidManifest.xml b/media/MediaBrowserService/Application/src/main/AndroidManifest.xml
index 570aeb4..6af29f0 100644
--- a/media/MediaBrowserService/Application/src/main/AndroidManifest.xml
+++ b/media/MediaBrowserService/Application/src/main/AndroidManifest.xml
@@ -1,70 +1,48 @@
-<?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.
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2017 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"
-          xmlns:tools="http://schemas.android.com/tools"
-          package="com.example.android.mediabrowserservice"
-    android:versionCode="1"
-    android:versionName="1.0">
 
-    <uses-permission android:name="android.permission.INTERNET" />
-    <uses-permission android:name="android.permission.WAKE_LOCK" />
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.example.android.mediasession">
+
+    <uses-permission android:name="android.permission.INTERNET"/>
+    <uses-permission android:name="android.permission.WAKE_LOCK"/>
 
     <application
-        android:allowBackup="true"
-        android:icon="@drawable/ic_launcher"
-        android:label="@string/app_name"
-        android:theme="@style/AppTheme"
-        tools:ignore="GoogleAppIndexingWarning,MissingIntentFilterForMediaSearch">
-
-        <meta-data
-            android:name="com.google.android.gms.car.application"
-            android:resource="@xml/automotive_app_desc" />
-
-
-        <activity
-            android:name=".MusicPlayerActivity"
+            android:allowBackup="true"
+            android:icon="@mipmap/ic_launcher"
             android:label="@string/app_name"
-            android:launchMode="singleTop">
+            android:supportsRtl="true"
+            android:theme="@style/AppTheme">
+        <activity
+                android:name=".ui.MainActivity"
+                android:launchMode="singleTop">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <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) -->
-        <meta-data
-            android:name="com.google.android.gms.car.notification.SmallIcon"
-            android:resource="@drawable/ic_notification" />
-
-        <!--
-             (OPTIONAL) use this meta data to override the theme from which Android Auto will
-             look for colors. If you don't set this, Android Auto will look
-             for color attributes in your application theme.
-        -->
-        <meta-data
-            android:name="com.google.android.gms.car.application.theme"
-            android:resource="@style/CarTheme" />
-
         <service
-            android:name=".MusicService"
-            android:exported="true">
+                android:name=".service.MusicService"
+                android:enabled="true"
+                android:exported="true">
             <intent-filter>
-                <action android:name="android.media.browse.MediaBrowserService" />
+                <action android:name="android.media.browse.MediaBrowserService"/>
             </intent-filter>
         </service>
 
@@ -78,10 +56,9 @@
         -->
         <receiver android:name="android.support.v4.media.session.MediaButtonReceiver">
             <intent-filter>
-                <action android:name="android.intent.action.MEDIA_BUTTON" />
+                <action android:name="android.intent.action.MEDIA_BUTTON"/>
             </intent-filter>
         </receiver>
-
     </application>
 
-</manifest>
+</manifest>
\ No newline at end of file
diff --git a/media/MediaBrowserService/Application/src/main/assets/jazz_in_paris.mp3 b/media/MediaBrowserService/Application/src/main/assets/jazz_in_paris.mp3
new file mode 100644
index 0000000..9a98517
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/assets/jazz_in_paris.mp3
Binary files differ
diff --git a/media/MediaBrowserService/Application/src/main/assets/the_coldest_shoulder.mp3 b/media/MediaBrowserService/Application/src/main/assets/the_coldest_shoulder.mp3
new file mode 100644
index 0000000..5f043d5
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/assets/the_coldest_shoulder.mp3
Binary files differ
diff --git a/media/MediaBrowserService/Application/src/main/java/com/example/android/mediabrowserservice/AlbumArtCache.java b/media/MediaBrowserService/Application/src/main/java/com/example/android/mediabrowserservice/AlbumArtCache.java
deleted file mode 100644
index 0f6f13f..0000000
--- a/media/MediaBrowserService/Application/src/main/java/com/example/android/mediabrowserservice/AlbumArtCache.java
+++ /dev/null
@@ -1,187 +0,0 @@
-/*
- * 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.mediabrowserservice;
-
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.os.AsyncTask;
-import android.util.Log;
-import android.util.LruCache;
-
-import java.io.BufferedInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.HttpURLConnection;
-import java.net.URL;
-
-/**
- * Implements a basic cache of album arts, with async loading support.
- */
-public final class AlbumArtCache {
-    private static final String TAG = AlbumArtCache.class.getSimpleName();
-
-    /**
-     * Listener for downloading album art.
-     */
-    public abstract static class FetchListener {
-        public abstract void onFetched(String artUrl, Bitmap bigImage, Bitmap iconImage);
-
-        public void onError(String artUrl, Exception e) {
-            Log.e(TAG, "AlbumArtFetchListener: error while downloading " + artUrl, e);
-        }
-    }
-
-    // Max read limit that we allow our input stream to mark/reset.
-    private static final int MAX_READ_LIMIT_PER_IMG = 1024 * 1024;
-
-    private static final int MAX_ALBUM_ART_CACHE_SIZE = 12 * 1024 * 1024;  // 12 MB
-    private static final int MAX_ART_WIDTH = 800;  // pixels
-    private static final int MAX_ART_HEIGHT = 480;  // pixels
-
-    // Resolution reasonable for carrying around as an icon (generally in
-    // MediaDescription.getIconBitmap). This should not be bigger than necessary, because
-    // the MediaDescription object should be lightweight. If you set it too high and try to
-    // serialize the MediaDescription, you may get FAILED BINDER TRANSACTION errors.
-    private static final int MAX_ART_WIDTH_ICON = 128;  // pixels
-    private static final int MAX_ART_HEIGHT_ICON = 128;  // pixels
-
-    private static final int BIG_BITMAP_INDEX = 0;
-    private static final int ICON_BITMAP_INDEX = 1;
-
-    private final LruCache<String, Bitmap[]> mCache;
-
-    private static final AlbumArtCache sInstance = new AlbumArtCache();
-
-    public static AlbumArtCache getInstance() {
-        return sInstance;
-    }
-
-    private AlbumArtCache() {
-        // Holds no more than MAX_ALBUM_ART_CACHE_SIZE bytes, bounded by maxmemory/4 and
-        // Integer.MAX_VALUE:
-        int maxSize = Math.min(MAX_ALBUM_ART_CACHE_SIZE,
-                (int) (Math.min(Integer.MAX_VALUE, Runtime.getRuntime().maxMemory() / 4)));
-        mCache = new LruCache<String, Bitmap[]>(maxSize) {
-            @Override
-            protected int sizeOf(String key, Bitmap[] value) {
-                return value[BIG_BITMAP_INDEX].getByteCount()
-                        + value[ICON_BITMAP_INDEX].getByteCount();
-            }
-        };
-    }
-
-    public Bitmap getBigImage(String artUrl) {
-        Bitmap[] result = mCache.get(artUrl);
-        return result == null ? null : result[BIG_BITMAP_INDEX];
-    }
-
-    public Bitmap getIconImage(String artUrl) {
-        Bitmap[] result = mCache.get(artUrl);
-        return result == null ? null : result[ICON_BITMAP_INDEX];
-    }
-
-    public void fetch(final String artUrl, final FetchListener listener) {
-        // WARNING: for the sake of simplicity, simultaneous multi-thread fetch requests
-        // are not handled properly: they may cause redundant costly operations, like HTTP
-        // requests and bitmap rescales. For production-level apps, we recommend you use
-        // a proper image loading library, like Glide.
-        Bitmap[] bitmap = mCache.get(artUrl);
-        if (bitmap != null) {
-            Log.d(TAG, "getOrFetch: album art is in cache, using it: " + artUrl);
-            listener.onFetched(artUrl, bitmap[BIG_BITMAP_INDEX], bitmap[ICON_BITMAP_INDEX]);
-            return;
-        }
-        Log.d(TAG, "getOrFetch: starting asynctask to fetch " + artUrl);
-
-        new AsyncTask<Void, Void, Bitmap[]>() {
-            @Override
-            protected Bitmap[] doInBackground(Void[] objects) {
-                Bitmap[] bitmaps;
-                try {
-                    Bitmap bitmap = fetchAndRescaleBitmap(artUrl, MAX_ART_WIDTH, MAX_ART_HEIGHT);
-                    Bitmap icon = scaleBitmap(bitmap, MAX_ART_WIDTH_ICON, MAX_ART_HEIGHT_ICON);
-                    bitmaps = new Bitmap[]{bitmap, icon};
-                    mCache.put(artUrl, bitmaps);
-                } catch (IOException e) {
-                    return null;
-                }
-                Log.d(TAG, "doInBackground: putting bitmap in cache. cache size=" + mCache.size());
-                return bitmaps;
-            }
-
-            @Override
-            protected void onPostExecute(Bitmap[] bitmaps) {
-                if (bitmaps == null) {
-                    listener.onError(artUrl, new IllegalArgumentException("got null bitmaps"));
-                } else {
-                    listener.onFetched(artUrl,
-                            bitmaps[BIG_BITMAP_INDEX], bitmaps[ICON_BITMAP_INDEX]);
-                }
-            }
-        }.execute();
-    }
-
-    private Bitmap scaleBitmap(Bitmap src, int maxWidth, int maxHeight) {
-        double scaleFactor = Math.min(
-                ((double) maxWidth) / src.getWidth(), ((double) maxHeight) / src.getHeight());
-        return Bitmap.createScaledBitmap(src,
-                (int) (src.getWidth() * scaleFactor), (int) (src.getHeight() * scaleFactor), false);
-    }
-
-    private Bitmap scaleBitmap(int scaleFactor, InputStream inputStream) {
-        // Get the dimensions of the bitmap
-        BitmapFactory.Options bitmapOptions = new BitmapFactory.Options();
-
-        // Decode the image file into a Bitmap sized to fill the View
-        bitmapOptions.inJustDecodeBounds = false;
-        bitmapOptions.inSampleSize = scaleFactor;
-
-        return BitmapFactory.decodeStream(inputStream, null, bitmapOptions);
-    }
-
-    private int findScaleFactor(int targetWidth, int targetHeight, InputStream inputStream) {
-        // Get the dimensions of the bitmap
-        BitmapFactory.Options bitmapOptions = new BitmapFactory.Options();
-        bitmapOptions.inJustDecodeBounds = true;
-        BitmapFactory.decodeStream(inputStream, null, bitmapOptions);
-        int actualWidth = bitmapOptions.outWidth;
-        int actualHeight = bitmapOptions.outHeight;
-
-        // Determine how much to scale down the image
-        return Math.min(actualWidth / targetWidth, actualHeight / targetHeight);
-    }
-
-    private Bitmap fetchAndRescaleBitmap(String uri, int width, int height)
-            throws IOException {
-        URL url = new URL(uri);
-        BufferedInputStream inputStream = null;
-        try {
-            HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
-            inputStream = new BufferedInputStream(urlConnection.getInputStream());
-            inputStream.mark(MAX_READ_LIMIT_PER_IMG);
-            int scaleFactor = findScaleFactor(width, height, inputStream);
-            Log.d(TAG, "Scaling bitmap " + uri + " by factor " + scaleFactor + " to support "
-                    + width + "x" + height + "requested dimension");
-            inputStream.reset();
-            return scaleBitmap(scaleFactor, inputStream);
-        } finally {
-            if (inputStream != null) {
-                inputStream.close();
-            }
-        }
-    }
-}
diff --git a/media/MediaBrowserService/Application/src/main/java/com/example/android/mediabrowserservice/BrowseFragment.java b/media/MediaBrowserService/Application/src/main/java/com/example/android/mediabrowserservice/BrowseFragment.java
deleted file mode 100644
index 2e55cd8..0000000
--- a/media/MediaBrowserService/Application/src/main/java/com/example/android/mediabrowserservice/BrowseFragment.java
+++ /dev/null
@@ -1,266 +0,0 @@
-/*
- * 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.mediabrowserservice;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.os.Bundle;
-import android.os.RemoteException;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.v4.app.Fragment;
-import android.support.v4.media.MediaBrowserCompat;
-import android.support.v4.media.MediaMetadataCompat;
-import android.support.v4.media.session.MediaControllerCompat;
-import android.support.v4.media.session.PlaybackStateCompat;
-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 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 MediaBrowserCompat} to connect to the {@link MusicService}. Once connected,
- * the fragment subscribes to get all the children. All {@link MediaBrowserCompat.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";
-
-    /**
-     * Interface between BrowseFragment and MusicPlayerActivity.
-     */
-    public interface FragmentDataHelper {
-        void onMediaItemSelected(MediaBrowserCompat.MediaItem item, boolean isPlaying);
-    }
-
-    // The mediaId to be used for subscribing for children using the MediaBrowser.
-    private String mMediaId;
-
-    private MediaBrowserCompat mMediaBrowser;
-    private BrowseAdapter mBrowserAdapter;
-
-    private MediaBrowserCompat.SubscriptionCallback mSubscriptionCallback =
-            new MediaBrowserCompat.SubscriptionCallback() {
-
-                @Override
-                public void onChildrenLoaded(String parentId,
-                                             List<MediaBrowserCompat.MediaItem> children) {
-                    mBrowserAdapter.clear();
-                    mBrowserAdapter.notifyDataSetInvalidated();
-                    for (MediaBrowserCompat.MediaItem item : children) {
-                        mBrowserAdapter.add(item);
-                    }
-                    mBrowserAdapter.notifyDataSetChanged();
-                }
-
-                @Override
-                public void onError(String id) {
-                    Toast.makeText(getActivity(), R.string.error_loading_media,
-                            Toast.LENGTH_LONG).show();
-                }
-            };
-
-    private MediaBrowserCompat.ConnectionCallback mConnectionCallback =
-            new MediaBrowserCompat.ConnectionCallback() {
-                @Override
-                public void onConnected() {
-                    Log.d(TAG, "onConnected: session token " + mMediaBrowser.getSessionToken());
-
-                    if (mMediaId == null) {
-                        mMediaId = mMediaBrowser.getRoot();
-                    }
-                    mMediaBrowser.subscribe(mMediaId, mSubscriptionCallback);
-                    try {
-                        MediaControllerCompat mediaController =
-                                new MediaControllerCompat(getActivity(),
-                                        mMediaBrowser.getSessionToken());
-                        MediaControllerCompat.setMediaController(getActivity(), mediaController);
-
-                        // Register a Callback to stay in sync
-                        mediaController.registerCallback(mControllerCallback);
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "Failed to connect to MediaController", e);
-                    }
-                }
-
-                @Override
-                public void onConnectionFailed() {
-                    Log.e(TAG, "onConnectionFailed");
-                }
-
-                @Override
-                public void onConnectionSuspended() {
-                    Log.d(TAG, "onConnectionSuspended");
-                    MediaControllerCompat mediaController = MediaControllerCompat
-                            .getMediaController(getActivity());
-                    if (mediaController != null) {
-                        mediaController.unregisterCallback(mControllerCallback);
-                        MediaControllerCompat.setMediaController(getActivity(), null);
-                    }
-                }
-            };
-
-    private MediaControllerCompat.Callback mControllerCallback =
-            new MediaControllerCompat.Callback() {
-                @Override
-                public void onMetadataChanged(MediaMetadataCompat metadata) {
-                    if (metadata != null) {
-                        mBrowserAdapter.setCurrentMediaMetadata(metadata);
-                    }
-                }
-
-                @Override
-                public void onPlaybackStateChanged(PlaybackStateCompat state) {
-                    mBrowserAdapter.setPlaybackState(state);
-                    mBrowserAdapter.notifyDataSetChanged();
-                }
-            };
-
-    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());
-
-        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) {
-                MediaBrowserCompat.MediaItem item = mBrowserAdapter.getItem(position);
-                boolean isPlaying = item.getMediaId().equals(mBrowserAdapter.getPlayingMediaId());
-                try {
-                    FragmentDataHelper listener = (FragmentDataHelper) getActivity();
-                    listener.onMediaItemSelected(item, isPlaying);
-                } 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 MediaBrowserCompat(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<MediaBrowserCompat.MediaItem> {
-        private String mCurrentMediaId;
-        private PlaybackStateCompat mPlaybackState;
-
-        public BrowseAdapter(Context context) {
-            super(context, R.layout.media_list_item, new ArrayList<MediaBrowserCompat.MediaItem>());
-        }
-
-        @Nullable
-        public String getPlayingMediaId() {
-            boolean isPlaying = mPlaybackState != null
-                    && mPlaybackState.getState() == PlaybackStateCompat.STATE_PLAYING;
-            return isPlaying ? mCurrentMediaId : null;
-        }
-
-        private void setCurrentMediaMetadata(MediaMetadataCompat mediaMetadata) {
-            mCurrentMediaId = mediaMetadata != null
-                    ? mediaMetadata.getString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID)
-                    : null;
-        }
-
-        private void setPlaybackState(PlaybackStateCompat playbackState) {
-            mPlaybackState = playbackState;
-        }
-
-        static class ViewHolder {
-            ImageView mImageView;
-            TextView mTitleView;
-            TextView mDescriptionView;
-        }
-
-        @NonNull
-        @Override
-        public View getView(int position, View convertView, @NonNull ViewGroup parent) {
-
-            ViewHolder holder;
-
-            if (convertView == null) {
-                convertView = LayoutInflater.from(getContext())
-                        .inflate(R.layout.media_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();
-            }
-
-            MediaBrowserCompat.MediaItem item = getItem(position);
-            holder.mTitleView.setText(item.getDescription().getTitle());
-            holder.mDescriptionView.setText(item.getDescription().getDescription());
-            if (item.isPlayable()) {
-                int playRes = item.getMediaId().equals(getPlayingMediaId())
-                        ? R.drawable.ic_equalizer_white_24dp
-                        : R.drawable.ic_play_arrow_white_24dp;
-                holder.mImageView.setImageDrawable(getContext().getResources()
-                        .getDrawable(playRes));
-                holder.mImageView.setVisibility(View.VISIBLE);
-            }
-            return convertView;
-        }
-
-    }
-}
diff --git a/media/MediaBrowserService/Application/src/main/java/com/example/android/mediabrowserservice/MediaNotificationHelper.java b/media/MediaBrowserService/Application/src/main/java/com/example/android/mediabrowserservice/MediaNotificationHelper.java
deleted file mode 100644
index 4cda405..0000000
--- a/media/MediaBrowserService/Application/src/main/java/com/example/android/mediabrowserservice/MediaNotificationHelper.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * 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.mediabrowserservice;
-
-import android.app.Notification;
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.support.v4.media.MediaDescriptionCompat;
-import android.support.v4.media.MediaMetadataCompat;
-import android.support.v4.media.session.MediaButtonReceiver;
-import android.support.v4.media.session.MediaControllerCompat;
-import android.support.v4.media.session.MediaSessionCompat;
-import android.support.v4.media.session.PlaybackStateCompat;
-import android.support.v7.app.NotificationCompat;
-
-/**
- * Helper class for building Media style Notifications from a
- * {@link android.support.v4.media.session.MediaSessionCompat}.
- */
-public class MediaNotificationHelper {
-    private MediaNotificationHelper() {
-        // Helper utility class; do not instantiate.
-    }
-
-    public static Notification createNotification(Context context,
-                                                  MediaSessionCompat mediaSession) {
-        MediaControllerCompat controller = mediaSession.getController();
-        MediaMetadataCompat mMetadata = controller.getMetadata();
-        PlaybackStateCompat mPlaybackState = controller.getPlaybackState();
-
-        if (mMetadata == null || mPlaybackState == null) {
-            return null;
-        }
-
-        boolean isPlaying = mPlaybackState.getState() == PlaybackStateCompat.STATE_PLAYING;
-        NotificationCompat.Action action = isPlaying
-                ? new NotificationCompat.Action(R.drawable.ic_pause_white_24dp,
-                    context.getString(R.string.label_pause),
-                    MediaButtonReceiver.buildMediaButtonPendingIntent(context,
-                            PlaybackStateCompat.ACTION_PAUSE))
-                : new NotificationCompat.Action(R.drawable.ic_play_arrow_white_24dp,
-                    context.getString(R.string.label_play),
-                    MediaButtonReceiver.buildMediaButtonPendingIntent(context,
-                            PlaybackStateCompat.ACTION_PLAY));
-
-        MediaDescriptionCompat description = mMetadata.getDescription();
-        Bitmap art = description.getIconBitmap();
-        if (art == null) {
-            // use a placeholder art while the remote art is being downloaded.
-            art = BitmapFactory.decodeResource(context.getResources(),
-                    R.drawable.ic_default_art);
-        }
-
-        NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(context);
-        notificationBuilder
-                .setStyle(new NotificationCompat.MediaStyle()
-                        // show only play/pause in compact view.
-                        .setShowActionsInCompactView(new int[]{0})
-                        .setMediaSession(mediaSession.getSessionToken()))
-                .addAction(action)
-                .setSmallIcon(R.drawable.ic_notification)
-                .setShowWhen(false)
-                .setContentIntent(controller.getSessionActivity())
-                .setContentTitle(description.getTitle())
-                .setContentText(description.getSubtitle())
-                .setLargeIcon(art)
-                .setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
-
-        return notificationBuilder.build();
-    }
-}
diff --git a/media/MediaBrowserService/Application/src/main/java/com/example/android/mediabrowserservice/MusicPlayerActivity.java b/media/MediaBrowserService/Application/src/main/java/com/example/android/mediabrowserservice/MusicPlayerActivity.java
deleted file mode 100644
index 0a3a7df..0000000
--- a/media/MediaBrowserService/Application/src/main/java/com/example/android/mediabrowserservice/MusicPlayerActivity.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * 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.mediabrowserservice;
-
-import android.os.Bundle;
-import android.support.v4.media.MediaBrowserCompat;
-import android.support.v4.media.session.MediaControllerCompat;
-import android.support.v7.app.AppCompatActivity;
-
-/**
- * Main activity for the music player.
- */
-public class MusicPlayerActivity extends AppCompatActivity
-        implements BrowseFragment.FragmentDataHelper {
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        setContentView(R.layout.activity_player);
-        if (savedInstanceState == null) {
-            getSupportFragmentManager().beginTransaction()
-                    .add(R.id.container, BrowseFragment.newInstance(null))
-                    .commit();
-        }
-    }
-
-    @Override
-    public void onMediaItemSelected(MediaBrowserCompat.MediaItem item, boolean isPlaying) {
-        if (item.isPlayable()) {
-            MediaControllerCompat controller = MediaControllerCompat.getMediaController(this);
-            MediaControllerCompat.TransportControls controls = controller.getTransportControls();
-
-            // If the item is playing, pause it, otherwise start it
-            if (isPlaying) {
-                controls.pause();
-            } else {
-                controls.playFromMediaId(item.getMediaId(), null);
-            }
-        } else if (item.isBrowsable()) {
-            getSupportFragmentManager().beginTransaction()
-                    .replace(R.id.container, BrowseFragment.newInstance(item.getMediaId()))
-                    .addToBackStack(null)
-                    .commit();
-        }
-    }
-}
diff --git a/media/MediaBrowserService/Application/src/main/java/com/example/android/mediabrowserservice/MusicService.java b/media/MediaBrowserService/Application/src/main/java/com/example/android/mediabrowserservice/MusicService.java
deleted file mode 100644
index 1cfb023..0000000
--- a/media/MediaBrowserService/Application/src/main/java/com/example/android/mediabrowserservice/MusicService.java
+++ /dev/null
@@ -1,564 +0,0 @@
-/*
-* 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.mediabrowserservice;
-
-import android.app.Notification;
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.graphics.Bitmap;
-import android.media.AudioManager;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
-import android.os.SystemClock;
-import android.support.annotation.NonNull;
-import android.support.v4.app.NotificationManagerCompat;
-import android.support.v4.media.MediaBrowserCompat;
-import android.support.v4.media.MediaBrowserServiceCompat;
-import android.support.v4.media.MediaMetadataCompat;
-import android.support.v4.media.session.MediaButtonReceiver;
-import android.support.v4.media.session.MediaSessionCompat;
-import android.support.v4.media.session.PlaybackStateCompat;
-import android.util.Log;
-
-import com.example.android.mediabrowserservice.model.MusicProvider;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-
-import static com.example.android.mediabrowserservice.model.MusicProvider.MEDIA_ID_EMPTY_ROOT;
-import static com.example.android.mediabrowserservice.model.MusicProvider.MEDIA_ID_ROOT;
-
-/**
- * This class provides a MediaBrowser through a service. It exposes the media library to a browsing
- * client, through the onGetRoot and onLoadChildren methods. It also creates a MediaSession and
- * exposes it through its MediaSession.Token, which allows the client to create a MediaController
- * that connects to and send control commands to the MediaSession remotely. This is useful for
- * user interfaces that need to interact with your media session, like Android Auto. You can
- * (should) also use the same service from your app's UI, which gives a seamless playback
- * experience to the user.
- * <p>
- * To implement a MediaBrowserService, you need to:
- * <p>
- * <ul>
- * <p>
- * <li> Extend {@link android.support.v4.media.MediaBrowserServiceCompat}, implementing the media
- * browsing related methods {@link android.support.v4.media.MediaBrowserServiceCompat#onGetRoot} and
- * {@link android.support.v4.media.MediaBrowserServiceCompat#onLoadChildren};
- * <li> In onCreate, start a new {@link android.support.v4.media.session.MediaSessionCompat} and
- * notify its parent with the session's token
- * {@link android.support.v4.media.MediaBrowserServiceCompat#setSessionToken};
- * <p>
- * <li> Set a callback on the
- * {@link android.support.v4.media.session.MediaSessionCompat#setCallback(MediaSessionCompat.Callback)}.
- * The callback will receive all the user's actions, like play, pause, etc;
- * <p>
- * <li> Handle all the actual music playing using any method your app prefers (for example,
- * {@link android.media.MediaPlayer})
- * <p>
- * <li> Update playbackState, "now playing" metadata and queue, using MediaSession proper methods
- * {@link android.support.v4.media.session.MediaSessionCompat#setPlaybackState(PlaybackStateCompat)}
- * {@link android.support.v4.media.session.MediaSessionCompat#setMetadata(MediaMetadataCompat)} and
- * if your implementation allows it,
- * {@link android.support.v4.media.session.MediaSessionCompat#setQueue(List)})
- * <p>
- * <li> Declare and export the service in AndroidManifest with an intent receiver for the action
- * android.media.browse.MediaBrowserService
- * <li> Declare a broadcast receiver to receive media button events. This is required if your app
- * supports Android KitKat or previous:
- * &lt;receiver android:name="android.support.v4.media.session.MediaButtonReceiver"&gt;
- * &lt;intent-filter&gt;
- * &lt;action android:name="android.intent.action.MEDIA_BUTTON" /&gt;
- * &lt;/intent-filter&gt;
- * &lt;/receiver&gt;
- * <p>
- * </ul>
- * <p>
- * To make your app compatible with Android Auto, you also need to:
- * <p>
- * <ul>
- * <p>
- * <li> Declare a meta-data tag in AndroidManifest.xml linking to a xml resource
- * with a &lt;automotiveApp&gt; root element. For a media app, this must include
- * an &lt;uses name="media"/&gt; element as a child.
- * For example, in AndroidManifest.xml:
- * &lt;meta-data android:name="com.google.android.gms.car.application"
- * android:resource="@xml/automotive_app_desc"/&gt;
- * And in res/values/automotive_app_desc.xml:
- * &lt;automotiveApp&gt;
- * &lt;uses name="media"/&gt;
- * &lt;/automotiveApp&gt;
- * <p>
- * </ul>
- *
- * @see <a href="README.md">README.md</a> for more details.
- */
-
-public class MusicService extends MediaBrowserServiceCompat {
-    private static final String TAG = MusicService.class.getSimpleName();
-
-    // ID for our MediaNotification.
-    public static final int NOTIFICATION_ID = 412;
-
-    // Request code for starting the UI.
-    private static final int REQUEST_CODE = 99;
-
-    // Delay stopSelf by using a handler.
-    private static final long STOP_DELAY = TimeUnit.SECONDS.toMillis(30);
-    private static final int STOP_CMD = 0x7c48;
-
-    private MusicProvider mMusicProvider;
-    private MediaSessionCompat mSession;
-    public NotificationManagerCompat mNotificationManager;
-    // Indicates whether the service was started.
-    private boolean mServiceStarted;
-    private Playback mPlayback;
-    private MediaSessionCompat.QueueItem mCurrentMedia;
-    private AudioBecomingNoisyReceiver mAudioBecomingNoisyReceiver;
-
-    /**
-     * Custom {@link Handler} to process the delayed stop command.
-     */
-    private Handler mDelayedStopHandler = new Handler(new Handler.Callback() {
-        @Override
-        public boolean handleMessage(Message msg) {
-            if (msg == null || msg.what != STOP_CMD) {
-                return false;
-            }
-
-            if (!mPlayback.isPlaying()) {
-                Log.d(TAG, "Stopping service");
-                stopSelf();
-                mServiceStarted = false;
-            }
-            return false;
-        }
-    });
-
-    /*
-     * (non-Javadoc)
-     * @see android.app.Service#onCreate()
-     */
-    @Override
-    public void onCreate() {
-        super.onCreate();
-        Log.d(TAG, "onCreate");
-
-        mMusicProvider = new MusicProvider();
-
-        // Start a new MediaSession.
-        mSession = new MediaSessionCompat(this, TAG);
-        setSessionToken(mSession.getSessionToken());
-        mSession.setCallback(new MediaSessionCallback());
-        mSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS
-                | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
-
-        mPlayback = new Playback(this, mMusicProvider);
-        mPlayback.setCallback(new Playback.Callback() {
-            @Override
-            public void onPlaybackStatusChanged(int state) {
-                updatePlaybackState(null);
-            }
-
-            @Override
-            public void onCompletion() {
-                // In this simple implementation there isn't a play queue, so we simply 'stop' after
-                // the song is over.
-                handleStopRequest();
-            }
-
-            @Override
-            public void onError(String error) {
-                updatePlaybackState(error);
-            }
-        });
-
-        Context context = getApplicationContext();
-
-        // This is an Intent to launch the app's UI, used primarily by the ongoing notification.
-        Intent intent = new Intent(context, MusicPlayerActivity.class);
-        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
-        PendingIntent pi = PendingIntent.getActivity(context, REQUEST_CODE, intent,
-                PendingIntent.FLAG_UPDATE_CURRENT);
-        mSession.setSessionActivity(pi);
-
-        mNotificationManager = NotificationManagerCompat.from(this);
-        mAudioBecomingNoisyReceiver = new AudioBecomingNoisyReceiver(this);
-
-        updatePlaybackState(null);
-    }
-
-    /**
-     * (non-Javadoc)
-     *
-     * @see android.app.Service#onStartCommand(android.content.Intent, int, int)
-     */
-    @Override
-    public int onStartCommand(Intent startIntent, int flags, int startId) {
-        MediaButtonReceiver.handleIntent(mSession, startIntent);
-        return super.onStartCommand(startIntent, flags, startId);
-    }
-
-    /**
-     * (non-Javadoc)
-     *
-     * @see android.app.Service#onDestroy()
-     */
-    @Override
-    public void onDestroy() {
-        Log.d(TAG, "onDestroy");
-        // Service is being killed, so make sure we release our resources
-        handleStopRequest();
-
-        mDelayedStopHandler.removeCallbacksAndMessages(null);
-        // Always release the MediaSession to clean up resources
-        // and notify associated MediaController(s).
-        mSession.release();
-    }
-
-    @Override
-    public BrowserRoot onGetRoot(@NonNull String clientPackageName,
-                                 int clientUid, Bundle rootHints) {
-        // Verify the client is authorized to browse media and return the root that
-        // makes the most sense here. In this example we simply verify the package name
-        // is the same as ours, but more complicated checks, and responses, are possible
-        if (!clientPackageName.equals(getPackageName())) {
-            // Allow the client to connect, but not browse, by returning an empty root
-            return new BrowserRoot(MEDIA_ID_EMPTY_ROOT, null);
-        }
-        return new BrowserRoot(MEDIA_ID_ROOT, null);
-    }
-
-    @Override
-    public void onLoadChildren(@NonNull final String parentMediaId,
-                               @NonNull final Result<List<MediaBrowserCompat.MediaItem>> result) {
-        Log.d(TAG, "OnLoadChildren: parentMediaId=" + parentMediaId);
-
-        if (!mMusicProvider.isInitialized()) {
-            // Use result.detach to allow calling result.sendResult from another thread:
-            result.detach();
-
-            mMusicProvider.retrieveMediaAsync(new MusicProvider.Callback() {
-                @Override
-                public void onMusicCatalogReady(boolean success) {
-                    if (success) {
-                        loadChildrenImpl(parentMediaId, result);
-                    } else {
-                        updatePlaybackState(getString(R.string.error_no_metadata));
-                        result.sendResult(Collections.<MediaBrowserCompat.MediaItem>emptyList());
-                    }
-                }
-            });
-
-        } else {
-            // If our music catalog is already loaded/cached, load them into result immediately
-            loadChildrenImpl(parentMediaId, result);
-        }
-    }
-
-    /**
-     * Actual implementation of onLoadChildren that assumes that MusicProvider is already
-     * initialized.
-     */
-    private void loadChildrenImpl(@NonNull final String parentMediaId,
-                                  final Result<List<MediaBrowserCompat.MediaItem>> result) {
-        List<MediaBrowserCompat.MediaItem> mediaItems = new ArrayList<>();
-
-        switch (parentMediaId) {
-            case MEDIA_ID_ROOT:
-                for (MediaMetadataCompat track : mMusicProvider.getAllMusics()) {
-                    MediaBrowserCompat.MediaItem bItem =
-                            new MediaBrowserCompat.MediaItem(track.getDescription(),
-                                    MediaBrowserCompat.MediaItem.FLAG_PLAYABLE);
-                    mediaItems.add(bItem);
-                }
-                break;
-            case MEDIA_ID_EMPTY_ROOT:
-                // Since the client provided the empty root we'll just send back an
-                // empty list
-                break;
-            default:
-                Log.w(TAG, "Skipping unmatched parentMediaId: " + parentMediaId);
-                break;
-        }
-        result.sendResult(mediaItems);
-    }
-
-    private final class MediaSessionCallback extends MediaSessionCompat.Callback {
-
-        @Override
-        public void onPlayFromMediaId(String mediaId, Bundle extras) {
-            Log.d(TAG, "playFromMediaId mediaId:" + mediaId + "  extras=" + extras);
-
-            // The mediaId used here is not the unique musicId. This one comes from the
-            // MediaBrowser, and is actually a "hierarchy-aware mediaID": a concatenation of
-            // the hierarchy in MediaBrowser and the actual unique musicID. This is necessary
-            // so we can build the correct playing queue, based on where the track was
-            // selected from.
-            MediaMetadataCompat media = mMusicProvider.getMusic(mediaId);
-            if (media != null) {
-                mCurrentMedia =
-                        new MediaSessionCompat.QueueItem(media.getDescription(), media.hashCode());
-
-                // play the music
-                handlePlayRequest();
-            }
-        }
-
-        @Override
-        public void onPlay() {
-            Log.d(TAG, "play");
-
-            if (mCurrentMedia != null) {
-                handlePlayRequest();
-            }
-        }
-
-        @Override
-        public void onSeekTo(long position) {
-            Log.d(TAG, "onSeekTo:" + position);
-            mPlayback.seekTo((int) position);
-        }
-
-        @Override
-        public void onPause() {
-            Log.d(TAG, "pause. current state=" + mPlayback.getState());
-            handlePauseRequest();
-        }
-
-        @Override
-        public void onStop() {
-            Log.d(TAG, "stop. current state=" + mPlayback.getState());
-            handleStopRequest();
-        }
-    }
-
-    /**
-     * Handle a request to play music
-     */
-    private void handlePlayRequest() {
-        Log.d(TAG, "handlePlayRequest: mState=" + mPlayback.getState());
-
-        if (mCurrentMedia == null) {
-            // Nothing to play
-            return;
-        }
-
-        mDelayedStopHandler.removeCallbacksAndMessages(null);
-        if (!mServiceStarted) {
-            Log.v(TAG, "Starting service");
-            // The MusicService needs to keep running even after the calling MediaBrowser
-            // is disconnected. Call startService(Intent) and then stopSelf(..) when we no longer
-            // need to play media.
-            startService(new Intent(getApplicationContext(), MusicService.class));
-            mServiceStarted = true;
-        }
-
-        if (!mSession.isActive()) {
-            mSession.setActive(true);
-        }
-
-        updateMetadata();
-        mPlayback.play(mCurrentMedia);
-    }
-
-    /**
-     * Handle a request to pause music
-     */
-    private void handlePauseRequest() {
-        Log.d(TAG, "handlePauseRequest: mState=" + mPlayback.getState());
-        mPlayback.pause();
-
-        // reset the delayed stop handler.
-        mDelayedStopHandler.removeCallbacksAndMessages(null);
-        mDelayedStopHandler.sendEmptyMessageDelayed(STOP_CMD, STOP_DELAY);
-    }
-
-    /**
-     * Handle a request to stop music
-     */
-    private void handleStopRequest() {
-        Log.d(TAG, "handleStopRequest: mState=" + mPlayback.getState());
-        mPlayback.stop();
-        // reset the delayed stop handler.
-        mDelayedStopHandler.removeCallbacksAndMessages(null);
-        mDelayedStopHandler.sendEmptyMessage(STOP_CMD);
-
-        updatePlaybackState(null);
-    }
-
-    private void updateMetadata() {
-        MediaSessionCompat.QueueItem queueItem = mCurrentMedia;
-        String musicId = queueItem.getDescription().getMediaId();
-        MediaMetadataCompat track = mMusicProvider.getMusic(musicId);
-
-        final String trackId = track.getString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID);
-        mSession.setMetadata(track);
-
-        // Set the proper album artwork on the media session, so it can be shown in the
-        // locked screen and in other places.
-        if (track.getDescription().getIconBitmap() == null
-                && track.getDescription().getIconUri() != null) {
-            fetchArtwork(trackId, track.getDescription().getIconUri());
-            postNotification();
-        }
-    }
-
-    private void fetchArtwork(final String trackId, final Uri albumUri) {
-        AlbumArtCache.getInstance().fetch(albumUri.toString(),
-                new AlbumArtCache.FetchListener() {
-                    @Override
-                    public void onFetched(String artUrl, Bitmap bitmap, Bitmap icon) {
-                        MediaSessionCompat.QueueItem queueItem = mCurrentMedia;
-                        MediaMetadataCompat track = mMusicProvider.getMusic(trackId);
-                        track = new MediaMetadataCompat.Builder(track)
-
-                                // Set high resolution bitmap in METADATA_KEY_ALBUM_ART. This is
-                                // used, for example, on the lockscreen background when the media
-                                // session is active.
-                                .putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, bitmap)
-
-                                // Set small version of the album art in the DISPLAY_ICON. This is
-                                // used on the MediaDescription and thus it should be small to be
-                                // serialized if necessary.
-                                .putBitmap(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON, icon)
-
-                                .build();
-
-                        mMusicProvider.updateMusic(trackId, track);
-
-                        // If we are still playing the same music
-                        String currentPlayingId = queueItem.getDescription().getMediaId();
-                        if (trackId.equals(currentPlayingId)) {
-                            mSession.setMetadata(track);
-                            postNotification();
-                        }
-                    }
-                });
-    }
-
-    /**
-     * Update the current media player state, optionally showing an error message.
-     *
-     * @param error if not null, error message to present to the user.
-     */
-    private void updatePlaybackState(String error) {
-        Log.d(TAG, "updatePlaybackState, playback state=" + mPlayback.getState());
-        long position = PlaybackStateCompat.PLAYBACK_POSITION_UNKNOWN;
-        if (mPlayback != null && mPlayback.isConnected()) {
-            position = mPlayback.getCurrentStreamPosition();
-        }
-
-        long playbackActions = PlaybackStateCompat.ACTION_PLAY
-                | PlaybackStateCompat.ACTION_PLAY_FROM_MEDIA_ID;
-        if (mPlayback.isPlaying()) {
-            playbackActions |= PlaybackStateCompat.ACTION_PAUSE;
-        }
-
-        PlaybackStateCompat.Builder stateBuilder = new PlaybackStateCompat.Builder()
-                .setActions(playbackActions);
-
-        int state = mPlayback.getState();
-
-        // If there is an error message, send it to the playback state:
-        if (error != null) {
-            // Error states are really only supposed to be used for errors that cause playback to
-            // stop unexpectedly and persist until the user takes action to fix it.
-            stateBuilder.setErrorMessage(error);
-            state = PlaybackStateCompat.STATE_ERROR;
-        }
-
-        // Because the playback state is pulled from the Playback class lint thinks it may not
-        // match permitted values.
-        //noinspection WrongConstant
-        stateBuilder.setState(state, position, 1.0f, SystemClock.elapsedRealtime());
-
-        // Set the activeQueueItemId if the current index is valid.
-        if (mCurrentMedia != null) {
-            stateBuilder.setActiveQueueItemId(mCurrentMedia.getQueueId());
-        }
-
-        mSession.setPlaybackState(stateBuilder.build());
-
-        if (state == PlaybackStateCompat.STATE_PLAYING) {
-            Notification notification = postNotification();
-            startForeground(NOTIFICATION_ID, notification);
-            mAudioBecomingNoisyReceiver.register();
-        } else {
-            if (state == PlaybackStateCompat.STATE_PAUSED) {
-                postNotification();
-            } else {
-                mNotificationManager.cancel(NOTIFICATION_ID);
-            }
-            stopForeground(false);
-            mAudioBecomingNoisyReceiver.unregister();
-        }
-    }
-
-    private Notification postNotification() {
-        Notification notification = MediaNotificationHelper.createNotification(this, mSession);
-        if (notification == null) {
-            return null;
-        }
-
-        mNotificationManager.notify(NOTIFICATION_ID, notification);
-        return notification;
-    }
-
-    /**
-     * Implementation of the AudioManager.ACTION_AUDIO_BECOMING_NOISY Receiver.
-     */
-
-    private class AudioBecomingNoisyReceiver extends BroadcastReceiver {
-        private final Context mContext;
-        private boolean mIsRegistered = false;
-
-        private IntentFilter mAudioNoisyIntentFilter =
-                new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
-
-        protected AudioBecomingNoisyReceiver(Context context) {
-            mContext = context.getApplicationContext();
-        }
-
-        public void register() {
-            if (!mIsRegistered) {
-                mContext.registerReceiver(this, mAudioNoisyIntentFilter);
-                mIsRegistered = true;
-            }
-        }
-
-        public void unregister() {
-            if (mIsRegistered) {
-                mContext.unregisterReceiver(this);
-                mIsRegistered = false;
-            }
-        }
-
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (AudioManager.ACTION_AUDIO_BECOMING_NOISY.equals(intent.getAction())) {
-                handlePauseRequest();
-            }
-        }
-    }
-}
diff --git a/media/MediaBrowserService/Application/src/main/java/com/example/android/mediabrowserservice/Playback.java b/media/MediaBrowserService/Application/src/main/java/com/example/android/mediabrowserservice/Playback.java
deleted file mode 100644
index 6af2d41..0000000
--- a/media/MediaBrowserService/Application/src/main/java/com/example/android/mediabrowserservice/Playback.java
+++ /dev/null
@@ -1,436 +0,0 @@
-/*
- * 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.mediabrowserservice;
-
-import android.content.Context;
-import android.media.AudioManager;
-import android.media.MediaPlayer;
-import android.net.wifi.WifiManager;
-import android.os.PowerManager;
-import android.support.v4.media.MediaMetadataCompat;
-import android.support.v4.media.session.MediaSessionCompat;
-import android.support.v4.media.session.PlaybackStateCompat;
-import android.text.TextUtils;
-import android.util.Log;
-
-import com.example.android.mediabrowserservice.model.MusicProvider;
-
-import java.io.IOException;
-
-import static android.media.MediaPlayer.OnCompletionListener;
-import static android.media.MediaPlayer.OnErrorListener;
-import static android.media.MediaPlayer.OnPreparedListener;
-import static android.media.MediaPlayer.OnSeekCompleteListener;
-
-/**
- * A class that implements local media playback using {@link android.media.MediaPlayer}
- */
-public class Playback implements AudioManager.OnAudioFocusChangeListener,
-        OnCompletionListener, OnErrorListener, OnPreparedListener, OnSeekCompleteListener {
-
-    private static final String TAG = Playback.class.getSimpleName();
-
-    /* package */ interface Callback {
-        /**
-         * On current music completed.
-         */
-        void onCompletion();
-
-        /**
-         * on Playback status changed
-         * Implementations can use this callback to update
-         * playback state on the media sessions.
-         */
-        void onPlaybackStatusChanged(int state);
-
-        /**
-         * @param error to be added to the PlaybackState
-         */
-        void onError(String error);
-
-    }
-
-    // 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 VOLUME_DUCK = 0.2f;
-    // The volume we set the media player when we have audio focus.
-    public static final float VOLUME_NORMAL = 1.0f;
-
-    // we don't have audio focus, and can't duck (play at a low volume)
-    private static final int AUDIO_NO_FOCUS_NO_DUCK = 0;
-    // we don't have focus, but can duck (play at a low volume)
-    private static final int AUDIO_NO_FOCUS_CAN_DUCK = 1;
-    // we have full audio focus
-    private static final int AUDIO_FOCUSED = 2;
-
-    private final MusicService mService;
-    private final MusicProvider mMusicProvider;
-    private final WifiManager.WifiLock mWifiLock;
-    private int mState = PlaybackStateCompat.STATE_NONE;
-    private boolean mPlayOnFocusGain;
-    private Callback mCallback;
-    private volatile int mCurrentPosition;
-    private volatile String mCurrentMediaId;
-
-    // Type of audio focus we have:
-    private int mAudioFocus = AUDIO_NO_FOCUS_NO_DUCK;
-    private AudioManager mAudioManager;
-    private MediaPlayer mMediaPlayer;
-
-    public Playback(MusicService service, MusicProvider musicProvider) {
-        Context context = service.getApplicationContext();
-        this.mService = service;
-        this.mMusicProvider = musicProvider;
-        this.mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
-
-        // Create the Wifi lock (this does not acquire the lock, this just creates it).
-        this.mWifiLock = ((WifiManager) context.getSystemService(Context.WIFI_SERVICE))
-                .createWifiLock(WifiManager.WIFI_MODE_FULL, "sample_lock");
-    }
-
-    public void stop() {
-        mState = PlaybackStateCompat.STATE_STOPPED;
-        if (mCallback != null) {
-            mCallback.onPlaybackStatusChanged(mState);
-        }
-        mCurrentPosition = getCurrentStreamPosition();
-        // Give up Audio focus
-        giveUpAudioFocus();
-        // Relax all resources
-        relaxResources(true);
-        if (mWifiLock.isHeld()) {
-            mWifiLock.release();
-        }
-    }
-
-    public int getState() {
-        return mState;
-    }
-
-    public boolean isConnected() {
-        return true;
-    }
-
-    public boolean isPlaying() {
-        return mPlayOnFocusGain || (mMediaPlayer != null && mMediaPlayer.isPlaying());
-    }
-
-    public int getCurrentStreamPosition() {
-        return mMediaPlayer != null ? mMediaPlayer.getCurrentPosition() : mCurrentPosition;
-    }
-
-    public void play(MediaSessionCompat.QueueItem item) {
-        mPlayOnFocusGain = true;
-        tryToGetAudioFocus();
-        String mediaId = item.getDescription().getMediaId();
-        boolean mediaHasChanged = !TextUtils.equals(mediaId, mCurrentMediaId);
-        if (mediaHasChanged) {
-            mCurrentPosition = 0;
-            mCurrentMediaId = mediaId;
-        }
-
-        if (mState == PlaybackStateCompat.STATE_PAUSED
-                && !mediaHasChanged && mMediaPlayer != null) {
-            configMediaPlayerState();
-        } else {
-            mState = PlaybackStateCompat.STATE_STOPPED;
-            relaxResources(false); // release everything except MediaPlayer
-            MediaMetadataCompat track = mMusicProvider.getMusic(item.getDescription().getMediaId());
-
-            String source = track.getString(MediaMetadataCompat.METADATA_KEY_MEDIA_URI);
-            try {
-                createMediaPlayerIfNeeded();
-
-                mState = PlaybackStateCompat.STATE_BUFFERING;
-
-                mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
-                mMediaPlayer.setDataSource(source);
-
-                // 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!
-                mMediaPlayer.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.
-                mWifiLock.acquire();
-
-                if (mCallback != null) {
-                    mCallback.onPlaybackStatusChanged(mState);
-                }
-
-            } catch (IOException ioException) {
-                Log.e(TAG, "Exception playing song", ioException);
-                if (mCallback != null) {
-                    mCallback.onError(ioException.getMessage());
-                }
-            }
-        }
-    }
-
-    public void pause() {
-        if (mState == PlaybackStateCompat.STATE_PLAYING) {
-            // Pause media player and cancel the 'foreground service' state.
-            if (mMediaPlayer != null && mMediaPlayer.isPlaying()) {
-                mMediaPlayer.pause();
-                mCurrentPosition = mMediaPlayer.getCurrentPosition();
-            }
-            // while paused, retain the MediaPlayer but give up audio focus
-            relaxResources(false);
-        }
-        mState = PlaybackStateCompat.STATE_PAUSED;
-        if (mCallback != null) {
-            mCallback.onPlaybackStatusChanged(mState);
-        }
-    }
-
-    public void seekTo(int position) {
-        Log.d(TAG, "seekTo called with " + position);
-
-        if (mMediaPlayer == null) {
-            // If we do not have a current media player, simply update the current position.
-            mCurrentPosition = position;
-        } else {
-            if (mMediaPlayer.isPlaying()) {
-                mState = PlaybackStateCompat.STATE_BUFFERING;
-            }
-            mMediaPlayer.seekTo(position);
-            if (mCallback != null) {
-                mCallback.onPlaybackStatusChanged(mState);
-            }
-        }
-    }
-
-    public void setCallback(Callback callback) {
-        this.mCallback = callback;
-    }
-
-    /**
-     * Try to get the system audio focus.
-     */
-    private void tryToGetAudioFocus() {
-        Log.d(TAG, "tryToGetAudioFocus");
-        int result = mAudioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC,
-                AudioManager.AUDIOFOCUS_GAIN);
-        mAudioFocus = (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED)
-                ? AUDIO_FOCUSED : AUDIO_NO_FOCUS_NO_DUCK;
-    }
-
-    /**
-     * Give up the audio focus.
-     */
-    private void giveUpAudioFocus() {
-        Log.d(TAG, "giveUpAudioFocus");
-        if (mAudioManager.abandonAudioFocus(this) == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
-            mAudioFocus = AUDIO_NO_FOCUS_NO_DUCK;
-        }
-    }
-
-    /**
-     * 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.
-     */
-    private void configMediaPlayerState() {
-        Log.d(TAG, "configMediaPlayerState. mAudioFocus=" + mAudioFocus);
-        if (mAudioFocus == AUDIO_NO_FOCUS_NO_DUCK) {
-            // If we don't have audio focus and can't duck, we have to pause,
-            if (mState == PlaybackStateCompat.STATE_PLAYING) {
-                pause();
-            }
-        } else {  // we have audio focus:
-            if (mAudioFocus == AUDIO_NO_FOCUS_CAN_DUCK) {
-                mMediaPlayer.setVolume(VOLUME_DUCK, VOLUME_DUCK); // we'll be relatively quiet
-            } else {
-                if (mMediaPlayer != null) {
-                    mMediaPlayer.setVolume(VOLUME_NORMAL, VOLUME_NORMAL); // we can be loud again
-                } // else do something for remote client.
-            }
-            // If we were playing when we lost focus, we need to resume playing.
-            if (mPlayOnFocusGain) {
-                if (mMediaPlayer != null && !mMediaPlayer.isPlaying()) {
-                    Log.d(TAG, "configMediaPlayerState startMediaPlayer. seeking to "
-                            + mCurrentPosition);
-                    if (mCurrentPosition == mMediaPlayer.getCurrentPosition()) {
-                        mMediaPlayer.start();
-                        mState = PlaybackStateCompat.STATE_PLAYING;
-                    } else {
-                        mMediaPlayer.seekTo(mCurrentPosition);
-                        mState = PlaybackStateCompat.STATE_BUFFERING;
-                    }
-                }
-                mPlayOnFocusGain = false;
-            }
-        }
-        if (mCallback != null) {
-            mCallback.onPlaybackStatusChanged(mState);
-        }
-    }
-
-    /**
-     * Called by AudioManager on audio focus changes.
-     * Implementation of {@link android.media.AudioManager.OnAudioFocusChangeListener}.
-     */
-    @Override
-    public void onAudioFocusChange(int focusChange) {
-        Log.d(TAG, "onAudioFocusChange. focusChange=" + focusChange);
-        if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
-            // We have gained focus:
-            mAudioFocus = AUDIO_FOCUSED;
-
-        } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS
-                || focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT
-                || focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) {
-            // We have lost focus. If we can duck (low playback volume), we can keep playing.
-            // Otherwise, we need to pause the playback.
-            boolean canDuck = focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK;
-            mAudioFocus = canDuck ? AUDIO_NO_FOCUS_CAN_DUCK : AUDIO_NO_FOCUS_NO_DUCK;
-
-            // If we are playing, we need to reset media player by calling configMediaPlayerState
-            // with mAudioFocus properly set.
-            if (mState == PlaybackStateCompat.STATE_PLAYING && !canDuck) {
-                // If we don't have audio focus and can't duck, we save the information that
-                // we were playing, so that we can resume playback once we get the focus back.
-                mPlayOnFocusGain = true;
-            }
-        } else {
-            Log.e(TAG, "onAudioFocusChange: Ignoring unsupported focusChange: " + focusChange);
-        }
-        configMediaPlayerState();
-    }
-
-    /**
-     * Called when MediaPlayer has completed a seek.
-     *
-     * @see android.media.MediaPlayer.OnSeekCompleteListener
-     */
-    @Override
-    public void onSeekComplete(MediaPlayer player) {
-        Log.d(TAG, "onSeekComplete from MediaPlayer:" + player.getCurrentPosition());
-        mCurrentPosition = player.getCurrentPosition();
-        if (mState == PlaybackStateCompat.STATE_BUFFERING) {
-            mMediaPlayer.start();
-            mState = PlaybackStateCompat.STATE_PLAYING;
-        }
-        if (mCallback != null) {
-            mCallback.onPlaybackStatusChanged(mState);
-        }
-    }
-
-    /**
-     * Called when media player is done playing current song.
-     *
-     * @see android.media.MediaPlayer.OnCompletionListener
-     */
-    @Override
-    public void onCompletion(MediaPlayer player) {
-        Log.d(TAG, "onCompletion from MediaPlayer");
-        // The media player finished playing the current song, so we go ahead
-        // and start the next.
-        if (mCallback != null) {
-            mCallback.onCompletion();
-        }
-    }
-
-    /**
-     * Called when media player is done preparing.
-     *
-     * @see android.media.MediaPlayer.OnPreparedListener
-     */
-    @Override
-    public void onPrepared(MediaPlayer player) {
-        Log.d(TAG, "onPrepared from MediaPlayer");
-        // The media player is done preparing. That means we can start playing if we
-        // have audio focus.
-        configMediaPlayerState();
-    }
-
-    /**
-     * 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.
-     *
-     * @see android.media.MediaPlayer.OnErrorListener
-     */
-    @Override
-    public boolean onError(MediaPlayer player, int what, int extra) {
-        Log.e(TAG, "Media player error: what=" + what + ", extra=" + extra);
-        if (mCallback != null) {
-            mCallback.onError("MediaPlayer error " + what + " (" + extra + ")");
-        }
-        return true; // true indicates we handled the error
-    }
-
-    /**
-     * 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.
-     */
-    private void createMediaPlayerIfNeeded() {
-        Log.d(TAG, "createMediaPlayerIfNeeded. needed? " + (mMediaPlayer == null));
-        if (mMediaPlayer == null) {
-            mMediaPlayer = 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.
-            mMediaPlayer.setWakeMode(mService.getApplicationContext(),
-                    PowerManager.PARTIAL_WAKE_LOCK);
-
-            // we want the media player to notify us when it's ready preparing,
-            // and when it's done playing:
-            mMediaPlayer.setOnPreparedListener(this);
-            mMediaPlayer.setOnCompletionListener(this);
-            mMediaPlayer.setOnErrorListener(this);
-            mMediaPlayer.setOnSeekCompleteListener(this);
-        } else {
-            mMediaPlayer.reset();
-        }
-    }
-
-    /**
-     * Releases resources used by the service for playback. This includes the
-     * "foreground service" status, the wake locks and possibly the MediaPlayer.
-     *
-     * @param releaseMediaPlayer Indicates whether the Media Player should also
-     *                           be released or not.
-     */
-    private void relaxResources(boolean releaseMediaPlayer) {
-        Log.d(TAG, "relaxResources. releaseMediaPlayer=" + releaseMediaPlayer);
-
-        mService.stopForeground(true);
-
-        // stop and release the Media Player, if it's available
-        if (releaseMediaPlayer && mMediaPlayer != null) {
-            mMediaPlayer.reset();
-            mMediaPlayer.release();
-            mMediaPlayer = null;
-        }
-
-        // we can also release the Wifi lock, if we're holding it
-        if (mWifiLock.isHeld()) {
-            mWifiLock.release();
-        }
-    }
-}
diff --git a/media/MediaBrowserService/Application/src/main/java/com/example/android/mediabrowserservice/model/MusicProvider.java b/media/MediaBrowserService/Application/src/main/java/com/example/android/mediabrowserservice/model/MusicProvider.java
deleted file mode 100644
index 7eead43..0000000
--- a/media/MediaBrowserService/Application/src/main/java/com/example/android/mediabrowserservice/model/MusicProvider.java
+++ /dev/null
@@ -1,258 +0,0 @@
-/*
- * 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.mediabrowserservice.model;
-
-import android.os.AsyncTask;
-import android.support.v4.media.MediaMetadataCompat;
-import android.util.Log;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import java.io.BufferedInputStream;
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.net.URL;
-import java.net.URLConnection;
-import java.util.Collections;
-import java.util.LinkedHashMap;
-
-/**
- * Utility class to get a list of MusicTrack's based on a server-side JSON
- * configuration.
- *
- * In a real application this class may pull data from a remote server, as we do here,
- * or potentially use {@link android.provider.MediaStore} to locate media files located on
- * the device.
- */
-public class MusicProvider {
-
-    private static final String TAG = MusicProvider.class.getSimpleName();
-
-    public static final String MEDIA_ID_ROOT = "__ROOT__";
-    public static final String MEDIA_ID_EMPTY_ROOT = "__EMPTY__";
-
-    private static final String CATALOG_URL =
-            "https://storage.googleapis.com/automotive-media/music.json";
-
-    private static final String JSON_MUSIC = "music";
-    private static final String JSON_TITLE = "title";
-    private static final String JSON_ALBUM = "album";
-    private static final String JSON_ARTIST = "artist";
-    private static final String JSON_GENRE = "genre";
-    private static final String JSON_SOURCE = "source";
-    private static final String JSON_IMAGE = "image";
-    private static final String JSON_TRACK_NUMBER = "trackNumber";
-    private static final String JSON_TOTAL_TRACK_COUNT = "totalTrackCount";
-    private static final String JSON_DURATION = "duration";
-
-    // Categorized caches for music track data:
-    private final LinkedHashMap<String, MediaMetadataCompat> mMusicListById;
-
-    private enum State {
-        NON_INITIALIZED, INITIALIZING, INITIALIZED
-    }
-
-    private volatile State mCurrentState = State.NON_INITIALIZED;
-
-    /**
-     * Callback used by MusicService.
-     */
-    public interface Callback {
-        void onMusicCatalogReady(boolean success);
-    }
-
-    public MusicProvider() {
-        mMusicListById = new LinkedHashMap<>();
-    }
-
-    public Iterable<MediaMetadataCompat> getAllMusics() {
-        if (mCurrentState != State.INITIALIZED || mMusicListById.isEmpty()) {
-            return Collections.emptyList();
-        }
-        return mMusicListById.values();
-    }
-
-    /**
-     * Return the MediaMetadata for the given musicID.
-     *
-     * @param musicId The unique music ID.
-     */
-    public MediaMetadataCompat getMusic(String musicId) {
-        return mMusicListById.containsKey(musicId) ? mMusicListById.get(musicId) : null;
-    }
-
-    /**
-     * Update the metadata associated with a musicId. If the musicId doesn't exist, the
-     * update is dropped. (That is, it does not create a new mediaId.)
-     * @param musicId The ID
-     * @param metadata New Metadata to associate with it
-     */
-    public synchronized void updateMusic(String musicId, MediaMetadataCompat metadata) {
-        MediaMetadataCompat track = mMusicListById.get(musicId);
-        if (track != null) {
-            mMusicListById.put(musicId, metadata);
-        }
-    }
-
-    public boolean isInitialized() {
-        return mCurrentState == State.INITIALIZED;
-    }
-
-    /**
-     * Get the list of music tracks from a server and caches the track information
-     * for future reference, keying tracks by musicId and grouping by genre.
-     */
-    public void retrieveMediaAsync(final Callback callback) {
-        Log.d(TAG, "retrieveMediaAsync called");
-        if (mCurrentState == State.INITIALIZED) {
-            // Already initialized, so call back immediately.
-            callback.onMusicCatalogReady(true);
-            return;
-        }
-
-        // Asynchronously load the music catalog in a separate thread
-        new AsyncTask<Void, Void, State>() {
-            @Override
-            protected State doInBackground(Void... params) {
-                retrieveMedia();
-                return mCurrentState;
-            }
-
-            @Override
-            protected void onPostExecute(State current) {
-                if (callback != null) {
-                    callback.onMusicCatalogReady(current == State.INITIALIZED);
-                }
-            }
-        }.execute();
-    }
-
-    private synchronized void retrieveMedia() {
-        try {
-            if (mCurrentState == State.NON_INITIALIZED) {
-                mCurrentState = State.INITIALIZING;
-
-                int slashPos = CATALOG_URL.lastIndexOf('/');
-                String path = CATALOG_URL.substring(0, slashPos + 1);
-                JSONObject jsonObj = fetchJSONFromUrl(CATALOG_URL);
-                if (jsonObj == null) {
-                    return;
-                }
-                JSONArray tracks = jsonObj.getJSONArray(JSON_MUSIC);
-                if (tracks != null) {
-                    for (int j = tracks.length() - 1; j >= 0; j--) {
-                        MediaMetadataCompat item = buildFromJSON(tracks.getJSONObject(j), path);
-                        String musicId = item.getString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID);
-                        mMusicListById.put(musicId, item);
-                    }
-                }
-                mCurrentState = State.INITIALIZED;
-            }
-        } catch (JSONException jsonException) {
-            Log.e(TAG, "Could not retrieve music list", jsonException);
-        } finally {
-            if (mCurrentState != State.INITIALIZED) {
-                // Something bad happened, so we reset state to NON_INITIALIZED to allow
-                // retries (eg if the network connection is temporary unavailable)
-                mCurrentState = State.NON_INITIALIZED;
-            }
-        }
-    }
-
-    private MediaMetadataCompat buildFromJSON(JSONObject json, String basePath)
-            throws JSONException {
-
-        String title = json.getString(JSON_TITLE);
-        String album = json.getString(JSON_ALBUM);
-        String artist = json.getString(JSON_ARTIST);
-        String genre = json.getString(JSON_GENRE);
-        String source = json.getString(JSON_SOURCE);
-        String iconUrl = json.getString(JSON_IMAGE);
-        int trackNumber = json.getInt(JSON_TRACK_NUMBER);
-        int totalTrackCount = json.getInt(JSON_TOTAL_TRACK_COUNT);
-        int duration = json.getInt(JSON_DURATION) * 1000; // ms
-
-        Log.d(TAG, "Found music track: " + json);
-
-        // Media is stored relative to JSON file
-        if (!source.startsWith("https")) {
-            source = basePath + source;
-        }
-        if (!iconUrl.startsWith("https")) {
-            iconUrl = basePath + iconUrl;
-        }
-        // Since we don't have a unique ID in the server, we fake one using the hashcode of
-        // the music source. In a real world app, this could come from the server.
-        String id = String.valueOf(source.hashCode());
-
-        // Adding the music source to the MediaMetadata (and consequently using it in the
-        // mediaSession.setMetadata) is not a good idea for a real world music app, because
-        // the session metadata can be accessed by notification listeners. This is done in this
-        // sample for convenience only.
-        return new MediaMetadataCompat.Builder()
-                .putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, id)
-                .putString(MediaMetadataCompat.METADATA_KEY_MEDIA_URI, source)
-                .putString(MediaMetadataCompat.METADATA_KEY_ALBUM, album)
-                .putString(MediaMetadataCompat.METADATA_KEY_ARTIST, artist)
-                .putLong(MediaMetadataCompat.METADATA_KEY_DURATION, duration)
-                .putString(MediaMetadataCompat.METADATA_KEY_GENRE, genre)
-                .putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, iconUrl)
-                .putString(MediaMetadataCompat.METADATA_KEY_TITLE, title)
-                .putLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER, trackNumber)
-                .putLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS, totalTrackCount)
-                .build();
-    }
-
-    /**
-     * Download a JSON file from a server, parse the content and return the JSON
-     * object.
-     *
-     * @return result JSONObject containing the parsed representation.
-     */
-    private JSONObject fetchJSONFromUrl(String urlString) {
-        InputStream inputStream = null;
-        try {
-            URL url = new URL(urlString);
-            URLConnection urlConnection = url.openConnection();
-            inputStream = new BufferedInputStream(urlConnection.getInputStream());
-            BufferedReader reader = new BufferedReader(new InputStreamReader(
-                    urlConnection.getInputStream(), "iso-8859-1"));
-            StringBuilder stringBuilder = new StringBuilder();
-            String line;
-            while ((line = reader.readLine()) != null) {
-                stringBuilder.append(line);
-            }
-            return new JSONObject(stringBuilder.toString());
-        } catch (IOException | JSONException exception) {
-            Log.e(TAG, "Failed to parse the json for media list", exception);
-            return null;
-        } finally {
-            // If the inputStream was opened, try to close it now.
-            if (inputStream != null) {
-                try {
-                    inputStream.close();
-                } catch (IOException ignored) {
-                    // Ignore the exception since there's nothing left to do with the stream
-                }
-            }
-        }
-    }
-}
diff --git a/media/MediaBrowserService/Application/src/main/java/com/example/android/mediasession/client/MediaBrowserAdapter.java b/media/MediaBrowserService/Application/src/main/java/com/example/android/mediasession/client/MediaBrowserAdapter.java
new file mode 100644
index 0000000..d2f0b7f
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/java/com/example/android/mediasession/client/MediaBrowserAdapter.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright 2017 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.mediasession.client;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.RemoteException;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.media.MediaBrowserCompat;
+import android.support.v4.media.MediaMetadataCompat;
+import android.support.v4.media.session.MediaControllerCompat;
+import android.support.v4.media.session.MediaSessionCompat;
+import android.support.v4.media.session.PlaybackStateCompat;
+import android.util.Log;
+
+import com.example.android.mediasession.service.MusicService;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Adapter for a MediaBrowser that handles connecting, disconnecting,
+ * and basic browsing.
+ */
+public class MediaBrowserAdapter {
+
+    private static final String TAG = MediaBrowserAdapter.class.getSimpleName();
+
+    /**
+     * Helper class for easily subscribing to changes in a MediaBrowserService connection.
+     */
+    public static abstract class MediaBrowserChangeListener {
+
+        public void onConnected(@Nullable MediaControllerCompat mediaController) {
+        }
+
+        public void onMetadataChanged(@Nullable MediaMetadataCompat mediaMetadata) {
+        }
+
+        public void onPlaybackStateChanged(@Nullable PlaybackStateCompat playbackState) {
+        }
+    }
+
+    private final InternalState mState;
+
+    private final Context mContext;
+    private final List<MediaBrowserChangeListener> mListeners = new ArrayList<>();
+
+    private final MediaBrowserConnectionCallback mMediaBrowserConnectionCallback =
+            new MediaBrowserConnectionCallback();
+    private final MediaControllerCallback mMediaControllerCallback =
+            new MediaControllerCallback();
+    private final MediaBrowserSubscriptionCallback mMediaBrowserSubscriptionCallback =
+            new MediaBrowserSubscriptionCallback();
+
+    private MediaBrowserCompat mMediaBrowser;
+
+    @Nullable
+    private MediaControllerCompat mMediaController;
+
+    public MediaBrowserAdapter(Context context) {
+        mContext = context;
+        mState = new InternalState();
+    }
+
+    public void onStart() {
+        if (mMediaBrowser == null) {
+            mMediaBrowser =
+                    new MediaBrowserCompat(
+                            mContext,
+                            new ComponentName(mContext, MusicService.class),
+                            mMediaBrowserConnectionCallback,
+                            null);
+            mMediaBrowser.connect();
+        }
+        Log.d(TAG, "onStart: Creating MediaBrowser, and connecting");
+    }
+
+    public void onStop() {
+        if (mMediaController != null) {
+            mMediaController.unregisterCallback(mMediaControllerCallback);
+            mMediaController = null;
+        }
+        if (mMediaBrowser != null && mMediaBrowser.isConnected()) {
+            mMediaBrowser.disconnect();
+            mMediaBrowser = null;
+        }
+        resetState();
+        Log.d(TAG, "onStop: Releasing MediaController, Disconnecting from MediaBrowser");
+    }
+
+    /**
+     * The internal state of the app needs to revert to what it looks like when it started before
+     * any connections to the {@link MusicService} happens via the {@link MediaSessionCompat}.
+     */
+    private void resetState() {
+        mState.reset();
+        performOnAllListeners(new ListenerCommand() {
+            @Override
+            public void perform(@NonNull MediaBrowserChangeListener listener) {
+                listener.onPlaybackStateChanged(null);
+            }
+        });
+        Log.d(TAG, "resetState: ");
+    }
+
+    public MediaControllerCompat.TransportControls getTransportControls() {
+        if (mMediaController == null) {
+            Log.d(TAG, "getTransportControls: MediaController is null!");
+            throw new IllegalStateException();
+        }
+        return mMediaController.getTransportControls();
+    }
+
+    public void addListener(MediaBrowserChangeListener listener) {
+        if (listener != null) {
+            mListeners.add(listener);
+        }
+    }
+
+    public void removeListener(MediaBrowserChangeListener listener) {
+        if (listener != null) {
+            if (mListeners.contains(listener)) {
+                mListeners.remove(listener);
+            }
+        }
+    }
+
+    public void performOnAllListeners(@NonNull ListenerCommand command) {
+        for (MediaBrowserChangeListener listener : mListeners) {
+            if (listener != null) {
+                try {
+                    command.perform(listener);
+                } catch (Exception e) {
+                    removeListener(listener);
+                }
+            }
+        }
+    }
+
+    public interface ListenerCommand {
+
+        void perform(@NonNull MediaBrowserChangeListener listener);
+    }
+
+    // Receives callbacks from the MediaBrowser when it has successfully connected to the
+    // MediaBrowserService (MusicService).
+    public class MediaBrowserConnectionCallback extends MediaBrowserCompat.ConnectionCallback {
+
+        // Happens as a result of onStart().
+        @Override
+        public void onConnected() {
+            try {
+                // Get a MediaController for the MediaSession.
+                mMediaController = new MediaControllerCompat(mContext,
+                                                             mMediaBrowser.getSessionToken());
+                mMediaController.registerCallback(mMediaControllerCallback);
+
+                // Sync existing MediaSession state to the UI.
+                mMediaControllerCallback.onMetadataChanged(
+                        mMediaController.getMetadata());
+                mMediaControllerCallback
+                        .onPlaybackStateChanged(mMediaController.getPlaybackState());
+
+                performOnAllListeners(new ListenerCommand() {
+                    @Override
+                    public void perform(@NonNull MediaBrowserChangeListener listener) {
+                        listener.onConnected(mMediaController);
+                    }
+                });
+            } catch (RemoteException e) {
+                Log.d(TAG, String.format("onConnected: Problem: %s", e.toString()));
+                throw new RuntimeException(e);
+            }
+
+            mMediaBrowser.subscribe(mMediaBrowser.getRoot(), mMediaBrowserSubscriptionCallback);
+        }
+    }
+
+    // Receives callbacks from the MediaBrowser when the MediaBrowserService has loaded new media
+    // that is ready for playback.
+    public class MediaBrowserSubscriptionCallback extends MediaBrowserCompat.SubscriptionCallback {
+
+        @Override
+        public void onChildrenLoaded(@NonNull String parentId,
+                                     @NonNull List<MediaBrowserCompat.MediaItem> children) {
+            assert mMediaController != null;
+
+            // Queue up all media items for this simple sample.
+            for (final MediaBrowserCompat.MediaItem mediaItem : children) {
+                mMediaController.addQueueItem(mediaItem.getDescription());
+            }
+
+            // Call "playFromMedia" so the UI is updated.
+            mMediaController.getTransportControls().prepare();
+        }
+    }
+
+    // Receives callbacks from the MediaController and updates the UI state,
+    // i.e.: Which is the current item, whether it's playing or paused, etc.
+    public class MediaControllerCallback extends MediaControllerCompat.Callback {
+
+        @Override
+        public void onMetadataChanged(final MediaMetadataCompat metadata) {
+            // Filtering out needless updates, given that the metadata has not changed.
+            if (isMediaIdSame(metadata, mState.getMediaMetadata())) {
+                Log.d(TAG, "onMetadataChanged: Filtering out needless onMetadataChanged() update");
+                return;
+            } else {
+                mState.setMediaMetadata(metadata);
+            }
+            performOnAllListeners(new ListenerCommand() {
+                @Override
+                public void perform(@NonNull MediaBrowserChangeListener listener) {
+                    listener.onMetadataChanged(metadata);
+                }
+            });
+        }
+
+        @Override
+        public void onPlaybackStateChanged(@Nullable final PlaybackStateCompat state) {
+            mState.setPlaybackState(state);
+            performOnAllListeners(new ListenerCommand() {
+                @Override
+                public void perform(@NonNull MediaBrowserChangeListener listener) {
+                    listener.onPlaybackStateChanged(state);
+                }
+            });
+        }
+
+        // This might happen if the MusicService is killed while the Activity is in the
+        // foreground and onStart() has been called (but not onStop()).
+        @Override
+        public void onSessionDestroyed() {
+            resetState();
+            onPlaybackStateChanged(null);
+            Log.d(TAG, "onSessionDestroyed: MusicService is dead!!!");
+        }
+
+        private boolean isMediaIdSame(MediaMetadataCompat currentMedia,
+                                     MediaMetadataCompat newMedia) {
+            if (currentMedia == null || newMedia == null) {
+                return false;
+            }
+            String newMediaId =
+                    newMedia.getString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID);
+            String currentMediaId =
+                    currentMedia.getString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID);
+            return newMediaId.equals(currentMediaId);
+        }
+
+    }
+
+    // A holder class that contains the internal state.
+    public class InternalState {
+
+        private PlaybackStateCompat playbackState;
+        private MediaMetadataCompat mediaMetadata;
+
+        public void reset() {
+            playbackState = null;
+            mediaMetadata = null;
+        }
+
+        public PlaybackStateCompat getPlaybackState() {
+            return playbackState;
+        }
+
+        public void setPlaybackState(PlaybackStateCompat playbackState) {
+            this.playbackState = playbackState;
+        }
+
+        public MediaMetadataCompat getMediaMetadata() {
+            return mediaMetadata;
+        }
+
+        public void setMediaMetadata(MediaMetadataCompat mediaMetadata) {
+            this.mediaMetadata = mediaMetadata;
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/media/MediaBrowserService/Application/src/main/java/com/example/android/mediasession/service/MusicService.java b/media/MediaBrowserService/Application/src/main/java/com/example/android/mediasession/service/MusicService.java
new file mode 100644
index 0000000..5d98bc2
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/java/com/example/android/mediasession/service/MusicService.java
@@ -0,0 +1,244 @@
+/*
+ * Copyright 2017 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.mediasession.service;
+
+import android.app.Notification;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.v4.content.ContextCompat;
+import android.support.v4.media.MediaBrowserCompat;
+import android.support.v4.media.MediaBrowserServiceCompat;
+import android.support.v4.media.MediaDescriptionCompat;
+import android.support.v4.media.MediaMetadataCompat;
+import android.support.v4.media.session.MediaSessionCompat;
+import android.support.v4.media.session.PlaybackStateCompat;
+import android.util.Log;
+
+import com.example.android.mediasession.service.contentcatalogs.MusicLibrary;
+import com.example.android.mediasession.service.notifications.MediaNotificationManager;
+import com.example.android.mediasession.service.players.MediaPlayerAdapter;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class MusicService extends MediaBrowserServiceCompat {
+
+    private static final String TAG = MusicService.class.getSimpleName();
+
+    private MediaSessionCompat mSession;
+    private PlayerAdapter mPlayback;
+    private MediaNotificationManager mMediaNotificationManager;
+    private MediaSessionCallback mCallback;
+    private boolean mServiceInStartedState;
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+
+        // Create a new MediaSession.
+        mSession = new MediaSessionCompat(this, "MusicService");
+        mCallback = new MediaSessionCallback();
+        mSession.setCallback(mCallback);
+        mSession.setFlags(
+                MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS |
+                MediaSessionCompat.FLAG_HANDLES_QUEUE_COMMANDS |
+                MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
+        setSessionToken(mSession.getSessionToken());
+
+        mMediaNotificationManager = new MediaNotificationManager(this);
+
+        mPlayback = new MediaPlayerAdapter(this, new MediaPlayerListener());
+        Log.d(TAG, "onCreate: MusicService creating MediaSession, and MediaNotificationManager");
+    }
+
+    @Override
+    public void onTaskRemoved(Intent rootIntent) {
+        super.onTaskRemoved(rootIntent);
+        stopSelf();
+    }
+
+    @Override
+    public void onDestroy() {
+        mMediaNotificationManager.onDestroy();
+        mPlayback.stop();
+        mSession.release();
+        Log.d(TAG, "onDestroy: MediaPlayerAdapter stopped, and MediaSession released");
+    }
+
+    @Override
+    public BrowserRoot onGetRoot(@NonNull String clientPackageName,
+                                 int clientUid,
+                                 Bundle rootHints) {
+        return new BrowserRoot(MusicLibrary.getRoot(), null);
+    }
+
+    @Override
+    public void onLoadChildren(
+            @NonNull final String parentMediaId,
+            @NonNull final Result<List<MediaBrowserCompat.MediaItem>> result) {
+        result.sendResult(MusicLibrary.getMediaItems());
+    }
+
+    // MediaSession Callback: Transport Controls -> MediaPlayerAdapter
+    public class MediaSessionCallback extends MediaSessionCompat.Callback {
+        private final List<MediaSessionCompat.QueueItem> mPlaylist = new ArrayList<>();
+        private int mQueueIndex = -1;
+        private MediaMetadataCompat mPreparedMedia;
+
+        @Override
+        public void onAddQueueItem(MediaDescriptionCompat description) {
+            mPlaylist.add(new MediaSessionCompat.QueueItem(description, description.hashCode()));
+            mQueueIndex = (mQueueIndex == -1) ? 0 : mQueueIndex;
+        }
+
+        @Override
+        public void onRemoveQueueItem(MediaDescriptionCompat description) {
+            mPlaylist.remove(new MediaSessionCompat.QueueItem(description, description.hashCode()));
+            mQueueIndex = (mPlaylist.isEmpty()) ? -1 : mQueueIndex;
+        }
+
+        @Override
+        public void onPrepare() {
+            if (mQueueIndex < 0 && mPlaylist.isEmpty()) {
+                // Nothing to play.
+                return;
+            }
+
+            final String mediaId = mPlaylist.get(mQueueIndex).getDescription().getMediaId();
+            mPreparedMedia = MusicLibrary.getMetadata(MusicService.this, mediaId);
+            mSession.setMetadata(mPreparedMedia);
+
+            if (!mSession.isActive()) {
+                mSession.setActive(true);
+            }
+        }
+
+        @Override
+        public void onPlay() {
+            if (!isReadyToPlay()) {
+                // Nothing to play.
+                return;
+            }
+
+            if (mPreparedMedia == null) {
+                onPrepare();
+            }
+
+            mPlayback.playFromMedia(mPreparedMedia);
+            Log.d(TAG, "onPlayFromMediaId: MediaSession active");
+        }
+
+        @Override
+        public void onPause() {
+            mPlayback.pause();
+        }
+
+        @Override
+        public void onStop() {
+            mPlayback.stop();
+            mSession.setActive(false);
+        }
+
+        @Override
+        public void onSkipToNext() {
+            mQueueIndex = (++mQueueIndex % mPlaylist.size());
+            mPreparedMedia = null;
+            onPlay();
+        }
+
+        @Override
+        public void onSkipToPrevious() {
+            mQueueIndex = mQueueIndex > 0 ? mQueueIndex - 1 : mPlaylist.size() - 1;
+            mPreparedMedia = null;
+            onPlay();
+        }
+
+        @Override
+        public void onSeekTo(long pos) {
+            mPlayback.seekTo(pos);
+        }
+
+        private boolean isReadyToPlay() {
+            return (!mPlaylist.isEmpty());
+        }
+    }
+
+    // MediaPlayerAdapter Callback: MediaPlayerAdapter state -> MusicService.
+    public class MediaPlayerListener extends PlaybackInfoListener {
+
+        private final ServiceManager mServiceManager;
+
+        MediaPlayerListener() {
+            mServiceManager = new ServiceManager();
+        }
+
+        @Override
+        public void onPlaybackStateChange(PlaybackStateCompat state) {
+            // Report the state to the MediaSession.
+            mSession.setPlaybackState(state);
+
+            // Manage the started state of this service.
+            switch (state.getState()) {
+                case PlaybackStateCompat.STATE_PLAYING:
+                    mServiceManager.moveServiceToStartedState(state);
+                    break;
+                case PlaybackStateCompat.STATE_PAUSED:
+                    mServiceManager.updateNotificationForPause(state);
+                    break;
+                case PlaybackStateCompat.STATE_STOPPED:
+                    mServiceManager.moveServiceOutOfStartedState(state);
+                    break;
+            }
+        }
+
+        class ServiceManager {
+
+            private void moveServiceToStartedState(PlaybackStateCompat state) {
+                Notification notification =
+                        mMediaNotificationManager.getNotification(
+                                mPlayback.getCurrentMedia(), state, getSessionToken());
+
+                if (!mServiceInStartedState) {
+                    ContextCompat.startForegroundService(
+                            MusicService.this,
+                            new Intent(MusicService.this, MusicService.class));
+                    mServiceInStartedState = true;
+                }
+
+                startForeground(MediaNotificationManager.NOTIFICATION_ID, notification);
+            }
+
+            private void updateNotificationForPause(PlaybackStateCompat state) {
+                stopForeground(false);
+                Notification notification =
+                        mMediaNotificationManager.getNotification(
+                                mPlayback.getCurrentMedia(), state, getSessionToken());
+                mMediaNotificationManager.getNotificationManager()
+                        .notify(MediaNotificationManager.NOTIFICATION_ID, notification);
+            }
+
+            private void moveServiceOutOfStartedState(PlaybackStateCompat state) {
+                stopForeground(true);
+                stopSelf();
+                mServiceInStartedState = false;
+            }
+        }
+
+    }
+
+}
\ No newline at end of file
diff --git a/media/MediaBrowserService/Application/src/main/java/com/example/android/mediasession/service/PlaybackInfoListener.java b/media/MediaBrowserService/Application/src/main/java/com/example/android/mediasession/service/PlaybackInfoListener.java
new file mode 100644
index 0000000..f19abd2
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/java/com/example/android/mediasession/service/PlaybackInfoListener.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2017 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.mediasession.service;
+
+import android.support.v4.media.session.PlaybackStateCompat;
+import android.support.v4.media.session.MediaSessionCompat;
+
+import com.example.android.mediasession.service.players.MediaPlayerAdapter;
+
+/**
+ * Listener to provide state updates from {@link MediaPlayerAdapter} (the media player)
+ * to {@link MusicService} (the service that holds our {@link MediaSessionCompat}.
+ */
+public abstract class PlaybackInfoListener {
+
+    public abstract void onPlaybackStateChange(PlaybackStateCompat state);
+
+    public void onPlaybackCompleted() {
+    }
+}
\ No newline at end of file
diff --git a/media/MediaBrowserService/Application/src/main/java/com/example/android/mediasession/service/PlayerAdapter.java b/media/MediaBrowserService/Application/src/main/java/com/example/android/mediasession/service/PlayerAdapter.java
new file mode 100644
index 0000000..83c810e
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/java/com/example/android/mediasession/service/PlayerAdapter.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright 2017 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.mediasession.service;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.media.AudioManager;
+import android.net.wifi.WifiManager;
+import android.support.annotation.NonNull;
+import android.support.v4.media.MediaMetadataCompat;
+import android.support.v4.media.session.PlaybackStateCompat;
+
+/**
+ * Abstract player implementation that handles playing music with proper handling of headphones
+ * and audio focus.
+ */
+public abstract class PlayerAdapter {
+
+    private static final float MEDIA_VOLUME_DEFAULT = 1.0f;
+    private static final float MEDIA_VOLUME_DUCK = 0.2f;
+
+    private static final IntentFilter AUDIO_NOISY_INTENT_FILTER =
+            new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
+
+    private boolean mAudioNoisyReceiverRegistered = false;
+    private final BroadcastReceiver mAudioNoisyReceiver =
+            new BroadcastReceiver() {
+                @Override
+                public void onReceive(Context context, Intent intent) {
+                    if (AudioManager.ACTION_AUDIO_BECOMING_NOISY.equals(intent.getAction())) {
+                        if (isPlaying()) {
+                            pause();
+                        }
+                    }
+                }
+            };
+
+    private final Context mApplicationContext;
+    private final AudioManager mAudioManager;
+    private final AudioFocusHelper mAudioFocusHelper;
+
+    private boolean mPlayOnAudioFocus = false;
+
+    public PlayerAdapter(@NonNull Context context) {
+        mApplicationContext = context.getApplicationContext();
+        mAudioManager = (AudioManager) mApplicationContext.getSystemService(Context.AUDIO_SERVICE);
+        mAudioFocusHelper = new AudioFocusHelper();
+    }
+
+    public abstract void playFromMedia(MediaMetadataCompat metadata);
+
+    public abstract MediaMetadataCompat getCurrentMedia();
+
+    public abstract boolean isPlaying();
+
+    public final void play() {
+        if (mAudioFocusHelper.requestAudioFocus()) {
+            registerAudioNoisyReceiver();
+            onPlay();
+        }
+    }
+
+    /**
+     * Called when media is ready to be played and indicates the app has audio focus.
+     */
+    protected abstract void onPlay();
+
+    public final void pause() {
+        if (!mPlayOnAudioFocus) {
+            mAudioFocusHelper.abandonAudioFocus();
+        }
+
+        unregisterAudioNoisyReceiver();
+        onPause();
+    }
+
+    /**
+     * Called when media must be paused.
+     */
+    protected abstract void onPause();
+
+    public final void stop() {
+        mAudioFocusHelper.abandonAudioFocus();
+        unregisterAudioNoisyReceiver();
+        onStop();
+    }
+
+    /**
+     * Called when the media must be stopped. The player should clean up resources at this
+     * point.
+     */
+    protected abstract void onStop();
+
+    public abstract void seekTo(long position);
+
+    public abstract void setVolume(float volume);
+
+    private void registerAudioNoisyReceiver() {
+        if (!mAudioNoisyReceiverRegistered) {
+            mApplicationContext.registerReceiver(mAudioNoisyReceiver, AUDIO_NOISY_INTENT_FILTER);
+            mAudioNoisyReceiverRegistered = true;
+        }
+    }
+
+    private void unregisterAudioNoisyReceiver() {
+        if (mAudioNoisyReceiverRegistered) {
+            mApplicationContext.unregisterReceiver(mAudioNoisyReceiver);
+            mAudioNoisyReceiverRegistered = false;
+        }
+    }
+
+    /**
+     * Helper class for managing audio focus related tasks.
+     */
+    private final class AudioFocusHelper
+            implements AudioManager.OnAudioFocusChangeListener {
+
+        private boolean requestAudioFocus() {
+            final int result = mAudioManager.requestAudioFocus(this,
+                    AudioManager.STREAM_MUSIC,
+                    AudioManager.AUDIOFOCUS_GAIN);
+            return result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
+        }
+
+        private void abandonAudioFocus() {
+            mAudioManager.abandonAudioFocus(this);
+        }
+
+        @Override
+        public void onAudioFocusChange(int focusChange) {
+            switch (focusChange) {
+                case AudioManager.AUDIOFOCUS_GAIN:
+                    if (mPlayOnAudioFocus && !isPlaying()) {
+                        play();
+                    } else if (isPlaying()) {
+                        setVolume(MEDIA_VOLUME_DEFAULT);
+                    }
+                    mPlayOnAudioFocus = false;
+                    break;
+                case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
+                    setVolume(MEDIA_VOLUME_DUCK);
+                    break;
+                case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
+                    if (isPlaying()) {
+                        mPlayOnAudioFocus = true;
+                        pause();
+                    }
+                    break;
+                case AudioManager.AUDIOFOCUS_LOSS:
+                    mAudioManager.abandonAudioFocus(this);
+                    mPlayOnAudioFocus = false;
+                    stop();
+                    break;
+            }
+        }
+    }
+}
diff --git a/media/MediaBrowserService/Application/src/main/java/com/example/android/mediasession/service/contentcatalogs/MusicLibrary.java b/media/MediaBrowserService/Application/src/main/java/com/example/android/mediasession/service/contentcatalogs/MusicLibrary.java
new file mode 100644
index 0000000..dacb009
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/java/com/example/android/mediasession/service/contentcatalogs/MusicLibrary.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright 2017 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.mediasession.service.contentcatalogs;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.support.v4.media.MediaBrowserCompat;
+import android.support.v4.media.MediaMetadataCompat;
+
+import com.example.android.mediasession.BuildConfig;
+import com.example.android.mediasession.R;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.TreeMap;
+import java.util.concurrent.TimeUnit;
+
+
+public class MusicLibrary {
+
+    private static final TreeMap<String, MediaMetadataCompat> music = new TreeMap<>();
+    private static final HashMap<String, Integer> albumRes = new HashMap<>();
+    private static final HashMap<String, String> musicFileName = new HashMap<>();
+
+    static {
+        createMediaMetadataCompat(
+                "Jazz_In_Paris",
+                "Jazz in Paris",
+                "Media Right Productions",
+                "Jazz & Blues",
+                "Jazz",
+                103,
+                TimeUnit.SECONDS,
+                "jazz_in_paris.mp3",
+                R.drawable.album_jazz_blues,
+                "album_jazz_blues");
+        createMediaMetadataCompat(
+                "The_Coldest_Shoulder",
+                "The Coldest Shoulder",
+                "The 126ers",
+                "Youtube Audio Library Rock 2",
+                "Rock",
+                160,
+                TimeUnit.SECONDS,
+                "the_coldest_shoulder.mp3",
+                R.drawable.album_youtube_audio_library_rock_2,
+                "album_youtube_audio_library_rock_2");
+    }
+
+    public static String getRoot() {
+        return "root";
+    }
+
+    private static String getAlbumArtUri(String albumArtResName) {
+        return ContentResolver.SCHEME_ANDROID_RESOURCE + "://" +
+                BuildConfig.APPLICATION_ID + "/drawable/" + albumArtResName;
+    }
+
+    public static String getMusicFilename(String mediaId) {
+        return musicFileName.containsKey(mediaId) ? musicFileName.get(mediaId) : null;
+    }
+
+    private static int getAlbumRes(String mediaId) {
+        return albumRes.containsKey(mediaId) ? albumRes.get(mediaId) : 0;
+    }
+
+    public static Bitmap getAlbumBitmap(Context context, String mediaId) {
+        return BitmapFactory.decodeResource(context.getResources(),
+                MusicLibrary.getAlbumRes(mediaId));
+    }
+
+    public static List<MediaBrowserCompat.MediaItem> getMediaItems() {
+        List<MediaBrowserCompat.MediaItem> result = new ArrayList<>();
+        for (MediaMetadataCompat metadata : music.values()) {
+            result.add(
+                    new MediaBrowserCompat.MediaItem(
+                            metadata.getDescription(), MediaBrowserCompat.MediaItem.FLAG_PLAYABLE));
+        }
+        return result;
+    }
+
+    public static MediaMetadataCompat getMetadata(Context context, String mediaId) {
+        MediaMetadataCompat metadataWithoutBitmap = music.get(mediaId);
+        Bitmap albumArt = getAlbumBitmap(context, mediaId);
+
+        // Since MediaMetadataCompat is immutable, we need to create a copy to set the album art.
+        // We don't set it initially on all items so that they don't take unnecessary memory.
+        MediaMetadataCompat.Builder builder = new MediaMetadataCompat.Builder();
+        for (String key :
+                new String[]{
+                        MediaMetadataCompat.METADATA_KEY_MEDIA_ID,
+                        MediaMetadataCompat.METADATA_KEY_ALBUM,
+                        MediaMetadataCompat.METADATA_KEY_ARTIST,
+                        MediaMetadataCompat.METADATA_KEY_GENRE,
+                        MediaMetadataCompat.METADATA_KEY_TITLE
+                }) {
+            builder.putString(key, metadataWithoutBitmap.getString(key));
+        }
+        builder.putLong(
+                MediaMetadataCompat.METADATA_KEY_DURATION,
+                metadataWithoutBitmap.getLong(MediaMetadataCompat.METADATA_KEY_DURATION));
+        builder.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, albumArt);
+        return builder.build();
+    }
+
+    private static void createMediaMetadataCompat(
+            String mediaId,
+            String title,
+            String artist,
+            String album,
+            String genre,
+            long duration,
+            TimeUnit durationUnit,
+            String musicFilename,
+            int albumArtResId,
+            String albumArtResName) {
+        music.put(
+                mediaId,
+                new MediaMetadataCompat.Builder()
+                        .putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, mediaId)
+                        .putString(MediaMetadataCompat.METADATA_KEY_ALBUM, album)
+                        .putString(MediaMetadataCompat.METADATA_KEY_ARTIST, artist)
+                        .putLong(MediaMetadataCompat.METADATA_KEY_DURATION,
+                                 TimeUnit.MILLISECONDS.convert(duration, durationUnit))
+                        .putString(MediaMetadataCompat.METADATA_KEY_GENRE, genre)
+                        .putString(
+                                MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI,
+                                getAlbumArtUri(albumArtResName))
+                        .putString(
+                                MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON_URI,
+                                getAlbumArtUri(albumArtResName))
+                        .putString(MediaMetadataCompat.METADATA_KEY_TITLE, title)
+                        .build());
+        albumRes.put(mediaId, albumArtResId);
+        musicFileName.put(mediaId, musicFilename);
+    }
+}
\ No newline at end of file
diff --git a/media/MediaBrowserService/Application/src/main/java/com/example/android/mediasession/service/notifications/MediaNotificationManager.java b/media/MediaBrowserService/Application/src/main/java/com/example/android/mediasession/service/notifications/MediaNotificationManager.java
new file mode 100644
index 0000000..0274494
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/java/com/example/android/mediasession/service/notifications/MediaNotificationManager.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright 2017 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.mediasession.service.notifications;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Color;
+import android.os.Build;
+import android.support.annotation.NonNull;
+import android.support.annotation.RequiresApi;
+import android.support.v4.app.NotificationCompat;
+import android.support.v4.content.ContextCompat;
+import android.support.v4.media.MediaDescriptionCompat;
+import android.support.v4.media.MediaMetadataCompat;
+import android.support.v4.media.app.NotificationCompat.MediaStyle;
+import android.support.v4.media.session.MediaButtonReceiver;
+import android.support.v4.media.session.MediaSessionCompat;
+import android.support.v4.media.session.PlaybackStateCompat;
+import android.support.v4.os.BuildCompat;
+import android.util.Log;
+
+import com.example.android.mediasession.R;
+import com.example.android.mediasession.service.MusicService;
+import com.example.android.mediasession.service.PlaybackInfoListener;
+import com.example.android.mediasession.service.contentcatalogs.MusicLibrary;
+import com.example.android.mediasession.ui.MainActivity;
+
+
+/**
+ * Keeps track of a notification and updates it automatically for a given MediaSession. This is
+ * required so that the music service don't get killed during playback.
+ */
+public class MediaNotificationManager {
+
+    public static final int NOTIFICATION_ID = 412;
+
+    private static final String TAG = MediaNotificationManager.class.getSimpleName();
+    private static final String CHANNEL_ID = "com.example.android.musicplayer.channel";
+    private static final int REQUEST_CODE = 501;
+
+    private final MusicService mService;
+
+    private final NotificationCompat.Action mPlayAction;
+    private final NotificationCompat.Action mPauseAction;
+    private final NotificationCompat.Action mNextAction;
+    private final NotificationCompat.Action mPrevAction;
+    private final NotificationManager mNotificationManager;
+
+    public MediaNotificationManager(MusicService service) {
+        mService = service;
+
+        mNotificationManager =
+                (NotificationManager) mService.getSystemService(Context.NOTIFICATION_SERVICE);
+
+        mPlayAction =
+                new NotificationCompat.Action(
+                        R.drawable.ic_play_arrow_white_24dp,
+                        mService.getString(R.string.label_play),
+                        MediaButtonReceiver.buildMediaButtonPendingIntent(
+                                mService,
+                                PlaybackStateCompat.ACTION_PLAY));
+        mPauseAction =
+                new NotificationCompat.Action(
+                        R.drawable.ic_pause_white_24dp,
+                        mService.getString(R.string.label_pause),
+                        MediaButtonReceiver.buildMediaButtonPendingIntent(
+                                mService,
+                                PlaybackStateCompat.ACTION_PAUSE));
+        mNextAction =
+                new NotificationCompat.Action(
+                        R.drawable.ic_skip_next_white_24dp,
+                        mService.getString(R.string.label_next),
+                        MediaButtonReceiver.buildMediaButtonPendingIntent(
+                                mService,
+                                PlaybackStateCompat.ACTION_SKIP_TO_NEXT));
+        mPrevAction =
+                new NotificationCompat.Action(
+                        R.drawable.ic_skip_previous_white_24dp,
+                        mService.getString(R.string.label_previous),
+                        MediaButtonReceiver.buildMediaButtonPendingIntent(
+                                mService,
+                                PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS));
+
+        // Cancel all notifications to handle the case where the Service was killed and
+        // restarted by the system.
+        mNotificationManager.cancelAll();
+    }
+
+    public void onDestroy() {
+        Log.d(TAG, "onDestroy: ");
+    }
+
+    public NotificationManager getNotificationManager() {
+        return mNotificationManager;
+    }
+
+    public Notification getNotification(MediaMetadataCompat metadata,
+                                        @NonNull PlaybackStateCompat state,
+                                        MediaSessionCompat.Token token) {
+        boolean isPlaying = state.getState() == PlaybackStateCompat.STATE_PLAYING;
+        MediaDescriptionCompat description = metadata.getDescription();
+        NotificationCompat.Builder builder =
+                buildNotification(state, token, isPlaying, description);
+        return builder.build();
+    }
+
+    private NotificationCompat.Builder buildNotification(@NonNull PlaybackStateCompat state,
+                                                         MediaSessionCompat.Token token,
+                                                         boolean isPlaying,
+                                                         MediaDescriptionCompat description) {
+
+        // Create the (mandatory) notification channel when running on Android Oreo.
+        if (isAndroidOOrHigher()) {
+            createChannel();
+        }
+
+        NotificationCompat.Builder builder = new NotificationCompat.Builder(mService, CHANNEL_ID);
+        builder.setStyle(
+                new MediaStyle()
+                        .setMediaSession(token)
+                        .setShowActionsInCompactView(0, 1, 2)
+                        // For backwards compatibility with Android L and earlier.
+                        .setShowCancelButton(true)
+                        .setCancelButtonIntent(
+                                MediaButtonReceiver.buildMediaButtonPendingIntent(
+                                        mService,
+                                        PlaybackStateCompat.ACTION_STOP)))
+                .setColor(ContextCompat.getColor(mService, R.color.notification_bg))
+                .setSmallIcon(R.drawable.ic_stat_image_audiotrack)
+                // Pending intent that is fired when user clicks on notification.
+                .setContentIntent(createContentIntent())
+                // Title - Usually Song name.
+                .setContentTitle(description.getTitle())
+                // Subtitle - Usually Artist name.
+                .setContentText(description.getSubtitle())
+                .setLargeIcon(MusicLibrary.getAlbumBitmap(mService, description.getMediaId()))
+                // When notification is deleted (when playback is paused and notification can be
+                // deleted) fire MediaButtonPendingIntent with ACTION_STOP.
+                .setDeleteIntent(MediaButtonReceiver.buildMediaButtonPendingIntent(
+                        mService, PlaybackStateCompat.ACTION_STOP))
+                // Show controls on lock screen even when user hides sensitive content.
+                .setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
+
+        // If skip to next action is enabled.
+        if ((state.getActions() & PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS) != 0) {
+            builder.addAction(mPrevAction);
+        }
+
+        builder.addAction(isPlaying ? mPauseAction : mPlayAction);
+
+        // If skip to prev action is enabled.
+        if ((state.getActions() & PlaybackStateCompat.ACTION_SKIP_TO_NEXT) != 0) {
+            builder.addAction(mNextAction);
+        }
+
+        return builder;
+    }
+
+    // Does nothing on versions of Android earlier than O.
+    @RequiresApi(Build.VERSION_CODES.O)
+    private void createChannel() {
+        if (mNotificationManager.getNotificationChannel(CHANNEL_ID) == null) {
+            // The user-visible name of the channel.
+            CharSequence name = "MediaSession";
+            // The user-visible description of the channel.
+            String description = "MediaSession and MediaPlayer";
+            int importance = NotificationManager.IMPORTANCE_LOW;
+            NotificationChannel mChannel = new NotificationChannel(CHANNEL_ID, name, importance);
+            // Configure the notification channel.
+            mChannel.setDescription(description);
+            mChannel.enableLights(true);
+            // Sets the notification light color for notifications posted to this
+            // channel, if the device supports this feature.
+            mChannel.setLightColor(Color.RED);
+            mChannel.enableVibration(true);
+            mChannel.setVibrationPattern(
+                    new long[]{100, 200, 300, 400, 500, 400, 300, 200, 400});
+            mNotificationManager.createNotificationChannel(mChannel);
+            Log.d(TAG, "createChannel: New channel created");
+        } else {
+            Log.d(TAG, "createChannel: Existing channel reused");
+        }
+    }
+
+    private boolean isAndroidOOrHigher() {
+        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
+    }
+
+    private PendingIntent createContentIntent() {
+        Intent openUI = new Intent(mService, MainActivity.class);
+        openUI.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
+        return PendingIntent.getActivity(
+                mService, REQUEST_CODE, openUI, PendingIntent.FLAG_CANCEL_CURRENT);
+    }
+
+}
\ No newline at end of file
diff --git a/media/MediaBrowserService/Application/src/main/java/com/example/android/mediasession/service/players/MediaPlayerAdapter.java b/media/MediaBrowserService/Application/src/main/java/com/example/android/mediasession/service/players/MediaPlayerAdapter.java
new file mode 100644
index 0000000..d24f44f
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/java/com/example/android/mediasession/service/players/MediaPlayerAdapter.java
@@ -0,0 +1,257 @@
+/*
+ * Copyright 2017 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.mediasession.service.players;
+
+import android.content.Context;
+import android.content.res.AssetFileDescriptor;
+import android.media.MediaPlayer;
+import android.os.SystemClock;
+import android.support.v4.media.MediaMetadataCompat;
+import android.support.v4.media.session.PlaybackStateCompat;
+import android.util.Log;
+
+import com.example.android.mediasession.service.PlaybackInfoListener;
+import com.example.android.mediasession.service.PlayerAdapter;
+import com.example.android.mediasession.service.contentcatalogs.MusicLibrary;
+import com.example.android.mediasession.ui.MainActivity;
+
+/**
+ * Exposes the functionality of the {@link MediaPlayer} and implements the {@link PlayerAdapter}
+ * so that {@link MainActivity} can control music playback.
+ */
+public final class MediaPlayerAdapter extends PlayerAdapter {
+
+    private final Context mContext;
+    private MediaPlayer mMediaPlayer;
+    private String mFilename;
+    private PlaybackInfoListener mPlaybackInfoListener;
+    private MediaMetadataCompat mCurrentMedia;
+    private int mState;
+    private boolean mCurrentMediaPlayedToCompletion;
+
+    // Work-around for a MediaPlayer bug related to the behavior of MediaPlayer.seekTo()
+    // while not playing.
+    private int mSeekWhileNotPlaying = -1;
+
+    public MediaPlayerAdapter(Context context, PlaybackInfoListener listener) {
+        super(context);
+        mContext = context.getApplicationContext();
+        mPlaybackInfoListener = listener;
+    }
+
+    /**
+     * Once the {@link MediaPlayer} is released, it can't be used again, and another one has to be
+     * created. In the onStop() method of the {@link MainActivity} the {@link MediaPlayer} is
+     * released. Then in the onStart() of the {@link MainActivity} a new {@link MediaPlayer}
+     * object has to be created. That's why this method is private, and called by load(int) and
+     * not the constructor.
+     */
+    private void initializeMediaPlayer() {
+        if (mMediaPlayer == null) {
+            mMediaPlayer = new MediaPlayer();
+            mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
+                @Override
+                public void onCompletion(MediaPlayer mediaPlayer) {
+                    mPlaybackInfoListener.onPlaybackCompleted();
+
+                    // Set the state to "paused" because it most closely matches the state
+                    // in MediaPlayer with regards to available state transitions compared
+                    // to "stop".
+                    // Paused allows: seekTo(), start(), pause(), stop()
+                    // Stop allows: stop()
+                    setNewState(PlaybackStateCompat.STATE_PAUSED);
+                }
+            });
+        }
+    }
+
+    // Implements PlaybackControl.
+    @Override
+    public void playFromMedia(MediaMetadataCompat metadata) {
+        mCurrentMedia = metadata;
+        final String mediaId = metadata.getDescription().getMediaId();
+        playFile(MusicLibrary.getMusicFilename(mediaId));
+    }
+
+    @Override
+    public MediaMetadataCompat getCurrentMedia() {
+        return mCurrentMedia;
+    }
+
+    private void playFile(String filename) {
+        boolean mediaChanged = (mFilename == null || !filename.equals(mFilename));
+        if (mCurrentMediaPlayedToCompletion) {
+            // Last audio file was played to completion, the resourceId hasn't changed, but the
+            // player was released, so force a reload of the media file for playback.
+            mediaChanged = true;
+            mCurrentMediaPlayedToCompletion = false;
+        }
+        if (!mediaChanged) {
+            if (!isPlaying()) {
+                play();
+            }
+            return;
+        } else {
+            release();
+        }
+
+        mFilename = filename;
+
+        initializeMediaPlayer();
+
+        try {
+            AssetFileDescriptor assetFileDescriptor = mContext.getAssets().openFd(mFilename);
+            mMediaPlayer.setDataSource(
+                    assetFileDescriptor.getFileDescriptor(),
+                    assetFileDescriptor.getStartOffset(),
+                    assetFileDescriptor.getLength());
+        } catch (Exception e) {
+            throw new RuntimeException("Failed to open file: " + mFilename, e);
+        }
+
+        try {
+            mMediaPlayer.prepare();
+        } catch (Exception e) {
+            throw new RuntimeException("Failed to open file: " + mFilename, e);
+        }
+
+        play();
+    }
+
+    @Override
+    public void onStop() {
+        // Regardless of whether or not the MediaPlayer has been created / started, the state must
+        // be updated, so that MediaNotificationManager can take down the notification.
+        setNewState(PlaybackStateCompat.STATE_STOPPED);
+        release();
+    }
+
+    private void release() {
+        if (mMediaPlayer != null) {
+            mMediaPlayer.release();
+            mMediaPlayer = null;
+        }
+    }
+
+    @Override
+    public boolean isPlaying() {
+        return mMediaPlayer != null && mMediaPlayer.isPlaying();
+    }
+
+    @Override
+    protected void onPlay() {
+        if (mMediaPlayer != null && !mMediaPlayer.isPlaying()) {
+            mMediaPlayer.start();
+            setNewState(PlaybackStateCompat.STATE_PLAYING);
+        }
+    }
+
+    @Override
+    protected void onPause() {
+        if (mMediaPlayer != null && mMediaPlayer.isPlaying()) {
+            mMediaPlayer.pause();
+            setNewState(PlaybackStateCompat.STATE_PAUSED);
+        }
+    }
+
+    // This is the main reducer for the player state machine.
+    private void setNewState(@PlaybackStateCompat.State int newPlayerState) {
+        mState = newPlayerState;
+
+        // Whether playback goes to completion, or whether it is stopped, the
+        // mCurrentMediaPlayedToCompletion is set to true.
+        if (mState == PlaybackStateCompat.STATE_STOPPED) {
+            mCurrentMediaPlayedToCompletion = true;
+        }
+
+        // Work around for MediaPlayer.getCurrentPosition() when it changes while not playing.
+        final long reportPosition;
+        if (mSeekWhileNotPlaying >= 0) {
+            reportPosition = mSeekWhileNotPlaying;
+
+            if (mState == PlaybackStateCompat.STATE_PLAYING) {
+                mSeekWhileNotPlaying = -1;
+            }
+        } else {
+            reportPosition = mMediaPlayer == null ? 0 : mMediaPlayer.getCurrentPosition();
+        }
+
+        final PlaybackStateCompat.Builder stateBuilder = new PlaybackStateCompat.Builder();
+        stateBuilder.setActions(getAvailableActions());
+        stateBuilder.setState(mState,
+                              reportPosition,
+                              1.0f,
+                              SystemClock.elapsedRealtime());
+        mPlaybackInfoListener.onPlaybackStateChange(stateBuilder.build());
+    }
+
+    /**
+     * Set the current capabilities available on this session. Note: If a capability is not
+     * listed in the bitmask of capabilities then the MediaSession will not handle it. For
+     * example, if you don't want ACTION_STOP to be handled by the MediaSession, then don't
+     * included it in the bitmask that's returned.
+     */
+    @PlaybackStateCompat.Actions
+    private long getAvailableActions() {
+        long actions = PlaybackStateCompat.ACTION_PLAY_FROM_MEDIA_ID
+                       | PlaybackStateCompat.ACTION_PLAY_FROM_SEARCH
+                       | PlaybackStateCompat.ACTION_SKIP_TO_NEXT
+                       | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS;
+        switch (mState) {
+            case PlaybackStateCompat.STATE_STOPPED:
+                actions |= PlaybackStateCompat.ACTION_PLAY
+                           | PlaybackStateCompat.ACTION_PAUSE;
+                break;
+            case PlaybackStateCompat.STATE_PLAYING:
+                actions |= PlaybackStateCompat.ACTION_STOP
+                           | PlaybackStateCompat.ACTION_PAUSE
+                           | PlaybackStateCompat.ACTION_SEEK_TO;
+                break;
+            case PlaybackStateCompat.STATE_PAUSED:
+                actions |= PlaybackStateCompat.ACTION_PLAY
+                           | PlaybackStateCompat.ACTION_STOP;
+                break;
+            default:
+                actions |= PlaybackStateCompat.ACTION_PLAY
+                           | PlaybackStateCompat.ACTION_PLAY_PAUSE
+                           | PlaybackStateCompat.ACTION_STOP
+                           | PlaybackStateCompat.ACTION_PAUSE;
+        }
+        return actions;
+    }
+
+    @Override
+    public void seekTo(long position) {
+        if (mMediaPlayer != null) {
+            if (!mMediaPlayer.isPlaying()) {
+                mSeekWhileNotPlaying = (int) position;
+            }
+            mMediaPlayer.seekTo((int) position);
+
+            // Set the state (to the current state) because the position changed and should
+            // be reported to clients.
+            setNewState(mState);
+        }
+    }
+
+    @Override
+    public void setVolume(float volume) {
+        if (mMediaPlayer != null) {
+            mMediaPlayer.setVolume(volume, volume);
+        }
+    }
+}
diff --git a/media/MediaBrowserService/Application/src/main/java/com/example/android/mediasession/ui/MainActivity.java b/media/MediaBrowserService/Application/src/main/java/com/example/android/mediasession/ui/MainActivity.java
new file mode 100644
index 0000000..4991789
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/java/com/example/android/mediasession/ui/MainActivity.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2017 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.mediasession.ui;
+
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v4.media.MediaMetadataCompat;
+import android.support.v4.media.session.MediaControllerCompat;
+import android.support.v4.media.session.PlaybackStateCompat;
+import android.support.v7.app.AppCompatActivity;
+import android.view.View;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.example.android.mediasession.R;
+import com.example.android.mediasession.client.MediaBrowserAdapter;
+import com.example.android.mediasession.service.contentcatalogs.MusicLibrary;
+
+public class MainActivity extends AppCompatActivity {
+
+    private ImageView mAlbumArt;
+    private TextView mTitleTextView;
+    private TextView mArtistTextView;
+    private ImageView mMediaControlsImage;
+    private MediaSeekBar mSeekBarAudio;
+
+    private MediaBrowserAdapter mMediaBrowserAdapter;
+
+    private boolean mIsPlaying;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_main);
+        initializeUI();
+        mMediaBrowserAdapter = new MediaBrowserAdapter(this);
+        mMediaBrowserAdapter.addListener(new MediaBrowserListener());
+    }
+
+    private void initializeUI() {
+        mTitleTextView = (TextView) findViewById(R.id.song_title);
+        mArtistTextView = (TextView) findViewById(R.id.song_artist);
+        mAlbumArt = (ImageView) findViewById(R.id.album_art);
+        mMediaControlsImage = (ImageView) findViewById(R.id.media_controls);
+        mSeekBarAudio = (MediaSeekBar) findViewById(R.id.seekbar_audio);
+
+        final Button buttonPrevious = (Button) findViewById(R.id.button_previous);
+        buttonPrevious.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                mMediaBrowserAdapter.getTransportControls().skipToPrevious();
+            }
+        });
+
+        final Button buttonPlay = (Button) findViewById(R.id.button_play);
+        buttonPlay.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                if (mIsPlaying) {
+                    mMediaBrowserAdapter.getTransportControls().pause();
+                } else {
+                    mMediaBrowserAdapter.getTransportControls().play();
+                }
+            }
+        });
+
+        final Button buttonNext = (Button) findViewById(R.id.button_next);
+        buttonNext.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                mMediaBrowserAdapter.getTransportControls().skipToNext();
+            }
+        });
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        mMediaBrowserAdapter.onStart();
+    }
+
+    @Override
+    public void onStop() {
+        super.onStop();
+        mSeekBarAudio.disconnectController();
+        mMediaBrowserAdapter.onStop();
+    }
+
+    private class MediaBrowserListener extends MediaBrowserAdapter.MediaBrowserChangeListener {
+
+        @Override
+        public void onConnected(@Nullable MediaControllerCompat mediaController) {
+            super.onConnected(mediaController);
+            mSeekBarAudio.setMediaController(mediaController);
+        }
+
+        @Override
+        public void onPlaybackStateChanged(PlaybackStateCompat playbackState) {
+            mIsPlaying = playbackState != null &&
+                    playbackState.getState() == PlaybackStateCompat.STATE_PLAYING;
+            mMediaControlsImage.setPressed(mIsPlaying);
+        }
+
+        @Override
+        public void onMetadataChanged(MediaMetadataCompat mediaMetadata) {
+            if (mediaMetadata == null) {
+                return;
+            }
+            mTitleTextView.setText(
+                    mediaMetadata.getString(MediaMetadataCompat.METADATA_KEY_TITLE));
+            mArtistTextView.setText(
+                    mediaMetadata.getString(MediaMetadataCompat.METADATA_KEY_ARTIST));
+            mAlbumArt.setImageBitmap(MusicLibrary.getAlbumBitmap(
+                    MainActivity.this,
+                    mediaMetadata.getString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID)));
+        }
+    }
+}
diff --git a/media/MediaBrowserService/Application/src/main/java/com/example/android/mediasession/ui/MediaSeekBar.java b/media/MediaBrowserService/Application/src/main/java/com/example/android/mediasession/ui/MediaSeekBar.java
new file mode 100644
index 0000000..71de9ca
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/java/com/example/android/mediasession/ui/MediaSeekBar.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright 2017 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.mediasession.ui;
+
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.support.v4.media.MediaMetadataCompat;
+import android.support.v4.media.session.MediaControllerCompat;
+import android.support.v4.media.session.MediaSessionCompat;
+import android.support.v4.media.session.PlaybackStateCompat;
+import android.support.v7.widget.AppCompatSeekBar;
+import android.util.AttributeSet;
+import android.view.animation.LinearInterpolator;
+import android.widget.SeekBar;
+
+/**
+ * SeekBar that can be used with a {@link MediaSessionCompat} to track and seek in playing
+ * media.
+ */
+
+public class MediaSeekBar extends AppCompatSeekBar {
+    private MediaControllerCompat mMediaController;
+    private ControllerCallback mControllerCallback;
+
+    private boolean mIsTracking = false;
+    private OnSeekBarChangeListener mOnSeekBarChangeListener = new OnSeekBarChangeListener() {
+        @Override
+        public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+        }
+
+        @Override
+        public void onStartTrackingTouch(SeekBar seekBar) {
+            mIsTracking = true;
+        }
+
+        @Override
+        public void onStopTrackingTouch(SeekBar seekBar) {
+            mMediaController.getTransportControls().seekTo(getProgress());
+            mIsTracking = false;
+        }
+    };
+    private ValueAnimator mProgressAnimator;
+
+    public MediaSeekBar(Context context) {
+        super(context);
+        super.setOnSeekBarChangeListener(mOnSeekBarChangeListener);
+    }
+
+    public MediaSeekBar(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        super.setOnSeekBarChangeListener(mOnSeekBarChangeListener);
+    }
+
+    public MediaSeekBar(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        super.setOnSeekBarChangeListener(mOnSeekBarChangeListener);
+    }
+
+    @Override
+    public void setOnSeekBarChangeListener(OnSeekBarChangeListener l) {
+        // Prohibit adding seek listeners to this subclass.
+    }
+
+    public void setMediaController(final MediaControllerCompat mediaController) {
+        if (mediaController != null) {
+            mControllerCallback = new ControllerCallback();
+            mediaController.registerCallback(mControllerCallback);
+        } else if (mMediaController != null) {
+            mMediaController.unregisterCallback(mControllerCallback);
+            mControllerCallback = null;
+        }
+        mMediaController = mediaController;
+    }
+
+    public void disconnectController() {
+        if (mMediaController != null) {
+            mMediaController.unregisterCallback(mControllerCallback);
+            mControllerCallback = null;
+            mMediaController = null;
+        }
+    }
+
+    private class ControllerCallback
+            extends MediaControllerCompat.Callback
+            implements ValueAnimator.AnimatorUpdateListener {
+
+        @Override
+        public void onSessionDestroyed() {
+            super.onSessionDestroyed();
+        }
+
+        @Override
+        public void onPlaybackStateChanged(PlaybackStateCompat state) {
+            super.onPlaybackStateChanged(state);
+
+            // If there's an ongoing animation, stop it now.
+            if (mProgressAnimator != null) {
+                mProgressAnimator.cancel();
+                mProgressAnimator = null;
+            }
+
+            final int progress = state != null
+                    ? (int) state.getPosition()
+                    : 0;
+            setProgress(progress);
+
+            // If the media is playing then the seekbar should follow it, and the easiest
+            // way to do that is to create a ValueAnimator to update it so the bar reaches
+            // the end of the media the same time as playback gets there (or close enough).
+            if (state != null && state.getState() == PlaybackStateCompat.STATE_PLAYING) {
+                final int timeToEnd = (int) ((getMax() - progress) / state.getPlaybackSpeed());
+
+                mProgressAnimator = ValueAnimator.ofInt(progress, getMax())
+                        .setDuration(timeToEnd);
+                mProgressAnimator.setInterpolator(new LinearInterpolator());
+                mProgressAnimator.addUpdateListener(this);
+                mProgressAnimator.start();
+            }
+        }
+
+        @Override
+        public void onMetadataChanged(MediaMetadataCompat metadata) {
+            super.onMetadataChanged(metadata);
+
+            final int max = metadata != null
+                    ? (int) metadata.getLong(MediaMetadataCompat.METADATA_KEY_DURATION)
+                    : 0;
+            setProgress(0);
+            setMax(max);
+        }
+
+        @Override
+        public void onAnimationUpdate(final ValueAnimator valueAnimator) {
+            // If the user is changing the slider, cancel the animation.
+            if (mIsTracking) {
+                valueAnimator.cancel();
+                return;
+            }
+
+            final int animatedIntValue = (int) valueAnimator.getAnimatedValue();
+            setProgress(animatedIntValue);
+        }
+    }
+}
diff --git a/media/MediaBrowserService/Application/src/main/res/drawable-hdpi/ic_launcher.png b/media/MediaBrowserService/Application/src/main/res/drawable-hdpi/ic_launcher.png
deleted file mode 100644
index 05ef6f6..0000000
--- a/media/MediaBrowserService/Application/src/main/res/drawable-hdpi/ic_launcher.png
+++ /dev/null
Binary files differ
diff --git a/media/MediaBrowserService/Application/src/main/res/drawable-hdpi/ic_notification.png b/media/MediaBrowserService/Application/src/main/res/drawable-hdpi/ic_notification.png
deleted file mode 100644
index a8cba40..0000000
--- a/media/MediaBrowserService/Application/src/main/res/drawable-hdpi/ic_notification.png
+++ /dev/null
Binary files differ
diff --git a/media/MediaBrowserService/Application/src/main/res/drawable-hdpi/ic_pause_white_24dp.png b/media/MediaBrowserService/Application/src/main/res/drawable-hdpi/ic_pause_white_24dp.png
index b4bdbb5..f7b3c1e 100644
--- a/media/MediaBrowserService/Application/src/main/res/drawable-hdpi/ic_pause_white_24dp.png
+++ b/media/MediaBrowserService/Application/src/main/res/drawable-hdpi/ic_pause_white_24dp.png
Binary files differ
diff --git a/media/MediaBrowserService/Application/src/main/res/drawable-hdpi/ic_play_arrow_white_24dp.png b/media/MediaBrowserService/Application/src/main/res/drawable-hdpi/ic_play_arrow_white_24dp.png
index 164385d..be73548 100644
--- a/media/MediaBrowserService/Application/src/main/res/drawable-hdpi/ic_play_arrow_white_24dp.png
+++ b/media/MediaBrowserService/Application/src/main/res/drawable-hdpi/ic_play_arrow_white_24dp.png
Binary files differ
diff --git a/media/MediaBrowserService/Application/src/main/res/drawable-hdpi/ic_shuffle_white_24dp.png b/media/MediaBrowserService/Application/src/main/res/drawable-hdpi/ic_shuffle_white_24dp.png
deleted file mode 100644
index 3eeb0ef..0000000
--- a/media/MediaBrowserService/Application/src/main/res/drawable-hdpi/ic_shuffle_white_24dp.png
+++ /dev/null
Binary files differ
diff --git a/media/MediaBrowserService/Application/src/main/res/drawable-hdpi/ic_skip_next_white_24dp.png b/media/MediaBrowserService/Application/src/main/res/drawable-hdpi/ic_skip_next_white_24dp.png
index 4eaf7ca..3c6ae11 100644
--- a/media/MediaBrowserService/Application/src/main/res/drawable-hdpi/ic_skip_next_white_24dp.png
+++ b/media/MediaBrowserService/Application/src/main/res/drawable-hdpi/ic_skip_next_white_24dp.png
Binary files differ
diff --git a/media/MediaBrowserService/Application/src/main/res/drawable-hdpi/ic_skip_previous_white_24dp.png b/media/MediaBrowserService/Application/src/main/res/drawable-hdpi/ic_skip_previous_white_24dp.png
index e59dedb..6883e61 100644
--- a/media/MediaBrowserService/Application/src/main/res/drawable-hdpi/ic_skip_previous_white_24dp.png
+++ b/media/MediaBrowserService/Application/src/main/res/drawable-hdpi/ic_skip_previous_white_24dp.png
Binary files differ
diff --git a/media/MediaBrowserService/Application/src/main/res/drawable-hdpi/ic_stat_image_audiotrack.png b/media/MediaBrowserService/Application/src/main/res/drawable-hdpi/ic_stat_image_audiotrack.png
new file mode 100755
index 0000000..bbbaa0e
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/res/drawable-hdpi/ic_stat_image_audiotrack.png
Binary files differ
diff --git a/media/MediaBrowserService/Application/src/main/res/drawable-mdpi/ic_launcher.png b/media/MediaBrowserService/Application/src/main/res/drawable-mdpi/ic_launcher.png
deleted file mode 100644
index f894fb8..0000000
--- a/media/MediaBrowserService/Application/src/main/res/drawable-mdpi/ic_launcher.png
+++ /dev/null
Binary files differ
diff --git a/media/MediaBrowserService/Application/src/main/res/drawable-mdpi/ic_pause_white_24dp.png b/media/MediaBrowserService/Application/src/main/res/drawable-mdpi/ic_pause_white_24dp.png
new file mode 100644
index 0000000..499341f
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/res/drawable-mdpi/ic_pause_white_24dp.png
Binary files differ
diff --git a/media/MediaBrowserService/Application/src/main/res/drawable-mdpi/ic_play_arrow_white_24dp.png b/media/MediaBrowserService/Application/src/main/res/drawable-mdpi/ic_play_arrow_white_24dp.png
new file mode 100644
index 0000000..bebdf37
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/res/drawable-mdpi/ic_play_arrow_white_24dp.png
Binary files differ
diff --git a/media/MediaBrowserService/Application/src/main/res/drawable-mdpi/ic_skip_next_white_24dp.png b/media/MediaBrowserService/Application/src/main/res/drawable-mdpi/ic_skip_next_white_24dp.png
new file mode 100644
index 0000000..d55f7e6
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/res/drawable-mdpi/ic_skip_next_white_24dp.png
Binary files differ
diff --git a/media/MediaBrowserService/Application/src/main/res/drawable-mdpi/ic_skip_previous_white_24dp.png b/media/MediaBrowserService/Application/src/main/res/drawable-mdpi/ic_skip_previous_white_24dp.png
new file mode 100644
index 0000000..259797d
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/res/drawable-mdpi/ic_skip_previous_white_24dp.png
Binary files differ
diff --git a/media/MediaBrowserService/Application/src/main/res/drawable-mdpi/ic_stat_image_audiotrack.png b/media/MediaBrowserService/Application/src/main/res/drawable-mdpi/ic_stat_image_audiotrack.png
new file mode 100755
index 0000000..e445a0c
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/res/drawable-mdpi/ic_stat_image_audiotrack.png
Binary files differ
diff --git a/media/MediaBrowserService/Application/src/main/res/drawable-nodpi/album_jazz_blues.jpg b/media/MediaBrowserService/Application/src/main/res/drawable-nodpi/album_jazz_blues.jpg
new file mode 100644
index 0000000..d3ffefc
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/res/drawable-nodpi/album_jazz_blues.jpg
Binary files differ
diff --git a/media/MediaBrowserService/Application/src/main/res/drawable-nodpi/album_youtube_audio_library_rock_2.jpg b/media/MediaBrowserService/Application/src/main/res/drawable-nodpi/album_youtube_audio_library_rock_2.jpg
new file mode 100644
index 0000000..8d5020c
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/res/drawable-nodpi/album_youtube_audio_library_rock_2.jpg
Binary files differ
diff --git a/media/MediaBrowserService/Application/src/main/res/drawable-xhdpi/ic_equalizer_white_24dp.png b/media/MediaBrowserService/Application/src/main/res/drawable-xhdpi/ic_equalizer_white_24dp.png
deleted file mode 100644
index dbba844..0000000
--- a/media/MediaBrowserService/Application/src/main/res/drawable-xhdpi/ic_equalizer_white_24dp.png
+++ /dev/null
Binary files differ
diff --git a/media/MediaBrowserService/Application/src/main/res/drawable-xhdpi/ic_launcher.png b/media/MediaBrowserService/Application/src/main/res/drawable-xhdpi/ic_launcher.png
deleted file mode 100644
index 43ade5e..0000000
--- a/media/MediaBrowserService/Application/src/main/res/drawable-xhdpi/ic_launcher.png
+++ /dev/null
Binary files differ
diff --git a/media/MediaBrowserService/Application/src/main/res/drawable-xhdpi/ic_pause_white_24dp.png b/media/MediaBrowserService/Application/src/main/res/drawable-xhdpi/ic_pause_white_24dp.png
index 14b6d17..e31b5b0 100644
--- a/media/MediaBrowserService/Application/src/main/res/drawable-xhdpi/ic_pause_white_24dp.png
+++ b/media/MediaBrowserService/Application/src/main/res/drawable-xhdpi/ic_pause_white_24dp.png
Binary files differ
diff --git a/media/MediaBrowserService/Application/src/main/res/drawable-xhdpi/ic_play_arrow_white_24dp.png b/media/MediaBrowserService/Application/src/main/res/drawable-xhdpi/ic_play_arrow_white_24dp.png
index a55d199..929ffec 100644
--- a/media/MediaBrowserService/Application/src/main/res/drawable-xhdpi/ic_play_arrow_white_24dp.png
+++ b/media/MediaBrowserService/Application/src/main/res/drawable-xhdpi/ic_play_arrow_white_24dp.png
Binary files differ
diff --git a/media/MediaBrowserService/Application/src/main/res/drawable-xhdpi/ic_shuffle_white_24dp.png b/media/MediaBrowserService/Application/src/main/res/drawable-xhdpi/ic_shuffle_white_24dp.png
deleted file mode 100644
index 8ce3a60..0000000
--- a/media/MediaBrowserService/Application/src/main/res/drawable-xhdpi/ic_shuffle_white_24dp.png
+++ /dev/null
Binary files differ
diff --git a/media/MediaBrowserService/Application/src/main/res/drawable-xhdpi/ic_skip_next_white_24dp.png b/media/MediaBrowserService/Application/src/main/res/drawable-xhdpi/ic_skip_next_white_24dp.png
index f282b92..343b7bb 100644
--- a/media/MediaBrowserService/Application/src/main/res/drawable-xhdpi/ic_skip_next_white_24dp.png
+++ b/media/MediaBrowserService/Application/src/main/res/drawable-xhdpi/ic_skip_next_white_24dp.png
Binary files differ
diff --git a/media/MediaBrowserService/Application/src/main/res/drawable-xhdpi/ic_skip_previous_white_24dp.png b/media/MediaBrowserService/Application/src/main/res/drawable-xhdpi/ic_skip_previous_white_24dp.png
index 2522877..e926c5e 100644
--- a/media/MediaBrowserService/Application/src/main/res/drawable-xhdpi/ic_skip_previous_white_24dp.png
+++ b/media/MediaBrowserService/Application/src/main/res/drawable-xhdpi/ic_skip_previous_white_24dp.png
Binary files differ
diff --git a/media/MediaBrowserService/Application/src/main/res/drawable-xhdpi/ic_stat_image_audiotrack.png b/media/MediaBrowserService/Application/src/main/res/drawable-xhdpi/ic_stat_image_audiotrack.png
new file mode 100755
index 0000000..5481659
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/res/drawable-xhdpi/ic_stat_image_audiotrack.png
Binary files differ
diff --git a/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_by_genre.png b/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_by_genre.png
deleted file mode 100644
index da3b4a7..0000000
--- a/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_by_genre.png
+++ /dev/null
Binary files differ
diff --git a/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_default_art.png b/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_default_art.png
deleted file mode 100644
index dfb9e67..0000000
--- a/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_default_art.png
+++ /dev/null
Binary files differ
diff --git a/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_equalizer_white_24dp.png b/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_equalizer_white_24dp.png
deleted file mode 100644
index b82a8d9..0000000
--- a/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_equalizer_white_24dp.png
+++ /dev/null
Binary files differ
diff --git a/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_launcher.png b/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_launcher.png
deleted file mode 100644
index 3058c27..0000000
--- a/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_launcher.png
+++ /dev/null
Binary files differ
diff --git a/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_media_with_pause.png b/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_media_with_pause.png
new file mode 100644
index 0000000..c6041da
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_media_with_pause.png
Binary files differ
diff --git a/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_media_with_play.png b/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_media_with_play.png
new file mode 100644
index 0000000..a0da57b
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_media_with_play.png
Binary files differ
diff --git a/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_pause_white_24dp.png b/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_pause_white_24dp.png
index 72dfa9f..1e71ba3 100644
--- a/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_pause_white_24dp.png
+++ b/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_pause_white_24dp.png
Binary files differ
diff --git a/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_play_arrow_white_24dp.png b/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_play_arrow_white_24dp.png
index 043acd8..4abfe88 100644
--- a/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_play_arrow_white_24dp.png
+++ b/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_play_arrow_white_24dp.png
Binary files differ
diff --git a/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_shuffle_white_24dp.png b/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_shuffle_white_24dp.png
deleted file mode 100644
index 718b6b5..0000000
--- a/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_shuffle_white_24dp.png
+++ /dev/null
Binary files differ
diff --git a/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_skip_next_white_24dp.png b/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_skip_next_white_24dp.png
index 4fe6088..616e47f 100644
--- a/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_skip_next_white_24dp.png
+++ b/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_skip_next_white_24dp.png
Binary files differ
diff --git a/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_skip_previous_white_24dp.png b/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_skip_previous_white_24dp.png
index 2c9310a..1ec9d65 100644
--- a/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_skip_previous_white_24dp.png
+++ b/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_skip_previous_white_24dp.png
Binary files differ
diff --git a/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_star_off.png b/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_star_off.png
deleted file mode 100644
index fb7afb0..0000000
--- a/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_star_off.png
+++ /dev/null
Binary files differ
diff --git a/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_star_on.png b/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_star_on.png
deleted file mode 100644
index 6f7fc75..0000000
--- a/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_star_on.png
+++ /dev/null
Binary files differ
diff --git a/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_stat_image_audiotrack.png b/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_stat_image_audiotrack.png
new file mode 100755
index 0000000..f214042
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/res/drawable-xxhdpi/ic_stat_image_audiotrack.png
Binary files differ
diff --git a/media/MediaBrowserService/Application/src/main/res/drawable-xxxhdpi/ic_launcher.png b/media/MediaBrowserService/Application/src/main/res/drawable-xxxhdpi/ic_launcher.png
deleted file mode 100644
index 6b4e4a2..0000000
--- a/media/MediaBrowserService/Application/src/main/res/drawable-xxxhdpi/ic_launcher.png
+++ /dev/null
Binary files differ
diff --git a/media/MediaBrowserService/Application/src/main/res/drawable/ic_play_pause_toggle.xml b/media/MediaBrowserService/Application/src/main/res/drawable/ic_play_pause_toggle.xml
new file mode 100644
index 0000000..2fe59d9
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/res/drawable/ic_play_pause_toggle.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2017 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:drawable="@drawable/ic_media_with_play" android:state_pressed="false" />
+    <item android:drawable="@drawable/ic_media_with_pause" />
+</selector>
\ No newline at end of file
diff --git a/media/MediaBrowserService/Application/src/main/res/layout/activity_main.xml b/media/MediaBrowserService/Application/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..76b8699
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/res/layout/activity_main.xml
@@ -0,0 +1,133 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2017 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.
+  -->
+
+<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+                                             xmlns:app="http://schemas.android.com/apk/res-auto"
+                                             xmlns:tools="http://schemas.android.com/tools"
+                                             android:id="@+id/frameLayout"
+                                             android:layout_width="match_parent"
+                                             android:layout_height="match_parent"
+                                             tools:background="@drawable/album_jazz_blues">
+
+    <ImageView
+        android:id="@+id/album_art"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:scaleType="fitXY"
+        tools:ignore="ContentDescription" />
+
+    <View
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:background="#a0ffffff"
+        app:layout_constraintBottom_toBottomOf="@+id/song_artist"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        tools:ignore="ContentDescription" />
+
+    <TextView
+        android:id="@+id/song_title"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="0dp"
+        android:gravity="center"
+        android:textAppearance="@style/TextAppearance.AppCompat.Large"
+        app:layout_constraintBottom_toBottomOf="@+id/song_artist"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        tools:text="Song Title" />
+
+    <TextView
+        android:id="@+id/song_artist"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:gravity="center"
+        android:paddingBottom="12dp"
+        android:textAppearance="@style/TextAppearance.AppCompat.Medium"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/song_title"
+        tools:text="Song Artist" />
+
+    <ImageView
+        android:id="@+id/media_controls"
+        android:layout_width="192dp"
+        android:layout_height="192dp"
+        android:alpha=".9"
+        android:src="@drawable/ic_play_pause_toggle"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="@+id/guideline"
+        tools:ignore="ContentDescription" />
+
+    <Button
+        android:id="@+id/button_previous"
+        android:layout_width="50dp"
+        android:layout_height="0dp"
+        android:background="@android:color/transparent"
+        android:contentDescription="@string/label_previous"
+        android:text=""
+        app:layout_constraintBottom_toBottomOf="@+id/media_controls"
+        app:layout_constraintStart_toStartOf="@+id/media_controls"
+        app:layout_constraintTop_toTopOf="@+id/media_controls" />
+
+    <Button
+        android:id="@+id/button_play"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:background="@android:color/transparent"
+        android:contentDescription="@string/label_play_pause"
+        android:text=""
+        app:layout_constraintBottom_toBottomOf="@+id/media_controls"
+        app:layout_constraintEnd_toStartOf="@+id/button_next"
+        app:layout_constraintStart_toEndOf="@+id/button_previous"
+        app:layout_constraintTop_toTopOf="@+id/media_controls" />
+
+    <Button
+        android:id="@+id/button_next"
+        android:layout_width="50dp"
+        android:layout_height="0dp"
+        android:layout_marginStart="8dp"
+        android:background="@android:color/transparent"
+        android:contentDescription="@string/label_next"
+        android:text=""
+        app:layout_constraintBottom_toBottomOf="@+id/media_controls"
+        app:layout_constraintEnd_toEndOf="@+id/media_controls"
+        app:layout_constraintTop_toTopOf="@+id/media_controls" />
+
+    <android.support.constraint.Guideline
+        android:id="@+id/guideline"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        app:layout_constraintGuide_percent=".5" />
+
+    <com.example.android.mediasession.ui.MediaSeekBar
+        android:id="@+id/seekbar_audio"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="16dp"
+        android:layout_marginStart="16dp"
+        android:paddingBottom="16dp"
+        android:paddingTop="16dp"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent" />
+
+</android.support.constraint.ConstraintLayout>
\ No newline at end of file
diff --git a/media/MediaBrowserService/Application/src/main/res/layout/activity_player.xml b/media/MediaBrowserService/Application/src/main/res/layout/activity_player.xml
deleted file mode 100644
index 64b47ee..0000000
--- a/media/MediaBrowserService/Application/src/main/res/layout/activity_player.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<!--
-  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/media/MediaBrowserService/Application/src/main/res/layout/fragment_list.xml b/media/MediaBrowserService/Application/src/main/res/layout/fragment_list.xml
deleted file mode 100644
index 8c5985b..0000000
--- a/media/MediaBrowserService/Application/src/main/res/layout/fragment_list.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<!--
-  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="match_parent"
-              android:background="?android:colorBackground"
-              android:orientation="vertical"
-              android:padding="@dimen/fragment_list_padding">
-
-    <ListView
-        android:id="@+id/list_view"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent">
-    </ListView>
-
-</LinearLayout>
diff --git a/media/MediaBrowserService/Application/src/main/res/layout/media_list_item.xml b/media/MediaBrowserService/Application/src/main/res/layout/media_list_item.xml
deleted file mode 100644
index c10ac51..0000000
--- a/media/MediaBrowserService/Application/src/main/res/layout/media_list_item.xml
+++ /dev/null
@@ -1,55 +0,0 @@
-<!--
-  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:orientation="vertical"
-        android:padding="@dimen/list_item_padding">
-
-        <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/media/MediaBrowserService/Application/src/main/res/mipmap-hdpi/ic_launcher.png b/media/MediaBrowserService/Application/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100755
index 0000000..1d1b1a0
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/media/MediaBrowserService/Application/src/main/res/mipmap-mdpi/ic_launcher.png b/media/MediaBrowserService/Application/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100755
index 0000000..11570be
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/media/MediaBrowserService/Application/src/main/res/mipmap-xhdpi/ic_launcher.png b/media/MediaBrowserService/Application/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100755
index 0000000..dc44e16
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/media/MediaBrowserService/Application/src/main/res/mipmap-xxhdpi/ic_launcher.png b/media/MediaBrowserService/Application/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100755
index 0000000..5826256
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/media/MediaBrowserService/Application/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/media/MediaBrowserService/Application/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100755
index 0000000..b226a47
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/media/MediaBrowserService/Application/src/main/res/values/colors.xml b/media/MediaBrowserService/Application/src/main/res/values/colors.xml
new file mode 100644
index 0000000..323a89b
--- /dev/null
+++ b/media/MediaBrowserService/Application/src/main/res/values/colors.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2017 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>
+    <color name="colorPrimary">#3F51B5</color>
+    <color name="colorPrimaryDark">#303F9F</color>
+    <color name="colorAccent">#FF4081</color>
+    <color name="notification_bg">#e91e63</color>
+</resources>
diff --git a/media/MediaBrowserService/Application/src/main/res/values/dimens.xml b/media/MediaBrowserService/Application/src/main/res/values/dimens.xml
deleted file mode 100644
index e57a8c9..0000000
--- a/media/MediaBrowserService/Application/src/main/res/values/dimens.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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="fragment_list_padding">16dp</dimen>
-    <dimen name="list_item_padding">4dp</dimen>
-    <dimen name="margin_text_view">6dp</dimen>
-</resources>
diff --git a/media/MediaBrowserService/Application/src/main/res/values/strings.xml b/media/MediaBrowserService/Application/src/main/res/values/strings.xml
index 9a4a50a..93b3eac 100644
--- a/media/MediaBrowserService/Application/src/main/res/values/strings.xml
+++ b/media/MediaBrowserService/Application/src/main/res/values/strings.xml
@@ -1,34 +1,25 @@
-<?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.
+  ~ Copyright 2017 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_name">MediaBrowserService Sample</string>
-    <string name="favorite">Favorite</string>
-    <string name="error_no_metadata">Unable to retrieve metadata.</string>
-    <string name="browse_genres">Genres</string>
-    <string name="browse_genre_subtitle">Songs by genre</string>
-    <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>
-    <string name="no_search_results">No search results.</string>
-
+    <string name="label_stop">Stop</string>
+    <string name="label_pause">Pause</string>
+    <string name="label_play">Play</string>
+    <string name="label_play_pause">Play and pause toggle</string>
+    <string name="label_previous">Previous track</string>
+    <string name="label_next">Next track</string>
 </resources>
diff --git a/media/MediaBrowserService/Application/src/main/res/values/strings_notifications.xml b/media/MediaBrowserService/Application/src/main/res/values/strings_notifications.xml
deleted file mode 100644
index 7e1ccd5..0000000
--- a/media/MediaBrowserService/Application/src/main/res/values/strings_notifications.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?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>
-
-    <string name="label_pause">Pause</string>
-    <string name="label_play">Play</string>
-    <string name="label_previous">Previous</string>
-    <string name="label_next">Next</string>
-
-</resources>
diff --git a/media/MediaBrowserService/Application/src/main/res/values/styles.xml b/media/MediaBrowserService/Application/src/main/res/values/styles.xml
index 5d91ab4..69ee900 100644
--- a/media/MediaBrowserService/Application/src/main/res/values/styles.xml
+++ b/media/MediaBrowserService/Application/src/main/res/values/styles.xml
@@ -1,45 +1,27 @@
-<?xml version="1.0" encoding="utf-8"?>
 <!--
-  Copyright (C) 2014 The Android Open Source Project
+  ~ Copyright 2017 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.
+  -->
 
-  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>
-    <color name="colorPrimary">#ffff5722</color>
-    <color name="colorPrimaryDark">#ffbf360c</color>
-    <color name="colorAccent">#ffff5722</color>
 
-    <style name="AppTheme" parent="Theme.AppCompat">
+    <!-- Base application theme. -->
+    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
+        <!-- Customize your theme here. -->
         <item name="colorPrimary">@color/colorPrimary</item>
         <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
         <item name="colorAccent">@color/colorAccent</item>
     </style>
 
-    <style name="CarTheme" parent="AppTheme">
-        <!-- colorPrimaryDark is currently used in Android Auto for:
-             - App background
-             - Drawer right side ("more" custom actions) background
-             - Notification icon badge tinting
-             - Overview “now playing” icon tinting
-         -->
-        <item name="colorPrimaryDark">#ffbf360c</item>
-
-        <!-- colorAccent is used in Android Auto for:
-             - Spinner
-             - progress bar
-             - floating action button background (Play/Pause in media apps)
-         -->
-        <item name="colorAccent">#ffff5722</item>
-    </style>
-
-</resources>
\ No newline at end of file
+</resources>
diff --git a/media/MediaBrowserService/Application/src/main/res/xml/automotive_app_desc.xml b/media/MediaBrowserService/Application/src/main/res/xml/automotive_app_desc.xml
deleted file mode 100644
index a84750b..0000000
--- a/media/MediaBrowserService/Application/src/main/res/xml/automotive_app_desc.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?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.
-  -->
-<automotiveApp>
-    <uses name="media"/>
-</automotiveApp>
diff --git a/media/MediaBrowserService/CONTRIB.md b/media/MediaBrowserService/CONTRIB.md
deleted file mode 100644
index 14a4fcf..0000000
--- a/media/MediaBrowserService/CONTRIB.md
+++ /dev/null
@@ -1,35 +0,0 @@
-# How to become a contributor and submit your own code
-
-## Contributor License Agreements
-
-We'd love to accept your sample apps and patches! Before we can take them, we
-have to jump a couple of legal hurdles.
-
-Please fill out either the individual or corporate Contributor License Agreement (CLA).
-
-  * If you are an individual writing original source code and you're sure you
-    own the intellectual property, then you'll need to sign an [individual CLA]
-    (https://developers.google.com/open-source/cla/individual).
-  * If you work for a company that wants to allow you to contribute your work,
-    then you'll need to sign a [corporate CLA]
-    (https://developers.google.com/open-source/cla/corporate).
-
-Follow either of the two links above to access the appropriate CLA and
-instructions for how to sign and return it. Once we receive it, we'll be able to
-accept your pull requests.
-
-## Contributing A Patch
-
-1. Submit an issue describing your proposed change to the repo in question.
-1. The repo owner will respond to your issue promptly.
-1. If your proposed change is accepted, and you haven't already done so, sign a
-   Contributor License Agreement (see details above).
-1. Fork the desired repo, develop and test your code changes.
-1. Ensure that your code adheres to the existing style in the sample to which
-   you are contributing. Refer to the
-   [Android Code Style Guide]
-   (https://source.android.com/source/code-style.html) for the
-   recommended coding standards for this organization.
-1. Ensure that your code has an appropriate set of unit tests which all pass.
-1. Submit a pull request.
-
diff --git a/media/MediaBrowserService/README.md b/media/MediaBrowserService/README.md
index 1f4333a..2deee4e 100644
--- a/media/MediaBrowserService/README.md
+++ b/media/MediaBrowserService/README.md
@@ -2,16 +2,22 @@
 Android MediaBrowserService Sample
 ===================================
 
-This sample shows how to implement an audio media app that provides
-media library metadata and playback controls through a standard
-service. It exposes a simple music library through the new
-MediaBrowserService and provides MediaSession callbacks. This allows
-it to be used in Android Auto, for example.
-When not connected to a car, the app has a very simple UI that browses
-the media library and provides simple playback controls. When
-connected to Android Auto, the same service provides data and callback
-to the Android Auto UI in the same manner as it provides them to the
-local UI.
+This sample shows how to implement a media app that allows
+background playback of audio, and provide a media library
+that is exposed to other apps.
+1. It allows other apps control media playback externally
+using MediaSession. This allows playback to be controlled by
+the Google Assistant, for example.
+2. It exposes a simple music library through MediaBrowserService.
+And it provides MediaSession callbacks. This allows it to be used
+by Android Auto, for example.
+When not connected to a car, the app has a very simple UI that
+allows for playback as well as skip to previous and next tracks.
+To learn more about MediaSession and MediaBrowserService, read
+this [article on Medium](https://medium.com/google-developers/understanding-mediasession-part-4-4-dcc77c535f99)
+that goes into the architectural details of these APIs.
+
+<img src="screenshots/architecture.png" height="400" alt="Architecture Diagram"/>
 
 Introduction
 ------------
@@ -72,14 +78,14 @@
 Pre-requisites
 --------------
 
-- Android SDK 25
-- Android Build Tools v25.0.2
+- Android SDK 26
+- Android Build Tools v26.0.1
 - Android Support Repository
 
 Screenshots
 -------------
 
-<img src="screenshots/1-main.png" height="400" alt="Screenshot"/> <img src="screenshots/2-music-play.png" height="400" alt="Screenshot"/> <img src="screenshots/3-music-notification.png" height="400" alt="Screenshot"/> 
+<img src="screenshots/1-main.png" height="400" alt="Screenshot"/> <img src="screenshots/2-notification.png" height="400" alt="Screenshot"/> 
 
 Getting Started
 ---------------
diff --git a/media/MediaBrowserService/gradle/wrapper/gradle-wrapper.properties b/media/MediaBrowserService/gradle/wrapper/gradle-wrapper.properties
index fdc22bf..a93175f 100644
--- a/media/MediaBrowserService/gradle/wrapper/gradle-wrapper.properties
+++ b/media/MediaBrowserService/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Wed Mar 22 15:15:46 PDT 2017
+#Tue Sep 19 11:11:29 PDT 2017
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-3.4.1-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip
diff --git a/media/MediaBrowserService/packaging.yaml b/media/MediaBrowserService/packaging.yaml
index bcd9ac5..b1d7bbd 100644
--- a/media/MediaBrowserService/packaging.yaml
+++ b/media/MediaBrowserService/packaging.yaml
@@ -10,6 +10,6 @@
 languages:    [Java]
 solutions:    [Mobile]
 github:       googlesamples/android-MediaBrowserService
-level:        BEGINNER
-icon:         MediaBrowserServiceSample/src/main/res/drawable-xxhdpi/ic_launcher.png
+level:        ADVANCED
+icon:         MediaBrowserServiceSample/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
 license:      apache2-android
diff --git a/media/MediaBrowserService/screenshots/1-main.png b/media/MediaBrowserService/screenshots/1-main.png
index 1b17d0e..f5e08a2 100644
--- a/media/MediaBrowserService/screenshots/1-main.png
+++ b/media/MediaBrowserService/screenshots/1-main.png
Binary files differ
diff --git a/media/MediaBrowserService/screenshots/2-music-play.png b/media/MediaBrowserService/screenshots/2-music-play.png
deleted file mode 100644
index 1c1439c..0000000
--- a/media/MediaBrowserService/screenshots/2-music-play.png
+++ /dev/null
Binary files differ
diff --git a/media/MediaBrowserService/screenshots/2-notification.png b/media/MediaBrowserService/screenshots/2-notification.png
new file mode 100644
index 0000000..92ed70e
--- /dev/null
+++ b/media/MediaBrowserService/screenshots/2-notification.png
Binary files differ
diff --git a/media/MediaBrowserService/screenshots/3-music-notification.png b/media/MediaBrowserService/screenshots/3-music-notification.png
deleted file mode 100644
index c86c045..0000000
--- a/media/MediaBrowserService/screenshots/3-music-notification.png
+++ /dev/null
Binary files differ
diff --git a/media/MediaBrowserService/screenshots/architecture.png b/media/MediaBrowserService/screenshots/architecture.png
new file mode 100644
index 0000000..7cc48bc
--- /dev/null
+++ b/media/MediaBrowserService/screenshots/architecture.png
Binary files differ
diff --git a/media/MediaBrowserService/screenshots/icon-web.png b/media/MediaBrowserService/screenshots/icon-web.png
index 99928a8..df4c249 100644
--- a/media/MediaBrowserService/screenshots/icon-web.png
+++ b/media/MediaBrowserService/screenshots/icon-web.png
Binary files differ
diff --git a/media/MediaBrowserService/template-params.xml b/media/MediaBrowserService/template-params.xml
index 737fadb..9abe212 100644
--- a/media/MediaBrowserService/template-params.xml
+++ b/media/MediaBrowserService/template-params.xml
@@ -17,14 +17,15 @@
 <sample>
     <name>MediaBrowserService</name>
     <group>Media</group>
-    <package>com.example.android.mediabrowserservice</package>
+    <package>com.example.android.mediasession</package>
 
     <minSdk>19</minSdk>
+    <auto_add_support_lib>true</auto_add_support_lib>
+    <use_java8>true</use_java8>
 
     <!-- Include additional dependencies here.-->
-    <dependency>com.android.support:support-compat:25.3.0</dependency>
-    <dependency>com.android.support:support-media-compat:25.3.0</dependency>
-    <dependency>com.android.support:appcompat-v7:25.3.0</dependency>
+    <dependency>com.android.support:appcompat-v7:26.1.0</dependency>
+    <dependency>com.android.support.constraint:constraint-layout:1.0.2</dependency>
 
     <strings>
         <intro>
@@ -32,22 +33,14 @@
 This sample shows how to implement an audio media app that provides
 media library metadata and playback controls through a standard
 service. It exposes a simple music library through the new
-MediaBrowserService and provides MediaSession callbacks. This allows
-it to be used in Android Auto, for example.
-When not connected to a car, the app has a very simple UI that browses
-the media library and provides simple playback controls. When
-connected to Android Auto, the same service provides data and callback
-to the Android Auto UI in the same manner as it provides them to the
-local UI.
-]]></intro>
+MediaBrowserService and provides MediaSession callbacks.]]>
+        </intro>
     </strings>
 
     <!-- The basic templates have already been enabled. Uncomment more as desired. -->
     <template src="base-build" />
     <template src="base-application" />
 
-    <use_support_library_vector_drawables>true</use_support_library_vector_drawables>
-
     <metadata>
     <status>PUBLISHED</status>
     <categories>Media</categories>
@@ -58,28 +51,33 @@
     <icon>screenshots/icon-web.png</icon>
     <screenshots>
         <img>screenshots/1-main.png</img>
-        <img>screenshots/2-music-play.png</img>
-        <img>screenshots/3-music-notification.png</img>
+        <img>screenshots/2-notification.png</img>
     </screenshots>
     <api_refs>
         <android>android.support.v4.media.MediaBrowserServiceCompat</android>
         <android>android.support.v4.media.MediaBrowserCompat</android>
         <android>android.support.v4.media.session.MediaSessionCompat</android>
         <android>android.support.v4.media.session.MediaControllerCompat</android>
-        <android>android.support.v7.app.NotificationCompat.MediaStyle</android>
+        <android>android.support.v4.media.app.NotificationCompat.MediaStyle</android>
     </api_refs>
     <description>
 <![CDATA[
-This sample shows how to implement an audio media app that provides
-media library metadata and playback controls through a standard
-service. It exposes a simple music library through the new
-MediaBrowserService and provides MediaSession callbacks. This allows
-it to be used in Android Auto, for example.
-When not connected to a car, the app has a very simple UI that browses
-the media library and provides simple playback controls. When
-connected to Android Auto, the same service provides data and callback
-to the Android Auto UI in the same manner as it provides them to the
-local UI.
+This sample shows how to implement a media app that allows
+background playback of audio, and provide a media library
+that is exposed to other apps.
+1. It allows other apps control media playback externally
+using MediaSession. This allows playback to be controlled by
+the Google Assistant, for example.
+2. It exposes a simple music library through MediaBrowserService.
+And it provides MediaSession callbacks. This allows it to be used
+by Android Auto, for example.
+When not connected to a car, the app has a very simple UI that
+allows for playback as well as skip to previous and next tracks.
+To learn more about MediaSession and MediaBrowserService, read
+this [article on Medium](https://medium.com/google-developers/understanding-mediasession-part-4-4-dcc77c535f99)
+that goes into the architectural details of these APIs.
+
+<img src="screenshots/architecture.png" height="400" alt="Architecture Diagram"/>
 ]]></description>
     <intro>
 <![CDATA[