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