Home | History | Annotate | Download | only in newavrcp
      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 
     17 package com.android.bluetooth.avrcp;
     18 
     19 import android.bluetooth.BluetoothA2dp;
     20 import android.bluetooth.BluetoothDevice;
     21 import android.bluetooth.IBluetoothAvrcpTarget;
     22 import android.content.BroadcastReceiver;
     23 import android.content.Context;
     24 import android.content.Intent;
     25 import android.content.IntentFilter;
     26 import android.media.AudioManager;
     27 import android.os.Looper;
     28 import android.os.SystemProperties;
     29 import android.os.UserManager;
     30 import android.util.Log;
     31 
     32 import com.android.bluetooth.BluetoothMetricsProto;
     33 import com.android.bluetooth.Utils;
     34 import com.android.bluetooth.a2dp.A2dpService;
     35 import com.android.bluetooth.btservice.MetricsLogger;
     36 import com.android.bluetooth.btservice.ProfileService;
     37 
     38 import java.util.List;
     39 import java.util.Objects;
     40 
     41 /**
     42  * Provides Bluetooth AVRCP Target profile as a service in the Bluetooth application.
     43  * @hide
     44  */
     45 public class AvrcpTargetService extends ProfileService {
     46     private static final String TAG = "NewAvrcpTargetService";
     47     private static final boolean DEBUG = true;
     48     private static final String AVRCP_ENABLE_PROPERTY = "persist.bluetooth.enablenewavrcp";
     49 
     50     private static final int AVRCP_MAX_VOL = 127;
     51     private static int sDeviceMaxVolume = 0;
     52 
     53     private MediaPlayerList mMediaPlayerList;
     54     private AudioManager mAudioManager;
     55     private AvrcpBroadcastReceiver mReceiver;
     56     private AvrcpNativeInterface mNativeInterface;
     57     private AvrcpVolumeManager mVolumeManager;
     58 
     59     // Only used to see if the metadata has changed from its previous value
     60     private MediaData mCurrentData;
     61 
     62     private static AvrcpTargetService sInstance = null;
     63 
     64     class ListCallback implements MediaPlayerList.MediaUpdateCallback,
     65             MediaPlayerList.FolderUpdateCallback {
     66         @Override
     67         public void run(MediaData data) {
     68             boolean metadata = !Objects.equals(mCurrentData.metadata, data.metadata);
     69             boolean state = !MediaPlayerWrapper.playstateEquals(mCurrentData.state, data.state);
     70             boolean queue = !Objects.equals(mCurrentData.queue, data.queue);
     71 
     72             if (DEBUG) {
     73                 Log.d(TAG, "onMediaUpdated: track_changed=" + metadata
     74                         + " state=" + state + " queue=" + queue);
     75             }
     76             mCurrentData = data;
     77 
     78             mNativeInterface.sendMediaUpdate(metadata, state, queue);
     79         }
     80 
     81         @Override
     82         public void run(boolean availablePlayers, boolean addressedPlayers,
     83                 boolean uids) {
     84             mNativeInterface.sendFolderUpdate(availablePlayers, addressedPlayers, uids);
     85         }
     86     }
     87 
     88     private class AvrcpBroadcastReceiver extends BroadcastReceiver {
     89         @Override
     90         public void onReceive(Context context, Intent intent) {
     91             String action = intent.getAction();
     92             if (action.equals(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED)) {
     93                 if (mNativeInterface == null) return;
     94 
     95                 // Update all the playback status info for each connected device
     96                 mNativeInterface.sendMediaUpdate(false, true, false);
     97             }
     98         }
     99     }
    100 
    101     /**
    102      * Get the AvrcpTargetService instance. Returns null if the service hasn't been initialized.
    103      */
    104     public static AvrcpTargetService get() {
    105         return sInstance;
    106     }
    107 
    108     @Override
    109     public String getName() {
    110         return TAG;
    111     }
    112 
    113     @Override
    114     protected IProfileServiceBinder initBinder() {
    115         return new AvrcpTargetBinder(this);
    116     }
    117 
    118     @Override
    119     protected void setUserUnlocked(int userId) {
    120         Log.i(TAG, "User unlocked, initializing the service");
    121 
    122         if (!SystemProperties.getBoolean(AVRCP_ENABLE_PROPERTY, true)) {
    123             Log.w(TAG, "Skipping initialization of the new AVRCP Target Player List");
    124             sInstance = null;
    125             return;
    126         }
    127 
    128         if (mMediaPlayerList != null) {
    129             mMediaPlayerList.init(new ListCallback());
    130         }
    131     }
    132 
    133     @Override
    134     protected boolean start() {
    135         if (sInstance != null) {
    136             Log.wtfStack(TAG, "The service has already been initialized");
    137             return false;
    138         }
    139 
    140         Log.i(TAG, "Starting the AVRCP Target Service");
    141         mCurrentData = new MediaData(null, null, null);
    142 
    143         mReceiver = new AvrcpBroadcastReceiver();
    144         IntentFilter filter = new IntentFilter();
    145         filter.addAction(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED);
    146         registerReceiver(mReceiver, filter);
    147 
    148         if (!SystemProperties.getBoolean(AVRCP_ENABLE_PROPERTY, true)) {
    149             Log.w(TAG, "Skipping initialization of the new AVRCP Target Service");
    150             sInstance = null;
    151             return true;
    152         }
    153 
    154         mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
    155         sDeviceMaxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
    156 
    157         mMediaPlayerList = new MediaPlayerList(Looper.myLooper(), this);
    158 
    159         UserManager userManager = UserManager.get(getApplicationContext());
    160         if (userManager.isUserUnlocked()) {
    161             mMediaPlayerList.init(new ListCallback());
    162         }
    163 
    164         mNativeInterface = AvrcpNativeInterface.getInterface();
    165         mNativeInterface.init(AvrcpTargetService.this);
    166 
    167         mVolumeManager = new AvrcpVolumeManager(this, mAudioManager, mNativeInterface);
    168 
    169         // Only allow the service to be used once it is initialized
    170         sInstance = this;
    171 
    172         return true;
    173     }
    174 
    175     @Override
    176     protected boolean stop() {
    177         Log.i(TAG, "Stopping the AVRCP Target Service");
    178 
    179         if (sInstance == null) {
    180             Log.w(TAG, "stop() called before start()");
    181             return true;
    182         }
    183 
    184         sInstance = null;
    185         unregisterReceiver(mReceiver);
    186 
    187         // We check the interfaces first since they only get set on User Unlocked
    188         if (mMediaPlayerList != null) mMediaPlayerList.cleanup();
    189         if (mNativeInterface != null) mNativeInterface.cleanup();
    190 
    191         mMediaPlayerList = null;
    192         mNativeInterface = null;
    193         mAudioManager = null;
    194         mReceiver = null;
    195         return true;
    196     }
    197 
    198     private void init() {
    199     }
    200 
    201     void deviceConnected(BluetoothDevice device, boolean absoluteVolume) {
    202         Log.i(TAG, "deviceConnected: device=" + device + " absoluteVolume=" + absoluteVolume);
    203         mVolumeManager.deviceConnected(device, absoluteVolume);
    204         MetricsLogger.logProfileConnectionEvent(BluetoothMetricsProto.ProfileId.AVRCP);
    205     }
    206 
    207     void deviceDisconnected(BluetoothDevice device) {
    208         Log.i(TAG, "deviceDisconnected: device=" + device);
    209         mVolumeManager.deviceDisconnected(device);
    210     }
    211 
    212     /**
    213      * Signal to the service that the current audio out device has changed and to inform
    214      * the audio service whether the new device supports absolute volume. If it does, also
    215      * set the absolute volume level on the remote device.
    216      */
    217     public void volumeDeviceSwitched(BluetoothDevice device) {
    218         if (DEBUG) {
    219             Log.d(TAG, "volumeDeviceSwitched: device=" + device);
    220         }
    221         mVolumeManager.volumeDeviceSwitched(device);
    222     }
    223 
    224     /**
    225      * Store the current system volume for a device in order to be retrieved later.
    226      */
    227     public void storeVolumeForDevice(BluetoothDevice device) {
    228         if (device == null) return;
    229 
    230         mVolumeManager.storeVolumeForDevice(device);
    231     }
    232 
    233     /**
    234      * Retrieve the remembered volume for a device. Returns -1 if there is no volume for the
    235      * device.
    236      */
    237     public int getRememberedVolumeForDevice(BluetoothDevice device) {
    238         if (device == null) return -1;
    239 
    240         return mVolumeManager.getVolume(device, -1);
    241     }
    242 
    243     // TODO (apanicke): Add checks to blacklist Absolute Volume devices if they behave poorly.
    244     void setVolume(int avrcpVolume) {
    245         int deviceVolume =
    246                 (int) Math.floor((double) avrcpVolume * sDeviceMaxVolume / AVRCP_MAX_VOL);
    247         if (DEBUG) {
    248             Log.d(TAG, "SendVolumeChanged: avrcpVolume=" + avrcpVolume
    249                     + " deviceVolume=" + deviceVolume
    250                     + " sDeviceMaxVolume=" + sDeviceMaxVolume);
    251         }
    252         mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, deviceVolume,
    253                 AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_BLUETOOTH_ABS_VOLUME);
    254     }
    255 
    256     /**
    257      * Set the volume on the remote device. Does nothing if the device doesn't support absolute
    258      * volume.
    259      */
    260     public void sendVolumeChanged(int deviceVolume) {
    261         int avrcpVolume =
    262                 (int) Math.floor((double) deviceVolume * AVRCP_MAX_VOL / sDeviceMaxVolume);
    263         if (avrcpVolume > 127) avrcpVolume = 127;
    264         if (DEBUG) {
    265             Log.d(TAG, "SendVolumeChanged: avrcpVolume=" + avrcpVolume
    266                     + " deviceVolume=" + deviceVolume
    267                     + " sDeviceMaxVolume=" + sDeviceMaxVolume);
    268         }
    269         mNativeInterface.sendVolumeChanged(avrcpVolume);
    270     }
    271 
    272     Metadata getCurrentSongInfo() {
    273         return mMediaPlayerList.getCurrentSongInfo();
    274     }
    275 
    276     PlayStatus getPlayState() {
    277         return PlayStatus.fromPlaybackState(mMediaPlayerList.getCurrentPlayStatus(),
    278                 Long.parseLong(getCurrentSongInfo().duration));
    279     }
    280 
    281     String getCurrentMediaId() {
    282         String id = mMediaPlayerList.getCurrentMediaId();
    283         if (id != null) return id;
    284 
    285         Metadata song = getCurrentSongInfo();
    286         if (song != null) return song.mediaId;
    287 
    288         // We always want to return something, the error string just makes debugging easier
    289         return "error";
    290     }
    291 
    292     List<Metadata> getNowPlayingList() {
    293         return mMediaPlayerList.getNowPlayingList();
    294     }
    295 
    296     int getCurrentPlayerId() {
    297         return mMediaPlayerList.getCurrentPlayerId();
    298     }
    299 
    300     // TODO (apanicke): Have the Player List also contain info about the play state of each player
    301     List<PlayerInfo> getMediaPlayerList() {
    302         return mMediaPlayerList.getMediaPlayerList();
    303     }
    304 
    305     void getPlayerRoot(int playerId, MediaPlayerList.GetPlayerRootCallback cb) {
    306         mMediaPlayerList.getPlayerRoot(playerId, cb);
    307     }
    308 
    309     void getFolderItems(int playerId, String mediaId, MediaPlayerList.GetFolderItemsCallback cb) {
    310         mMediaPlayerList.getFolderItems(playerId, mediaId, cb);
    311     }
    312 
    313     void playItem(int playerId, boolean nowPlaying, String mediaId) {
    314         // NOTE: playerId isn't used if nowPlaying is true, since its assumed to be the current
    315         // active player
    316         mMediaPlayerList.playItem(playerId, nowPlaying, mediaId);
    317     }
    318 
    319     // TODO (apanicke): Handle key events here in the service. Currently it was more convenient to
    320     // handle them there but logically they make more sense handled here.
    321     void sendMediaKeyEvent(int event, boolean pushed) {
    322         if (DEBUG) Log.d(TAG, "getMediaKeyEvent: event=" + event + " pushed=" + pushed);
    323         mMediaPlayerList.sendMediaKeyEvent(event, pushed);
    324     }
    325 
    326     void setActiveDevice(BluetoothDevice device) {
    327         Log.i(TAG, "setActiveDevice: device=" + device);
    328         if (device == null) {
    329             Log.wtfStack(TAG, "setActiveDevice: could not find device " + device);
    330         }
    331         A2dpService.getA2dpService().setActiveDevice(device);
    332     }
    333 
    334     /**
    335      * Dump debugging information to the string builder
    336      */
    337     public void dump(StringBuilder sb) {
    338         sb.append("\nProfile: AvrcpTargetService:\n");
    339         if (sInstance == null) {
    340             sb.append("AvrcpTargetService not running");
    341             return;
    342         }
    343 
    344         StringBuilder tempBuilder = new StringBuilder();
    345         if (mMediaPlayerList != null) {
    346             mMediaPlayerList.dump(tempBuilder);
    347         } else {
    348             tempBuilder.append("\nMedia Player List is empty\n");
    349         }
    350 
    351         mVolumeManager.dump(tempBuilder);
    352 
    353         // Tab everything over by two spaces
    354         sb.append(tempBuilder.toString().replaceAll("(?m)^", "  "));
    355     }
    356 
    357     private static class AvrcpTargetBinder extends IBluetoothAvrcpTarget.Stub
    358             implements IProfileServiceBinder {
    359         private AvrcpTargetService mService;
    360 
    361         AvrcpTargetBinder(AvrcpTargetService service) {
    362             mService = service;
    363         }
    364 
    365         @Override
    366         public void cleanup() {
    367             mService = null;
    368         }
    369 
    370         @Override
    371         public void sendVolumeChanged(int volume) {
    372             if (!Utils.checkCaller()) {
    373                 Log.w(TAG, "sendVolumeChanged not allowed for non-active user");
    374                 return;
    375             }
    376 
    377             if (mService == null) {
    378                 return;
    379             }
    380 
    381             mService.sendVolumeChanged(volume);
    382         }
    383     }
    384 }
    385