• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * Copyright (C) 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.car.radio.media;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.content.Context;
22 import android.hardware.radio.ProgramSelector;
23 import android.hardware.radio.RadioManager.ProgramInfo;
24 import android.net.Uri;
25 import android.os.Bundle;
26 import android.os.IBinder;
27 import android.os.RemoteException;
28 import android.support.v4.media.MediaMetadataCompat;
29 import android.support.v4.media.RatingCompat;
30 import android.support.v4.media.session.MediaSessionCompat;
31 import android.support.v4.media.session.PlaybackStateCompat;
32 import android.util.Log;
33 
34 import com.android.car.broadcastradio.support.Program;
35 import com.android.car.broadcastradio.support.media.BrowseTree;
36 import com.android.car.broadcastradio.support.platform.ImageResolver;
37 import com.android.car.broadcastradio.support.platform.ProgramInfoExt;
38 import com.android.car.broadcastradio.support.platform.ProgramSelectorExt;
39 import com.android.car.radio.R;
40 import com.android.car.radio.audio.IPlaybackStateListener;
41 import com.android.car.radio.service.IRadioManager;
42 import com.android.car.radio.utils.ThrowingRunnable;
43 
44 import java.util.Objects;
45 
46 /**
47  * Implementation of tuner's MediaSession.
48  */
49 public class TunerSession extends MediaSessionCompat implements IPlaybackStateListener {
50     private static final String TAG = "BcRadioApp.msess";
51 
52     private final Object mLock = new Object();
53 
54     private final Context mContext;
55     private final BrowseTree mBrowseTree;
56     @Nullable private final ImageResolver mImageResolver;
57     private final IRadioManager mUiSession;
58     private final PlaybackStateCompat.Builder mPlaybackStateBuilder =
59             new PlaybackStateCompat.Builder();
60     @Nullable private ProgramInfo mCurrentProgram;
61 
TunerSession(@onNull Context context, @NonNull BrowseTree browseTree, @NonNull IRadioManager uiSession, @Nullable ImageResolver imageResolver)62     public TunerSession(@NonNull Context context, @NonNull BrowseTree browseTree,
63             @NonNull IRadioManager uiSession, @Nullable ImageResolver imageResolver) {
64         super(context, TAG);
65 
66         mContext = Objects.requireNonNull(context);
67         mBrowseTree = Objects.requireNonNull(browseTree);
68         mImageResolver = imageResolver;
69         mUiSession = Objects.requireNonNull(uiSession);
70 
71         // ACTION_PAUSE is reserved for time-shifted playback
72         mPlaybackStateBuilder.setActions(
73                 PlaybackStateCompat.ACTION_STOP
74                 | PlaybackStateCompat.ACTION_PLAY
75                 | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS
76                 | PlaybackStateCompat.ACTION_SKIP_TO_NEXT
77                 | PlaybackStateCompat.ACTION_SET_RATING
78                 | PlaybackStateCompat.ACTION_PLAY_FROM_MEDIA_ID
79                 | PlaybackStateCompat.ACTION_PLAY_FROM_URI);
80         setRatingType(RatingCompat.RATING_HEART);
81         onPlaybackStateChanged(PlaybackStateCompat.STATE_NONE);
82         setCallback(new TunerSessionCallback());
83 
84         setActive(true);
85     }
86 
updateMetadata()87     private void updateMetadata() {
88         synchronized (mLock) {
89             if (mCurrentProgram == null) return;
90             boolean fav = mBrowseTree.isFavorite(mCurrentProgram.getSelector());
91             setMetadata(MediaMetadataCompat.fromMediaMetadata(
92                     ProgramInfoExt.toMediaMetadata(mCurrentProgram, fav, mImageResolver)));
93         }
94     }
95 
notifyProgramInfoChanged(@onNull ProgramInfo info)96     public void notifyProgramInfoChanged(@NonNull ProgramInfo info) {
97         synchronized (mLock) {
98             mCurrentProgram = info;
99             updateMetadata();
100         }
101     }
102 
103     @Override
onPlaybackStateChanged(@laybackStateCompat.State int state)104     public void onPlaybackStateChanged(@PlaybackStateCompat.State int state) {
105         synchronized (mPlaybackStateBuilder) {
106             mPlaybackStateBuilder.setState(state,
107                     PlaybackStateCompat.PLAYBACK_POSITION_UNKNOWN, 1.0f);
108             setPlaybackState(mPlaybackStateBuilder.build());
109         }
110     }
111 
notifyFavoritesChanged()112     public void notifyFavoritesChanged() {
113         updateMetadata();
114     }
115 
selectionError()116     private void selectionError() {
117         exec(() -> mUiSession.mute());
118         mPlaybackStateBuilder.setErrorMessage(mContext.getString(R.string.invalid_selection));
119         onPlaybackStateChanged(PlaybackStateCompat.STATE_ERROR);
120         mPlaybackStateBuilder.setErrorMessage(null);
121     }
122 
exec(ThrowingRunnable<RemoteException> func)123     private void exec(ThrowingRunnable<RemoteException> func) {
124         try {
125             func.run();
126         } catch (RemoteException ex) {
127             Log.e(TAG, "Failed to execute MediaSession callback", ex);
128         }
129     }
130 
131     private class TunerSessionCallback extends MediaSessionCompat.Callback {
132         @Override
onStop()133         public void onStop() {
134             exec(() -> mUiSession.mute());
135         }
136 
137         @Override
onPlay()138         public void onPlay() {
139             exec(() -> mUiSession.unMute());
140         }
141 
142         @Override
onSkipToNext()143         public void onSkipToNext() {
144             exec(() -> mUiSession.seekForward());
145         }
146 
147         @Override
onSkipToPrevious()148         public void onSkipToPrevious() {
149             exec(() -> mUiSession.seekBackward());
150         }
151 
152         @Override
onSetRating(RatingCompat rating)153         public void onSetRating(RatingCompat rating) {
154             synchronized (mLock) {
155                 if (mCurrentProgram == null) return;
156                 if (rating.hasHeart()) {
157                     Program fav = Program.fromProgramInfo(mCurrentProgram);
158                     exec(() -> mUiSession.addFavorite(fav));
159                 } else {
160                     ProgramSelector fav = mCurrentProgram.getSelector();
161                     exec(() -> mUiSession.removeFavorite(fav));
162                 }
163             }
164         }
165 
166         @Override
onPlayFromMediaId(String mediaId, Bundle extras)167         public void onPlayFromMediaId(String mediaId, Bundle extras) {
168             if (mBrowseTree.getRoot().getRootId().equals(mediaId)) {
169                 // general play command
170                 onPlay();
171                 return;
172             }
173 
174             ProgramSelector selector = mBrowseTree.parseMediaId(mediaId);
175             if (selector != null) {
176                 exec(() -> mUiSession.tune(selector));
177             } else {
178                 Log.w(TAG, "Invalid media ID: " + mediaId);
179                 selectionError();
180             }
181         }
182 
183         @Override
onPlayFromUri(Uri uri, Bundle extras)184         public void onPlayFromUri(Uri uri, Bundle extras) {
185             ProgramSelector selector = ProgramSelectorExt.fromUri(uri);
186             if (selector != null) {
187                 exec(() -> mUiSession.tune(selector));
188             } else {
189                 Log.w(TAG, "Invalid URI: " + uri);
190                 selectionError();
191             }
192         }
193     }
194 
195     @Override
asBinder()196     public IBinder asBinder() {
197         throw new UnsupportedOperationException("Not a binder");
198     }
199 }
200