1/*
2 * Copyright (C) 2013 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.incallui;
18
19import android.app.Service;
20import android.content.Intent;
21import android.os.Handler;
22import android.os.IBinder;
23import android.os.Message;
24
25import com.android.services.telephony.common.AudioMode;
26import com.android.services.telephony.common.Call;
27import com.android.services.telephony.common.ICallCommandService;
28import com.android.services.telephony.common.ICallHandlerService;
29
30import java.util.AbstractMap;
31import java.util.List;
32import java.util.Map;
33
34/**
35 * Service used to listen for call state changes.
36 */
37public class CallHandlerService extends Service {
38
39    private final static String TAG = CallHandlerService.class.getSimpleName();
40
41    private static final int ON_UPDATE_CALL = 1;
42    private static final int ON_UPDATE_MULTI_CALL = 2;
43    private static final int ON_UPDATE_CALL_WITH_TEXT_RESPONSES = 3;
44    private static final int ON_AUDIO_MODE = 4;
45    private static final int ON_SUPPORTED_AUDIO_MODE = 5;
46    private static final int ON_DISCONNECT_CALL = 6;
47    private static final int ON_BRING_TO_FOREGROUND = 7;
48    private static final int ON_POST_CHAR_WAIT = 8;
49    private static final int ON_START = 9;
50    private static final int ON_DESTROY = 10;
51
52    private static final int LARGEST_MSG_ID = ON_DESTROY;
53
54
55    private CallList mCallList;
56    private Handler mMainHandler;
57    private Object mHandlerInitLock = new Object();
58    private InCallPresenter mInCallPresenter;
59    private AudioModeProvider mAudioModeProvider;
60    private boolean mServiceStarted = false;
61
62    @Override
63    public void onCreate() {
64        Log.i(TAG, "onCreate");
65        super.onCreate();
66
67        synchronized(mHandlerInitLock) {
68            if (mMainHandler == null) {
69                mMainHandler = new MainHandler();
70            }
71        }
72
73    }
74
75    @Override
76    public void onDestroy() {
77        Log.i(TAG, "onDestroy");
78
79        // onDestroy will get called when:
80        // 1) there are no more calls
81        // 2) the client (TeleService) crashes.
82        //
83        // Because onDestroy is not sequenced with calls to CallHandlerService binder,
84        // we cannot know which is happening.
85        // Thats okay since in both cases we want to end all calls and let the UI know it can tear
86        // itself down when it's ready. Start the destruction sequence.
87        mMainHandler.sendMessage(mMainHandler.obtainMessage(ON_DESTROY));
88    }
89
90
91    @Override
92    public IBinder onBind(Intent intent) {
93        Log.i(TAG, "onBind");
94        return mBinder;
95    }
96
97    @Override
98    public boolean onUnbind(Intent intent) {
99        Log.i(TAG, "onUnbind");
100
101        // Returning true here means we get called on rebind, which is a feature we do not need.
102        // Return false so that all reconnections happen with a call to onBind().
103        return false;
104    }
105
106    private final ICallHandlerService.Stub mBinder = new ICallHandlerService.Stub() {
107
108        @Override
109        public void startCallService(ICallCommandService service) {
110            try {
111                Log.d(TAG, "startCallService: " + service.toString());
112
113                mMainHandler.sendMessage(mMainHandler.obtainMessage(ON_START, service));
114            } catch (Exception e) {
115                Log.e(TAG, "Error processing setCallCommandservice() call", e);
116            }
117        }
118
119        @Override
120        public void onDisconnect(Call call) {
121            try {
122                Log.i(TAG, "onDisconnected: " + call);
123                mMainHandler.sendMessage(mMainHandler.obtainMessage(ON_DISCONNECT_CALL, call));
124            } catch (Exception e) {
125                Log.e(TAG, "Error processing onDisconnect() call.", e);
126            }
127        }
128
129        @Override
130        public void onIncoming(Call call, List<String> textResponses) {
131            try {
132                Log.i(TAG, "onIncomingCall: " + call);
133                Map.Entry<Call, List<String>> incomingCall
134                        = new AbstractMap.SimpleEntry<Call, List<String>>(call, textResponses);
135                mMainHandler.sendMessage(mMainHandler.obtainMessage(
136                        ON_UPDATE_CALL_WITH_TEXT_RESPONSES, incomingCall));
137            } catch (Exception e) {
138                Log.e(TAG, "Error processing onIncoming() call.", e);
139            }
140        }
141
142        @Override
143        public void onUpdate(List<Call> calls) {
144            try {
145                Log.i(TAG, "onUpdate: " + calls);
146                mMainHandler.sendMessage(mMainHandler.obtainMessage(ON_UPDATE_MULTI_CALL, calls));
147            } catch (Exception e) {
148                Log.e(TAG, "Error processing onUpdate() call.", e);
149            }
150        }
151
152        @Override
153        public void onAudioModeChange(int mode, boolean muted) {
154            try {
155                Log.i(TAG, "onAudioModeChange : " +
156                        AudioMode.toString(mode));
157                mMainHandler.sendMessage(mMainHandler.obtainMessage(ON_AUDIO_MODE, mode,
158                            muted ? 1 : 0, null));
159            } catch (Exception e) {
160                Log.e(TAG, "Error processing onAudioModeChange() call.", e);
161            }
162        }
163
164        @Override
165        public void onSupportedAudioModeChange(int modeMask) {
166            try {
167                Log.i(TAG, "onSupportedAudioModeChange : " +
168                        AudioMode.toString(modeMask));
169                mMainHandler.sendMessage(mMainHandler.obtainMessage(ON_SUPPORTED_AUDIO_MODE,
170                        modeMask, 0, null));
171            } catch (Exception e) {
172                Log.e(TAG, "Error processing onSupportedAudioModeChange() call.", e);
173            }
174        }
175
176        @Override
177        public void bringToForeground(boolean showDialpad) {
178            mMainHandler.sendMessage(mMainHandler.obtainMessage(ON_BRING_TO_FOREGROUND,
179                    showDialpad ? 1 : 0, 0));
180        }
181
182        @Override
183        public void onPostDialWait(int callId, String chars) {
184            mMainHandler.sendMessage(mMainHandler.obtainMessage(ON_POST_CHAR_WAIT, callId, 0,
185                    chars));
186        }
187    };
188
189    private void doStart(ICallCommandService service) {
190        Log.i(TAG, "doStart");
191
192        // always setup the new callcommandservice
193        CallCommandClient.getInstance().setService(service);
194
195        // If we have a new service when one is already started, we can continue
196        // using the service that we already have.
197        if (mServiceStarted) {
198            Log.i(TAG, "Starting a service before another one is completed");
199            doStop();
200        }
201
202        mCallList = CallList.getInstance();
203        mAudioModeProvider = AudioModeProvider.getInstance();
204        mInCallPresenter = InCallPresenter.getInstance();
205
206        mInCallPresenter.setUp(getApplicationContext(), mCallList, mAudioModeProvider);
207
208        mServiceStarted = true;
209    }
210
211    public void doStop() {
212        Log.i(TAG, "doStop");
213
214        if (!mServiceStarted) {
215            return;
216        }
217
218        mServiceStarted = false;
219
220        // We are disconnected, clear the call list so that UI can start
221        // tearing itself down.
222        mCallList.clearOnDisconnect();
223        mCallList = null;
224
225        mInCallPresenter.tearDown();
226        mInCallPresenter = null;
227        mAudioModeProvider = null;
228    }
229
230    /**
231     * Handles messages from the service so that they get executed on the main thread, where they
232     * can interact with UI.
233     */
234    private class MainHandler extends Handler {
235        MainHandler() {
236            super(getApplicationContext().getMainLooper(), null, true);
237        }
238
239        @Override
240        public void handleMessage(Message msg) {
241            executeMessage(msg);
242        }
243    }
244
245    private void executeMessage(Message msg) {
246        if (msg.what > LARGEST_MSG_ID) {
247            // If you got here, you may have added a new message and forgotten to
248            // update LARGEST_MSG_ID
249            Log.wtf(TAG, "Cannot handle message larger than LARGEST_MSG_ID.");
250        }
251
252        // If we are not initialized, ignore all messages except start up
253        if (!mServiceStarted && msg.what != ON_START) {
254            Log.i(TAG, "System not initialized.  Ignoring message: " + msg.what);
255            return;
256        }
257
258        Log.d(TAG, "executeMessage " + msg.what);
259
260        switch (msg.what) {
261            case ON_UPDATE_CALL:
262                Log.i(TAG, "ON_UPDATE_CALL: " + msg.obj);
263                mCallList.onUpdate((Call) msg.obj);
264                break;
265            case ON_UPDATE_MULTI_CALL:
266                Log.i(TAG, "ON_UPDATE_MULTI_CALL: " + msg.obj);
267                mCallList.onUpdate((List<Call>) msg.obj);
268                break;
269            case ON_UPDATE_CALL_WITH_TEXT_RESPONSES:
270                AbstractMap.SimpleEntry<Call, List<String>> entry
271                        = (AbstractMap.SimpleEntry<Call, List<String>>) msg.obj;
272                Log.i(TAG, "ON_INCOMING_CALL: " + entry.getKey());
273                mCallList.onIncoming(entry.getKey(), entry.getValue());
274                break;
275            case ON_DISCONNECT_CALL:
276                Log.i(TAG, "ON_DISCONNECT_CALL: " + msg.obj);
277                mCallList.onDisconnect((Call) msg.obj);
278                break;
279            case ON_POST_CHAR_WAIT:
280                mInCallPresenter.onPostDialCharWait(msg.arg1, (String) msg.obj);
281                break;
282            case ON_AUDIO_MODE:
283                Log.i(TAG, "ON_AUDIO_MODE: " +
284                        AudioMode.toString(msg.arg1) + ", muted (" + (msg.arg2 == 1) + ")");
285                mAudioModeProvider.onAudioModeChange(msg.arg1, msg.arg2 == 1);
286                break;
287            case ON_SUPPORTED_AUDIO_MODE:
288                Log.i(TAG, "ON_SUPPORTED_AUDIO_MODE: " + AudioMode.toString(
289                        msg.arg1));
290
291                mAudioModeProvider.onSupportedAudioModeChange(msg.arg1);
292                break;
293            case ON_BRING_TO_FOREGROUND:
294                Log.i(TAG, "ON_BRING_TO_FOREGROUND" + msg.arg1);
295                if (mInCallPresenter != null) {
296                    mInCallPresenter.bringToForeground(msg.arg1 != 0);
297                }
298                break;
299            case ON_START:
300                doStart((ICallCommandService) msg.obj);
301                break;
302            case ON_DESTROY:
303                doStop();
304                break;
305            default:
306                break;
307        }
308    }
309}
310