1/*
2 * Copyright (C) 2015 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.car;
18
19import android.car.Car;
20import android.car.hardware.radio.CarRadioEvent;
21import android.car.hardware.radio.CarRadioPreset;
22import android.car.hardware.radio.ICarRadio;
23import android.car.hardware.radio.ICarRadioEventListener;
24import android.content.Context;
25import android.content.pm.PackageManager;
26import android.os.IBinder;
27import android.os.Process;
28import android.os.RemoteException;
29import android.util.Log;
30
31import com.android.car.hal.VehicleHal;
32import com.android.car.hal.RadioHalService;
33
34import java.io.PrintWriter;
35import java.util.HashMap;
36
37public class CarRadioService extends ICarRadio.Stub
38        implements CarServiceBase, RadioHalService.RadioListener {
39    public static boolean DBG = true;
40    public static String TAG = CarLog.TAG_RADIO + ".CarRadioService";
41
42    private RadioHalService mRadioHal;
43    private final HashMap<IBinder, ICarRadioEventListener> mListenersMap =
44      new HashMap<IBinder, ICarRadioEventListener>();
45    private final HashMap<IBinder, RadioDeathRecipient> mDeathRecipientMap =
46        new HashMap<IBinder, RadioDeathRecipient>();
47    private final Context mContext;
48
49    public CarRadioService(Context context) {
50        mRadioHal = VehicleHal.getInstance().getRadioHal();
51        mContext = context;
52    }
53
54    class RadioDeathRecipient implements IBinder.DeathRecipient {
55        private String TAG = CarRadioService.TAG + ".RadioDeathRecipient";
56        private IBinder mListenerBinder;
57
58        RadioDeathRecipient(IBinder listenerBinder) {
59            mListenerBinder = listenerBinder;
60        }
61
62        /**
63         * Client died. Remove the listener from HAL service and unregister if this is the last
64         * client.
65         */
66        @Override
67        public void binderDied() {
68            if (DBG) {
69                Log.d(TAG, "binderDied " + mListenerBinder);
70            }
71            mListenerBinder.unlinkToDeath(this, 0);
72            CarRadioService.this.unregisterListenerLocked(mListenerBinder);
73        }
74
75        void release() {
76            mListenerBinder.unlinkToDeath(this, 0);
77        }
78    }
79
80    @Override
81    public synchronized void init() {
82    }
83
84    @Override
85    public synchronized void release() {
86        for (IBinder listenerBinder : mListenersMap.keySet()) {
87            RadioDeathRecipient deathRecipient = mDeathRecipientMap.get(listenerBinder);
88            deathRecipient.release();
89        }
90        mDeathRecipientMap.clear();
91        mListenersMap.clear();
92    }
93
94    @Override
95    public void dump(PrintWriter writer) {
96    }
97
98    @Override
99    public int getPresetCount() {
100        return mRadioHal.getPresetCount();
101    }
102
103    @Override
104    public synchronized void registerListener(ICarRadioEventListener listener) {
105        if (DBG) {
106            Log.d(TAG, "registerListener");
107        }
108        if (listener == null) {
109            Log.e(TAG, "registerListener: Listener is null.");
110            throw new IllegalStateException("listener cannot be null.");
111        }
112
113        IBinder listenerBinder = listener.asBinder();
114        if (mListenersMap.containsKey(listenerBinder)) {
115            // Already registered, nothing to do.
116            return;
117        }
118
119        RadioDeathRecipient deathRecipient = new RadioDeathRecipient(listenerBinder);
120        try {
121            listenerBinder.linkToDeath(deathRecipient, 0);
122        } catch (RemoteException e) {
123            Log.e(TAG, "Failed to link death for recipient. " + e);
124            throw new IllegalStateException(Car.CAR_NOT_CONNECTED_EXCEPTION_MSG);
125        }
126        mDeathRecipientMap.put(listenerBinder, deathRecipient);
127
128        if (mListenersMap.isEmpty()) {
129            mRadioHal.registerListener(this);
130        }
131
132        mListenersMap.put(listenerBinder, listener);
133    }
134
135    @Override
136    public synchronized void unregisterListener(ICarRadioEventListener listener) {
137        if (DBG) {
138            Log.d(TAG, "unregisterListener");
139        }
140
141        if (listener == null) {
142            Log.e(TAG, "unregisterListener: Listener is null.");
143            throw new IllegalArgumentException("Listener is null");
144        }
145
146        IBinder listenerBinder = listener.asBinder();
147        if (!mListenersMap.containsKey(listenerBinder)) {
148            Log.e(TAG, "unregisterListener: Listener was not previously registered.");
149        }
150        unregisterListenerLocked(listenerBinder);
151    }
152
153    // Removes the listenerBinder from the current state.
154    // The function assumes that the binder will exist both in listeners and death recipients list.
155    private void unregisterListenerLocked(IBinder listenerBinder) {
156        Object status = mListenersMap.remove(listenerBinder);
157        if (status == null) throw new IllegalStateException(
158            "Map must contain the event listener.");
159
160        // If there is a state muck up, the release() call will throw an exception automagically.
161        mDeathRecipientMap.get(listenerBinder).release();
162        mDeathRecipientMap.remove(listenerBinder);
163
164        if (mListenersMap.isEmpty()) {
165            mRadioHal.unregisterListener();
166        }
167    }
168
169    @Override
170    public CarRadioPreset getPreset(int index) {
171        if (DBG) {
172            Log.d(TAG, "getPreset " + index);
173        }
174        return mRadioHal.getRadioPreset(index);
175    }
176
177    @Override
178    public boolean setPreset(CarRadioPreset preset) {
179        checkRadioPremissions();
180        if (DBG) {
181            Log.d(TAG, "setPreset " + preset);
182        }
183        boolean status = mRadioHal.setRadioPreset(preset);
184        if (status == false) {
185            Log.e(TAG, "setPreset failed!");
186        }
187        return status;
188    }
189
190    @Override
191    public synchronized void onEvent(CarRadioEvent event) {
192        for (ICarRadioEventListener l : mListenersMap.values()) {
193            try {
194                l.onEvent(event);
195            } catch (RemoteException ex) {
196                // If we could not send a record, its likely the connection snapped. Let the binder
197                // death handle the situation.
198                Log.e(TAG, "onEvent calling failed: " + ex);
199            }
200        }
201    }
202
203    private void checkRadioPremissions() {
204        if (getCallingUid() != Process.SYSTEM_UID &&
205            mContext.checkCallingOrSelfPermission(Car.PERMISSION_CAR_RADIO) !=
206            PackageManager.PERMISSION_GRANTED) {
207            throw new SecurityException("requires system app or " +
208                Car.PERMISSION_CAR_RADIO);
209        }
210    }
211}
212