• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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.car.radio.service;
17 
18 import android.os.RemoteException;
19 import android.util.IndentingPrintWriter;
20 
21 import androidx.annotation.GuardedBy;
22 import androidx.annotation.NonNull;
23 import androidx.annotation.Nullable;
24 import androidx.lifecycle.LiveData;
25 
26 import com.android.car.broadcastradio.support.Program;
27 import com.android.car.radio.SkipMode;
28 import com.android.car.radio.util.Log;
29 
30 import java.util.List;
31 
32 /**
33  * Helper class used to keep track of which station should be toggled next (or prev), based on
34  * {@link SkipMode}.
35  */
36 final class SkipController {
37 
38     private static final String TAG = SkipController.class.getSimpleName();
39 
40     private final Object mLock = new Object();
41 
42     private final IRadioAppService.Stub mService;
43 
44     @GuardedBy("mLock")
45     private List<Program> mFavorites;
46 
47     @GuardedBy("mLock")
48     private int mCurrentIndex;
49 
50     @GuardedBy("mLock")
51     private SkipMode mSkipMode;
52 
SkipController(@onNull IRadioAppService.Stub service, @NonNull LiveData<List<Program>> favorites, @NonNull SkipMode initialMode)53     SkipController(@NonNull IRadioAppService.Stub service,
54             @NonNull LiveData<List<Program>> favorites, @NonNull SkipMode initialMode) {
55         mService = service;
56         mSkipMode = initialMode;
57 
58         Log.v(TAG, "Initial mode: %s", initialMode);
59 
60         // TODO(b/137647889): not really working because they're changed in a different process.
61         // As such, the changes are only effective after the radio service restarts - that's
62         // not ideal, but it's better than nothing :-)
63         // Long term, we need to provide a way to sync them...
64         favorites.observeForever(this::onFavoritesChanged);
65     }
66 
setSkipMode(@onNull SkipMode mode)67     void setSkipMode(@NonNull SkipMode mode) {
68         Log.d(TAG, "setSkipMode(%s)", mode);
69         synchronized (mLock) {
70             this.mSkipMode = mode;
71         }
72     }
73 
skip(boolean forward, ITuneCallback callback)74     void skip(boolean forward, ITuneCallback callback) throws RemoteException {
75         Log.d(TAG, "skip(%s, %s)", mSkipMode, forward);
76 
77         Program program = null;
78         synchronized (mLock) {
79             if (mSkipMode == SkipMode.FAVORITES || mSkipMode == SkipMode.BROWSE) {
80                 program = getFavoriteLocked(forward);
81                 if (program == null) {
82                     Log.d(TAG, "skip(%s): no favorites, seeking instead", forward);
83                 }
84             }
85         }
86 
87         if (program != null) {
88             Log.d(TAG, "skip(%s): changing to %s", forward, program.getName());
89             mService.tune(program.getSelector(), callback);
90         } else {
91             mService.seek(forward, callback);
92         }
93     }
94 
onFavoritesChanged(List<Program> favorites)95     private void onFavoritesChanged(List<Program> favorites) {
96         Log.v(TAG, "onFavoritesChanged(): %s", favorites);
97         synchronized (this) {
98             mFavorites = favorites;
99             // TODO(b/137647889): try to preserve currentIndex, either pointing to the same station,
100             // or the closest one
101             mCurrentIndex = 0;
102         }
103     }
104 
105     @Nullable
getFavoriteLocked(boolean next)106     private Program getFavoriteLocked(boolean next) {
107         if (mFavorites == null || mFavorites.isEmpty()) return null;
108 
109         // TODO(b/137647889): to keep it simple, we're only interacting through explicit calls
110         // to prev/next, but ideally it should also take in account the current station.
111         // For example, say the favorites are 4, 8, 15, 16, 23, 42 and user skipped from
112         // 15 to 16 but later manually tuned to 5. In this case, if the user skips again we'll
113         // return 23 (next index), but ideally it would be 8 (i.e, next favorite whose value
114         // is higher than 5)
115         if (next) {
116             mCurrentIndex++;
117             if (mCurrentIndex >= mFavorites.size()) {
118                 mCurrentIndex = 0;
119             }
120         } else {
121             mCurrentIndex--;
122             if (mCurrentIndex < 0) {
123                 mCurrentIndex = mFavorites.size() - 1;
124             }
125         }
126         Program program = mFavorites.get(mCurrentIndex);
127         Log.v(TAG, "getting favorite #" + mCurrentIndex + ": " + program.getName());
128         return program;
129     }
130 
dump(IndentingPrintWriter pw)131     void dump(IndentingPrintWriter pw) {
132         pw.println("SkipController");
133         pw.increaseIndent();
134         synchronized (mLock) {
135             pw.printf("mode: %s\n", mSkipMode);
136             pw.printf("current index: %d\n", mCurrentIndex);
137             if (mFavorites == null || mFavorites.isEmpty()) {
138                 pw.println("no favorites");
139             } else {
140                 pw.printf("%d favorites:\n", mFavorites.size());
141                 pw.increaseIndent();
142                 for (int i = 0; i < mFavorites.size(); i++) {
143                     pw.printf("Favorite[%d]: %s ", i, mFavorites.get(i).getName());
144                     if (i == mCurrentIndex) {
145                         pw.printf(" is current");
146                     }
147                     pw.println();
148                 }
149                 pw.decreaseIndent();
150             }
151         }
152         pw.decreaseIndent();
153     }
154 }
155