Home | History | Annotate | Download | only in media
      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  */
     16 package androidx.mediarouter.media;
     17 
     18 import android.content.Context;
     19 import android.media.AudioManager;
     20 import android.os.Build;
     21 
     22 import androidx.annotation.RequiresApi;
     23 
     24 import 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  */
     32 abstract 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