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