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