Added Android code
[wl-app.git] / Android / app / src / main / java / com / moiseum / wolnelektury / view / player / service / MediaBrowserHelper.java
diff --git a/Android/app/src/main/java/com/moiseum/wolnelektury/view/player/service/MediaBrowserHelper.java b/Android/app/src/main/java/com/moiseum/wolnelektury/view/player/service/MediaBrowserHelper.java
new file mode 100755 (executable)
index 0000000..413b84b
--- /dev/null
@@ -0,0 +1,249 @@
+/*
+ * 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.moiseum.wolnelektury.view.player.service;
+
+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.media.MediaBrowserCompat;
+import android.support.v4.media.MediaBrowserServiceCompat;
+import android.support.v4.media.MediaMetadataCompat;
+import android.support.v4.media.session.MediaControllerCompat;
+import android.support.v4.media.session.MediaControllerCompat.Callback;
+import android.support.v4.media.session.MediaSessionCompat;
+import android.support.v4.media.session.PlaybackStateCompat;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Helper class for a MediaBrowser that handles connecting, disconnecting,
+ * and basic browsing with simplified callbacks.
+ */
+public class MediaBrowserHelper {
+
+    private static final String TAG = MediaBrowserHelper.class.getSimpleName();
+
+    private final Context mContext;
+    private final Class<? extends MediaBrowserServiceCompat> mMediaBrowserServiceClass;
+
+    private final List<Callback> mCallbackList = new ArrayList<>();
+
+    private final MediaBrowserConnectionCallback mMediaBrowserConnectionCallback;
+    private final MediaControllerCallback mMediaControllerCallback;
+    private final MediaBrowserSubscriptionCallback mMediaBrowserSubscriptionCallback;
+
+    private MediaBrowserCompat mMediaBrowser;
+
+    @Nullable
+    private MediaControllerCompat mMediaController;
+
+    public MediaBrowserHelper(Context context,
+                              Class<? extends MediaBrowserServiceCompat> serviceClass) {
+        mContext = context;
+        mMediaBrowserServiceClass = serviceClass;
+
+        mMediaBrowserConnectionCallback = new MediaBrowserConnectionCallback();
+        mMediaControllerCallback = new MediaControllerCallback();
+        mMediaBrowserSubscriptionCallback = new MediaBrowserSubscriptionCallback();
+    }
+
+    public void onStart() {
+        if (mMediaBrowser == null) {
+            mMediaBrowser =
+                    new MediaBrowserCompat(
+                            mContext,
+                            new ComponentName(mContext, mMediaBrowserServiceClass),
+                            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");
+    }
+
+    /**
+     * Called after connecting with a {@link MediaBrowserServiceCompat}.
+     * <p>
+     * Override to perform processing after a connection is established.
+     *
+     * @param mediaController {@link MediaControllerCompat} associated with the connected
+     *                        MediaSession.
+     */
+    protected void onConnected(@NonNull MediaControllerCompat mediaController) {
+    }
+
+    /**
+     * Called after loading a browsable {@link MediaBrowserCompat.MediaItem}
+     *
+     * @param parentId The media ID of the parent item.
+     * @param children List (possibly empty) of child items.
+     */
+    protected void onChildrenLoaded(@NonNull String parentId,
+                                    @NonNull List<MediaBrowserCompat.MediaItem> children) {
+    }
+
+    /**
+     * Called when the {@link MediaBrowserServiceCompat} connection is lost.
+     */
+    protected void onDisconnected() {
+    }
+
+    @NonNull
+    protected final MediaControllerCompat getMediaController() {
+        if (mMediaController == null) {
+            throw new IllegalStateException("MediaController is null!");
+        }
+        return mMediaController;
+    }
+
+    /**
+     * The internal state of the app needs to revert to what it looks like when it started before
+     * any connections to the {@link AudiobookService} happens via the {@link MediaSessionCompat}.
+     */
+    private void resetState() {
+        performOnAllCallbacks(callback -> callback.onPlaybackStateChanged(null));
+        Log.d(TAG, "resetState: ");
+    }
+
+    public MediaControllerCompat.TransportControls getTransportControls() {
+        if (mMediaController == null) {
+            Log.d(TAG, "getTransportControls: MediaController is null!");
+            throw new IllegalStateException("MediaController is null!");
+        }
+        return mMediaController.getTransportControls();
+    }
+
+    public void registerCallback(Callback callback) {
+        if (callback != null) {
+            mCallbackList.add(callback);
+
+            // Update with the latest metadata/playback state.
+            if (mMediaController != null) {
+                final MediaMetadataCompat metadata = mMediaController.getMetadata();
+                if (metadata != null) {
+                    callback.onMetadataChanged(metadata);
+                }
+
+                final PlaybackStateCompat playbackState = mMediaController.getPlaybackState();
+                if (playbackState != null) {
+                    callback.onPlaybackStateChanged(playbackState);
+                }
+            }
+        }
+    }
+
+    private void performOnAllCallbacks(@NonNull CallbackCommand command) {
+        for (Callback callback : mCallbackList) {
+            if (callback != null) {
+                command.perform(callback);
+            }
+        }
+    }
+
+    /**
+     * Helper for more easily performing operations on all listening clients.
+     */
+    private interface CallbackCommand {
+        void perform(@NonNull Callback callback);
+    }
+
+    // Receives callbacks from the MediaBrowser when it has successfully connected to the
+    // MediaBrowserService (AudiobookService).
+    private 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());
+
+                MediaBrowserHelper.this.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) {
+            MediaBrowserHelper.this.onChildrenLoaded(parentId, children);
+        }
+    }
+
+    // Receives callbacks from the MediaController and updates the UI state,
+    // i.e.: Which is the current item, whether it's playing or paused, etc.
+    private class MediaControllerCallback extends MediaControllerCompat.Callback {
+
+        @Override
+        public void onMetadataChanged(final MediaMetadataCompat metadata) {
+            performOnAllCallbacks(callback -> callback.onMetadataChanged(metadata));
+        }
+
+        @Override
+        public void onPlaybackStateChanged(@Nullable final PlaybackStateCompat state) {
+            performOnAllCallbacks(callback -> callback.onPlaybackStateChanged(state));
+        }
+
+           @Override
+           public void onExtrasChanged(Bundle extras) {
+               performOnAllCallbacks(callback -> callback.onExtrasChanged(extras));
+           }
+
+           // This might happen if the AudiobookService is killed while the Activity is in the
+        // foreground and onStart() has been called (but not onStop()).
+        @Override
+        public void onSessionDestroyed() {
+            resetState();
+            onPlaybackStateChanged(null);
+
+            MediaBrowserHelper.this.onDisconnected();
+        }
+    }
+}
\ No newline at end of file