Home | History | Annotate | Download | only in media
      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