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