1/**
2 * Copyright (C) 2017 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
17package com.android.server.broadcastradio.hal1;
18
19import android.annotation.NonNull;
20import android.annotation.Nullable;
21import android.hardware.radio.ITuner;
22import android.hardware.radio.ITunerCallback;
23import android.hardware.radio.ProgramList;
24import android.hardware.radio.ProgramSelector;
25import android.hardware.radio.RadioManager;
26import android.hardware.radio.RadioMetadata;
27import android.hardware.radio.RadioTuner;
28import android.os.IBinder;
29import android.os.RemoteException;
30import android.util.Slog;
31
32import java.util.List;
33import java.util.Map;
34import java.util.Objects;
35import java.util.Set;
36import java.util.concurrent.atomic.AtomicReference;
37import java.util.stream.Collectors;
38
39class TunerCallback implements ITunerCallback {
40    private static final String TAG = "BroadcastRadioService.TunerCallback";
41
42    /**
43     * This field is used by native code, do not access or modify.
44     */
45    private final long mNativeContext;
46
47    @NonNull private final Tuner mTuner;
48    @NonNull private final ITunerCallback mClientCallback;
49
50    private final AtomicReference<ProgramList.Filter> mProgramListFilter = new AtomicReference<>();
51    private boolean mInitialConfigurationDone = false;
52
53    TunerCallback(@NonNull Tuner tuner, @NonNull ITunerCallback clientCallback, int halRev) {
54        mTuner = tuner;
55        mClientCallback = clientCallback;
56        mNativeContext = nativeInit(tuner, halRev);
57    }
58
59    @Override
60    protected void finalize() throws Throwable {
61        nativeFinalize(mNativeContext);
62        super.finalize();
63    }
64
65    private native long nativeInit(@NonNull Tuner tuner, int halRev);
66    private native void nativeFinalize(long nativeContext);
67    private native void nativeDetach(long nativeContext);
68
69    public void detach() {
70        nativeDetach(mNativeContext);
71    }
72
73    private interface RunnableThrowingRemoteException {
74        void run() throws RemoteException;
75    }
76
77    private void dispatch(RunnableThrowingRemoteException func) {
78        try {
79            func.run();
80        } catch (RemoteException e) {
81            Slog.e(TAG, "client died", e);
82        }
83    }
84
85    // called from native side
86    private void handleHwFailure() {
87        onError(RadioTuner.ERROR_HARDWARE_FAILURE);
88        mTuner.close();
89    }
90
91    void startProgramListUpdates(@Nullable ProgramList.Filter filter) {
92        if (filter == null) filter = new ProgramList.Filter();
93        mProgramListFilter.set(filter);
94        sendProgramListUpdate();
95    }
96
97    void stopProgramListUpdates() {
98        mProgramListFilter.set(null);
99    }
100
101    boolean isInitialConfigurationDone() {
102        return mInitialConfigurationDone;
103    }
104
105    @Override
106    public void onError(int status) {
107        dispatch(() -> mClientCallback.onError(status));
108    }
109
110    @Override
111    public void onTuneFailed(int result, ProgramSelector selector) {
112        Slog.e(TAG, "Not applicable for HAL 1.x");
113    }
114
115    @Override
116    public void onConfigurationChanged(RadioManager.BandConfig config) {
117        mInitialConfigurationDone = true;
118        dispatch(() -> mClientCallback.onConfigurationChanged(config));
119    }
120
121    @Override
122    public void onCurrentProgramInfoChanged(RadioManager.ProgramInfo info) {
123        dispatch(() -> mClientCallback.onCurrentProgramInfoChanged(info));
124    }
125
126    @Override
127    public void onTrafficAnnouncement(boolean active) {
128        dispatch(() -> mClientCallback.onTrafficAnnouncement(active));
129    }
130
131    @Override
132    public void onEmergencyAnnouncement(boolean active) {
133        dispatch(() -> mClientCallback.onEmergencyAnnouncement(active));
134    }
135
136    @Override
137    public void onAntennaState(boolean connected) {
138        dispatch(() -> mClientCallback.onAntennaState(connected));
139    }
140
141    @Override
142    public void onBackgroundScanAvailabilityChange(boolean isAvailable) {
143        dispatch(() -> mClientCallback.onBackgroundScanAvailabilityChange(isAvailable));
144    }
145
146    @Override
147    public void onBackgroundScanComplete() {
148        dispatch(() -> mClientCallback.onBackgroundScanComplete());
149    }
150
151    @Override
152    public void onProgramListChanged() {
153        dispatch(() -> mClientCallback.onProgramListChanged());
154        sendProgramListUpdate();
155    }
156
157    private void sendProgramListUpdate() {
158        ProgramList.Filter filter = mProgramListFilter.get();
159        if (filter == null) return;
160
161        List<RadioManager.ProgramInfo> modified;
162        try {
163            modified = mTuner.getProgramList(filter.getVendorFilter());
164        } catch (IllegalStateException ex) {
165            Slog.d(TAG, "Program list not ready yet");
166            return;
167        }
168        Set<RadioManager.ProgramInfo> modifiedSet = modified.stream().collect(Collectors.toSet());
169        ProgramList.Chunk chunk = new ProgramList.Chunk(true, true, modifiedSet, null);
170        dispatch(() -> mClientCallback.onProgramListUpdated(chunk));
171    }
172
173    @Override
174    public void onProgramListUpdated(ProgramList.Chunk chunk) {
175        dispatch(() -> mClientCallback.onProgramListUpdated(chunk));
176    }
177
178    @Override
179    public void onParametersUpdated(Map parameters) {
180        Slog.e(TAG, "Not applicable for HAL 1.x");
181    }
182
183    @Override
184    public IBinder asBinder() {
185        throw new RuntimeException("Not a binder");
186    }
187}
188