1/*
2 * Copyright (C) 2013 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package androidx.mediarouter.media;
17
18import android.content.Context;
19import android.media.AudioManager;
20import android.os.Build;
21
22import androidx.annotation.RequiresApi;
23
24import java.lang.ref.WeakReference;
25
26/**
27 * Provides access to features of the remote control client.
28 *
29 * Hidden for now but we might want to make this available to applications
30 * in the future.
31 */
32abstract class RemoteControlClientCompat {
33    protected final Context mContext;
34    protected final Object mRcc;
35    protected VolumeCallback mVolumeCallback;
36
37    protected RemoteControlClientCompat(Context context, Object rcc) {
38        mContext = context;
39        mRcc = rcc;
40    }
41
42    public static RemoteControlClientCompat obtain(Context context, Object rcc) {
43        if (Build.VERSION.SDK_INT >= 16) {
44            return new JellybeanImpl(context, rcc);
45        }
46        return new LegacyImpl(context, rcc);
47    }
48
49    public Object getRemoteControlClient() {
50        return mRcc;
51    }
52
53    /**
54     * Sets the current playback information.
55     * Must be called at least once to attach to the remote control client.
56     *
57     * @param info The playback information.  Must not be null.
58     */
59    public void setPlaybackInfo(PlaybackInfo info) {
60    }
61
62    /**
63     * Sets a callback to receive volume change requests from the remote control client.
64     *
65     * @param callback The volume callback to use or null if none.
66     */
67    public void setVolumeCallback(VolumeCallback callback) {
68        mVolumeCallback = callback;
69    }
70
71    /**
72     * Specifies information about the playback.
73     */
74    public static final class PlaybackInfo {
75        public int volume;
76        public int volumeMax;
77        public int volumeHandling = MediaRouter.RouteInfo.PLAYBACK_VOLUME_FIXED;
78        public int playbackStream = AudioManager.STREAM_MUSIC;
79        public int playbackType = MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE;
80    }
81
82    /**
83     * Called when volume updates are requested by the remote control client.
84     */
85    public interface VolumeCallback {
86        /**
87         * Called when the volume should be increased or decreased.
88         *
89         * @param direction An integer indicating whether the volume is to be increased
90         * (positive value) or decreased (negative value).
91         * For bundled changes, the absolute value indicates the number of changes
92         * in the same direction, e.g. +3 corresponds to three "volume up" changes.
93         */
94        public void onVolumeUpdateRequest(int direction);
95
96        /**
97         * Called when the volume for the route should be set to the given value.
98         *
99         * @param volume An integer indicating the new volume value that should be used,
100         * always between 0 and the value set by {@link PlaybackInfo#volumeMax}.
101         */
102        public void onVolumeSetRequest(int volume);
103    }
104
105    /**
106     * Legacy implementation for platform versions prior to Jellybean.
107     * Does nothing.
108     */
109    static class LegacyImpl extends RemoteControlClientCompat {
110        public LegacyImpl(Context context, Object rcc) {
111            super(context, rcc);
112        }
113    }
114
115    /**
116     * Implementation for Jellybean.
117     *
118     * The basic idea of this implementation is to attach the RCC to a UserRouteInfo
119     * in order to hook up stream metadata and volume callbacks because there is no
120     * other API available to do so in this platform version.  The UserRouteInfo itself
121     * is not attached to the MediaRouter so it is transparent to the user.
122     */
123    @RequiresApi(16)
124    static class JellybeanImpl extends RemoteControlClientCompat {
125        private final Object mRouterObj;
126        private final Object mUserRouteCategoryObj;
127        private final Object mUserRouteObj;
128        private boolean mRegistered;
129
130        public JellybeanImpl(Context context, Object rcc) {
131            super(context, rcc);
132
133            mRouterObj = MediaRouterJellybean.getMediaRouter(context);
134            mUserRouteCategoryObj = MediaRouterJellybean.createRouteCategory(
135                    mRouterObj, "", false);
136            mUserRouteObj = MediaRouterJellybean.createUserRoute(
137                    mRouterObj, mUserRouteCategoryObj);
138        }
139
140        @Override
141        public void setPlaybackInfo(PlaybackInfo info) {
142            MediaRouterJellybean.UserRouteInfo.setVolume(
143                    mUserRouteObj, info.volume);
144            MediaRouterJellybean.UserRouteInfo.setVolumeMax(
145                    mUserRouteObj, info.volumeMax);
146            MediaRouterJellybean.UserRouteInfo.setVolumeHandling(
147                    mUserRouteObj, info.volumeHandling);
148            MediaRouterJellybean.UserRouteInfo.setPlaybackStream(
149                    mUserRouteObj, info.playbackStream);
150            MediaRouterJellybean.UserRouteInfo.setPlaybackType(
151                    mUserRouteObj, info.playbackType);
152
153            if (!mRegistered) {
154                mRegistered = true;
155                MediaRouterJellybean.UserRouteInfo.setVolumeCallback(mUserRouteObj,
156                        MediaRouterJellybean.createVolumeCallback(
157                                new VolumeCallbackWrapper(this)));
158                MediaRouterJellybean.UserRouteInfo.setRemoteControlClient(mUserRouteObj, mRcc);
159            }
160        }
161
162        private static final class VolumeCallbackWrapper
163                implements MediaRouterJellybean.VolumeCallback {
164            // Unfortunately, the framework never unregisters its volume observer from
165            // the audio service so the UserRouteInfo object may leak along with
166            // any callbacks that we attach to it.  Use a weak reference to prevent
167            // the volume callback from holding strong references to anything important.
168            private final WeakReference<JellybeanImpl> mImplWeak;
169
170            public VolumeCallbackWrapper(JellybeanImpl impl) {
171                mImplWeak = new WeakReference<JellybeanImpl>(impl);
172            }
173
174            @Override
175            public void onVolumeUpdateRequest(Object routeObj, int direction) {
176                JellybeanImpl impl = mImplWeak.get();
177                if (impl != null && impl.mVolumeCallback != null) {
178                    impl.mVolumeCallback.onVolumeUpdateRequest(direction);
179                }
180            }
181
182            @Override
183            public void onVolumeSetRequest(Object routeObj, int volume) {
184                JellybeanImpl impl = mImplWeak.get();
185                if (impl != null && impl.mVolumeCallback != null) {
186                    impl.mVolumeCallback.onVolumeSetRequest(volume);
187                }
188            }
189        }
190    }
191}
192