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