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 android.car.hardware.radio;
18
19import android.annotation.SystemApi;
20import android.car.Car;
21import android.car.CarManagerBase;
22import android.car.CarNotConnectedException;
23import android.hardware.radio.RadioManager;
24import android.os.Handler;
25import android.os.IBinder;
26import android.os.Looper;
27import android.os.Message;
28import android.os.RemoteException;
29import android.util.Log;
30
31import com.android.internal.annotations.GuardedBy;
32
33import java.lang.ref.WeakReference;
34
35/**
36 * Car Radio manager.
37 *
38 * This API works in conjunction with the {@link RadioManager.java} and provides
39 * features additional to the ones provided in there. It supports:
40 *
41 * 1. Capability to control presets.
42 * @hide
43 */
44@SystemApi
45public final class CarRadioManager implements CarManagerBase {
46    private final static boolean DBG = false;
47    private final static String TAG = "CarRadioManager";
48
49    // Constants handled in the handler (see mHandler below).
50    private final static int MSG_RADIO_EVENT = 0;
51
52    private int mCount = 0;
53    private final ICarRadio mService;
54    @GuardedBy("this")
55    private CarRadioEventListener mListener = null;
56    @GuardedBy("this")
57    private CarRadioEventListenerToService mListenerToService = null;
58    private static final class EventCallbackHandler extends Handler {
59        WeakReference<CarRadioManager> mMgr;
60
61        EventCallbackHandler(CarRadioManager mgr, Looper looper) {
62            super(looper);
63            mMgr = new WeakReference<CarRadioManager>(mgr);
64        }
65
66        @Override
67        public void handleMessage(Message msg) {
68            switch (msg.what) {
69                case MSG_RADIO_EVENT:
70                    CarRadioManager mgr = mMgr.get();
71                    if (mgr != null) {
72                        mgr.dispatchEventToClient((CarRadioEvent) msg.obj);
73                    }
74                    break;
75                default:
76                    Log.e(TAG, "Event type not handled?" + msg);
77            }
78        }
79    }
80
81    private final Handler mHandler;
82
83    private static class CarRadioEventListenerToService extends ICarRadioEventListener.Stub {
84        private final WeakReference<CarRadioManager> mManager;
85
86        public CarRadioEventListenerToService(CarRadioManager manager) {
87            mManager = new WeakReference<CarRadioManager>(manager);
88        }
89
90        @Override
91        public void onEvent(CarRadioEvent event) {
92            CarRadioManager manager = mManager.get();
93            if (manager != null) {
94                manager.handleEvent(event);
95            }
96        }
97    }
98
99
100    /** Listener for car radio events.
101     */
102    public interface CarRadioEventListener {
103        /**
104         * Called when there is a preset value is reprogrammed.
105         */
106        void onEvent(final CarRadioEvent event);
107    }
108
109    /**
110     * Get an instance of the CarRadioManager.
111     *
112     * Should not be obtained directly by clients, use {@link Car#getCarManager(String)} instead.
113     * @hide
114     */
115    public CarRadioManager(IBinder service, Handler handler) throws CarNotConnectedException {
116        mService = ICarRadio.Stub.asInterface(service);
117        mHandler = new EventCallbackHandler(this, handler.getLooper());
118
119        // Populate the fixed values.
120        try {
121            mCount = mService.getPresetCount();
122        } catch (RemoteException ex) {
123            Log.e(TAG, "Could not connect: " + ex.toString());
124            throw new CarNotConnectedException(ex);
125        }
126    }
127
128    /**
129     * Register {@link CarRadioEventListener} to get radio unit changes.
130     */
131    public synchronized void registerListener(CarRadioEventListener listener)
132            throws CarNotConnectedException {
133        if (mListener != null) {
134            throw new IllegalStateException("Listener already registered. Did you call " +
135                "registerListener() twice?");
136        }
137
138        mListener = listener;
139        try {
140            mListenerToService = new CarRadioEventListenerToService(this);
141            mService.registerListener(mListenerToService);
142        } catch (RemoteException ex) {
143            // Do nothing.
144            Log.e(TAG, "Could not connect: " + ex.toString());
145            throw new CarNotConnectedException(ex);
146        } catch (IllegalStateException ex) {
147            Car.checkCarNotConnectedExceptionFromCarService(ex);
148        }
149    }
150
151    /**
152     * Unregister {@link CarRadioEventListener}.
153     */
154    public synchronized void unregisterListener() {
155        if (DBG) {
156            Log.d(TAG, "unregisterListener");
157        }
158        try {
159            mService.unregisterListener(mListenerToService);
160        } catch (RemoteException ex) {
161            Log.e(TAG, "Could not connect: " + ex.toString());
162            //ignore
163        }
164        mListenerToService = null;
165        mListener = null;
166    }
167
168    /**
169     * Get the number of (hard) presets supported by car radio unit.
170     *
171     * @return: A positive value if the call succeeded, -1 if it failed.
172     */
173    public int getPresetCount() throws CarNotConnectedException {
174        return mCount;
175    }
176
177    /**
178     * Get preset value for a specific radio preset.
179     * @return: a {@link CarRadioPreset} object, {@link null} if the call failed.
180     */
181    public CarRadioPreset getPreset(int presetNumber) throws CarNotConnectedException {
182        if (DBG) {
183            Log.d(TAG, "getPreset");
184        }
185        try {
186            CarRadioPreset preset = mService.getPreset(presetNumber);
187            return preset;
188        } catch (RemoteException ex) {
189            Log.e(TAG, "getPreset failed with " + ex.toString());
190            throw new CarNotConnectedException(ex);
191        }
192    }
193
194    /**
195     * Set the preset value to a specific radio preset.
196     *
197     * In order to ensure that the preset value indeed get updated, wait for event on the listener
198     * registered via registerListener().
199     *
200     * @return: {@link boolean} value which returns true if the request succeeded and false
201     * otherwise. Common reasons for the failure could be:
202     * a) Preset is invalid (the preset number is out of range from {@link getPresetCount()}.
203     * b) Listener is not set correctly, since otherwise the user of this API cannot confirm if the
204     * request succeeded.
205     */
206    public boolean setPreset(CarRadioPreset preset) throws IllegalArgumentException,
207            CarNotConnectedException {
208        try {
209            return mService.setPreset(preset);
210        } catch (RemoteException ex) {
211            throw new CarNotConnectedException(ex);
212         }
213    }
214
215    private void dispatchEventToClient(CarRadioEvent event) {
216        CarRadioEventListener listener;
217        synchronized (this) {
218            listener = mListener;
219        }
220        if (listener != null) {
221            listener.onEvent(event);
222        } else {
223            Log.e(TAG, "Listener died, not dispatching event.");
224        }
225    }
226
227    private void handleEvent(CarRadioEvent event) {
228        mHandler.sendMessage(mHandler.obtainMessage(MSG_RADIO_EVENT, event));
229    }
230
231    /** @hide */
232    @Override
233    public synchronized void onCarDisconnected() {
234        mListener = null;
235        mListenerToService = null;
236    }
237}
238