1692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim/*
2692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim * Copyright 2018 The Android Open Source Project
3692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim *
4692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim * Licensed under the Apache License, Version 2.0 (the "License");
5692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim * you may not use this file except in compliance with the License.
6692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim * You may obtain a copy of the License at
7692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim *
8692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim *      http://www.apache.org/licenses/LICENSE-2.0
9692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim *
10692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim * Unless required by applicable law or agreed to in writing, software
11692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim * distributed under the License is distributed on an "AS IS" BASIS,
12692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim * See the License for the specific language governing permissions and
14692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim * limitations under the License.
15692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim */
16692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Limpackage com.android.support.mediarouter.media;
17692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim
18692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Limimport android.content.Context;
19692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Limimport android.media.AudioManager;
20692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Limimport android.os.Build;
21692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Limimport android.support.annotation.RequiresApi;
22692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim
23692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Limimport java.lang.ref.WeakReference;
24692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim
25692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim/**
26692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim * Provides access to features of the remote control client.
27692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim *
28692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim * Hidden for now but we might want to make this available to applications
29692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim * in the future.
30692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim */
31692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Limabstract class RemoteControlClientCompat {
32692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim    protected final Context mContext;
33692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim    protected final Object mRcc;
34692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim    protected VolumeCallback mVolumeCallback;
35692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim
36692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim    protected RemoteControlClientCompat(Context context, Object rcc) {
37692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim        mContext = context;
38692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim        mRcc = rcc;
39692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim    }
40692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim
41692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim    public static RemoteControlClientCompat obtain(Context context, Object rcc) {
42692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim        if (Build.VERSION.SDK_INT >= 16) {
43692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim            return new JellybeanImpl(context, rcc);
44692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim        }
45692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim        return new LegacyImpl(context, rcc);
46692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim    }
47692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim
48692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim    public Object getRemoteControlClient() {
49692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim        return mRcc;
50692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim    }
51692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim
52692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim    /**
53692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim     * Sets the current playback information.
54692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim     * Must be called at least once to attach to the remote control client.
55692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim     *
56692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim     * @param info The playback information.  Must not be null.
57692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim     */
58692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim    public void setPlaybackInfo(PlaybackInfo info) {
59692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim    }
60692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim
61692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim    /**
62692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim     * Sets a callback to receive volume change requests from the remote control client.
63692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim     *
64692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim     * @param callback The volume callback to use or null if none.
65692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim     */
66692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim    public void setVolumeCallback(VolumeCallback callback) {
67692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim        mVolumeCallback = callback;
68692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim    }
69692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim
70692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim    /**
71692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim     * Specifies information about the playback.
72692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim     */
73692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim    public static final class PlaybackInfo {
74692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim        public int volume;
75692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim        public int volumeMax;
76692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim        public int volumeHandling = MediaRouter.RouteInfo.PLAYBACK_VOLUME_FIXED;
77692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim        public int playbackStream = AudioManager.STREAM_MUSIC;
78692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim        public int playbackType = MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE;
79692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim    }
80692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim
81692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim    /**
82692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim     * Called when volume updates are requested by the remote control client.
83692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim     */
84692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim    public interface VolumeCallback {
85692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim        /**
86692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim         * Called when the volume should be increased or decreased.
87692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim         *
88692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim         * @param direction An integer indicating whether the volume is to be increased
89692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim         * (positive value) or decreased (negative value).
90692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim         * For bundled changes, the absolute value indicates the number of changes
91692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim         * in the same direction, e.g. +3 corresponds to three "volume up" changes.
92692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim         */
93692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim        public void onVolumeUpdateRequest(int direction);
94692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim
95692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim        /**
96692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim         * Called when the volume for the route should be set to the given value.
97692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim         *
98692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim         * @param volume An integer indicating the new volume value that should be used,
99692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim         * always between 0 and the value set by {@link PlaybackInfo#volumeMax}.
100692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim         */
101692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim        public void onVolumeSetRequest(int volume);
102692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim    }
103692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim
104692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim    /**
105692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim     * Legacy implementation for platform versions prior to Jellybean.
106692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim     * Does nothing.
107692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim     */
108692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim    static class LegacyImpl extends RemoteControlClientCompat {
109692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim        public LegacyImpl(Context context, Object rcc) {
110692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim            super(context, rcc);
111692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim        }
112692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim    }
113692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim
114692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim    /**
115692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim     * Implementation for Jellybean.
116692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim     *
117692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim     * The basic idea of this implementation is to attach the RCC to a UserRouteInfo
118692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim     * in order to hook up stream metadata and volume callbacks because there is no
119692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim     * other API available to do so in this platform version.  The UserRouteInfo itself
120692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim     * is not attached to the MediaRouter so it is transparent to the user.
121692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim     */
122692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim    // @@RequiresApi(16)
123692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim    static class JellybeanImpl extends RemoteControlClientCompat {
124692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim        private final Object mRouterObj;
125692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim        private final Object mUserRouteCategoryObj;
126692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim        private final Object mUserRouteObj;
127692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim        private boolean mRegistered;
128692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim
129692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim        public JellybeanImpl(Context context, Object rcc) {
130692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim            super(context, rcc);
131692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim
132692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim            mRouterObj = MediaRouterJellybean.getMediaRouter(context);
133692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim            mUserRouteCategoryObj = MediaRouterJellybean.createRouteCategory(
134692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim                    mRouterObj, "", false);
135692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim            mUserRouteObj = MediaRouterJellybean.createUserRoute(
136692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim                    mRouterObj, mUserRouteCategoryObj);
137692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim        }
138692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim
139692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim        @Override
140692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim        public void setPlaybackInfo(PlaybackInfo info) {
141692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim            MediaRouterJellybean.UserRouteInfo.setVolume(
142692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim                    mUserRouteObj, info.volume);
143692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim            MediaRouterJellybean.UserRouteInfo.setVolumeMax(
144692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim                    mUserRouteObj, info.volumeMax);
145692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim            MediaRouterJellybean.UserRouteInfo.setVolumeHandling(
146692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim                    mUserRouteObj, info.volumeHandling);
147692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim            MediaRouterJellybean.UserRouteInfo.setPlaybackStream(
148692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim                    mUserRouteObj, info.playbackStream);
149692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim            MediaRouterJellybean.UserRouteInfo.setPlaybackType(
150692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim                    mUserRouteObj, info.playbackType);
151692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim
152692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim            if (!mRegistered) {
153692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim                mRegistered = true;
154692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim                MediaRouterJellybean.UserRouteInfo.setVolumeCallback(mUserRouteObj,
155692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim                        MediaRouterJellybean.createVolumeCallback(
156692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim                                new VolumeCallbackWrapper(this)));
157692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim                MediaRouterJellybean.UserRouteInfo.setRemoteControlClient(mUserRouteObj, mRcc);
158692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim            }
159692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim        }
160692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim
161692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim        private static final class VolumeCallbackWrapper
162692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim                implements MediaRouterJellybean.VolumeCallback {
163692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim            // Unfortunately, the framework never unregisters its volume observer from
164692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim            // the audio service so the UserRouteInfo object may leak along with
165692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim            // any callbacks that we attach to it.  Use a weak reference to prevent
166692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim            // the volume callback from holding strong references to anything important.
167692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim            private final WeakReference<JellybeanImpl> mImplWeak;
168692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim
169692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim            public VolumeCallbackWrapper(JellybeanImpl impl) {
170692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim                mImplWeak = new WeakReference<JellybeanImpl>(impl);
171692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim            }
172692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim
173692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim            @Override
174692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim            public void onVolumeUpdateRequest(Object routeObj, int direction) {
175692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim                JellybeanImpl impl = mImplWeak.get();
176692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim                if (impl != null && impl.mVolumeCallback != null) {
177692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim                    impl.mVolumeCallback.onVolumeUpdateRequest(direction);
178692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim                }
179692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim            }
180692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim
181692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim            @Override
182692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim            public void onVolumeSetRequest(Object routeObj, int volume) {
183692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim                JellybeanImpl impl = mImplWeak.get();
184692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim                if (impl != null && impl.mVolumeCallback != null) {
185692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim                    impl.mVolumeCallback.onVolumeSetRequest(volume);
186692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim                }
187692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim            }
188692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim        }
189692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim    }
190692a547730bbc95ad277d5214ef3d786ce1e499fSungsoo Lim}
191