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