summaryrefslogtreecommitdiffstats
path: root/telecomm/java/android/telecomm/InCallService.java
blob: c70f56e949f1ff3f1dd97c73d6fe35680ea7fdd7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
/*
 * Copyright (C) 2013 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.telecomm;

import android.app.Service;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;

import com.android.internal.os.SomeArgs;
import com.android.internal.telecomm.IInCallAdapter;
import com.android.internal.telecomm.IInCallService;

/**
 * This service is implemented by any app that wishes to provide the user-interface for managing
 * phone calls. Telecomm binds to this service while there exists a live (active or incoming)
 * call, and uses it to notify the in-call app of any live and and recently disconnected calls.
 * TODO(santoscordon): Needs more/better description of lifecycle once the interface is better
 * defined.
 * TODO(santoscordon): What happens if two or more apps on a given device implement this interface?
 */
public abstract class InCallService extends Service {
    private static final int MSG_SET_IN_CALL_ADAPTER = 1;
    private static final int MSG_ADD_CALL = 2;
    private static final int MSG_SET_ACTIVE = 3;
    private static final int MSG_SET_DISCONNECTED = 4;
    private static final int MSG_SET_HOLD = 5;
    private static final int MSG_ON_AUDIO_STATE_CHANGED = 6;
    private static final int MSG_SET_DIALING = 7;
    private static final int MSG_SET_RINGING = 8;
    private static final int MSG_SET_POST_DIAL = 9;
    private static final int MSG_SET_POST_DIAL_WAIT = 10;
    private static final int MSG_SET_HANDOFF_ENABLED = 11;

    /** Default Handler used to consolidate binder method calls onto a single thread. */
    private final Handler mHandler = new Handler(Looper.getMainLooper()) {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_SET_IN_CALL_ADAPTER:
                    InCallAdapter adapter = new InCallAdapter((IInCallAdapter) msg.obj);
                    setInCallAdapter(adapter);
                    break;
                case MSG_ADD_CALL:
                    addCall((CallInfo) msg.obj);
                    break;
                case MSG_SET_ACTIVE:
                    setActive((String) msg.obj);
                    break;
                case MSG_SET_DIALING:
                    setDialing((String) msg.obj);
                    break;
                case MSG_SET_RINGING:
                    setRinging((String) msg.obj);
                    break;
                case MSG_SET_POST_DIAL: {
                    SomeArgs args = (SomeArgs) msg.obj;
                    try {
                        String callId = (String) args.arg1;
                        String remaining = (String) args.arg2;
                        setPostDial(callId, remaining);
                    } finally {
                        args.recycle();
                    }
                    break;
                }
                case MSG_SET_POST_DIAL_WAIT: {
                    SomeArgs args = (SomeArgs) msg.obj;
                    try {
                        String callId = (String) args.arg1;
                        String remaining = (String) args.arg2;
                        setPostDialWait(callId, remaining);
                    } finally {
                        args.recycle();
                    }
                    break;
                }
                case MSG_SET_DISCONNECTED:
                    setDisconnected((String) msg.obj, msg.arg1);
                    break;
                case MSG_SET_HOLD:
                    setOnHold((String) msg.obj);
                    break;
                case MSG_ON_AUDIO_STATE_CHANGED:
                    onAudioStateChanged((CallAudioState) msg.obj);
                    break;
                case MSG_SET_HANDOFF_ENABLED:
                    setHandoffEnabled((String) msg.obj, msg.arg1 == 1 ? true : false);
                    break;
                default:
                    break;
            }
        }
    };

    /** Manages the binder calls so that the implementor does not need to deal with it. */
    private final class InCallServiceBinder extends IInCallService.Stub {
        /** {@inheritDoc} */
        @Override
        public void setInCallAdapter(IInCallAdapter inCallAdapter) {
            mHandler.obtainMessage(MSG_SET_IN_CALL_ADAPTER, inCallAdapter).sendToTarget();
        }

        /** {@inheritDoc} */
        @Override
        public void addCall(CallInfo callInfo) {
            mHandler.obtainMessage(MSG_ADD_CALL, callInfo).sendToTarget();
        }

        /** {@inheritDoc} */
        @Override
        public void setActive(String callId) {
            mHandler.obtainMessage(MSG_SET_ACTIVE, callId).sendToTarget();
        }

        /** {@inheritDoc} */
        @Override
        public void setDisconnected(String callId, int disconnectCause) {
            mHandler.obtainMessage(MSG_SET_DISCONNECTED, disconnectCause, 0, callId).sendToTarget();
        }

        /** {@inheritDoc} */
        @Override
        public void setOnHold(String callId) {
            mHandler.obtainMessage(MSG_SET_HOLD, callId).sendToTarget();
        }

        /** {@inheritDoc} */
        @Override
        public void onAudioStateChanged(CallAudioState audioState) {
            mHandler.obtainMessage(MSG_ON_AUDIO_STATE_CHANGED, audioState).sendToTarget();
        }

        @Override
        public void setDialing(String callId) {
            mHandler.obtainMessage(MSG_SET_DIALING, callId).sendToTarget();
        }

        @Override
        public void setRinging(String callId) {
            mHandler.obtainMessage(MSG_SET_RINGING, callId).sendToTarget();
        }

        @Override
        public void setPostDial(String callId, String remaining) {
            SomeArgs args = SomeArgs.obtain();
            args.arg1 = callId;
            args.arg2 = remaining;
            mHandler.obtainMessage(MSG_SET_POST_DIAL, args).sendToTarget();
        }

        @Override
        public void setPostDialWait(String callId, String remaining) {
            SomeArgs args = SomeArgs.obtain();
            args.arg1 = callId;
            args.arg2 = remaining;
            mHandler.obtainMessage(MSG_SET_POST_DIAL_WAIT, args).sendToTarget();
        }

        @Override
        public void setHandoffEnabled(String callId, boolean isHandoffEnabled) {
            mHandler.obtainMessage(MSG_SET_HANDOFF_ENABLED, isHandoffEnabled ? 1 : 0, 0,
                    callId).sendToTarget();
        }
    }

    private final InCallServiceBinder mBinder;

    protected InCallService() {
        mBinder = new InCallServiceBinder();
    }

    @Override
    public final IBinder onBind(Intent intent) {
        return mBinder;
    }

    /**
     * Provides the in-call app an adapter object through which to send call-commands such as
     * answering and rejecting incoming calls, disconnecting active calls, and putting calls in
     * special states (mute, hold, etc).
     *
     * @param inCallAdapter Adapter through which an in-call app can send call-commands to Telecomm.
     */
    protected abstract void setInCallAdapter(InCallAdapter inCallAdapter);

    /**
     * Indicates to the in-call app that a new call has been created and an appropriate
     * user-interface should be built and shown to notify the user. Information about the call
     * including its current state is passed in through the callInfo object.
     *
     * @param callInfo Information about the new call.
     */
     protected abstract void addCall(CallInfo callInfo);

    /**
     * Indicates to the in-call app that the specified call is currently connected to another party
     * and a communication channel is open between them. Normal transitions are to
     * {@link #setDisconnected(String,int)} when the call is complete.
     *
     * @param callId The identifier of the call changing state.
     */
    protected abstract void setActive(String callId);

    /**
     * Indicates to the in-call app that the specified call is outgoing and in the dialing state.
     * Normal transition are to {@link #setActive(String)} if the call was answered,
     * {@link #setPostDial(String,String)} if the dialed number includes a post-dial DTMF string, or
     * {@link #setDisconnected(String,int)} if the call was disconnected immediately.
     *
     * @param callId The identifier of the call changing state.
     */
    protected abstract void setDialing(String callId);

    /**
     * Indicates to the in-call app that the specified call is incoming and the user still has the
     * option of answering, rejecting, or doing nothing with the call. This state is usually
     * associated with some type of audible ringtone. Normal transitions are to
     * {@link #setActive(String)} if the call is answered, or {@link #setDisconnected(String,int)}
     * if the call is not answered or is otherwise disconnected for some reason.
     *
     * @param callId The identifier of the call changing state.
     */
    protected abstract void setRinging(String callId);

    /**
     * Indicates to the in-call app that a call has been moved to the
     * {@link CallState#DISCONNECTED} and the user should be notified.
     *
     * @param callId The identifier of the call that was disconnected.
     * @param disconnectCause The reason for the disconnection, any of
     *         {@link android.telephony.DisconnectCause}.
     */
    protected abstract void setDisconnected(String callId, int disconnectCause);

    /**
     * Indicates to the in-call app that a call has been moved to the
     * {@link android.telecomm.CallState#ON_HOLD} state and the user should be notified.
     *
     * @param callId The identifier of the call that was put on hold.
     */
    protected abstract void setOnHold(String callId);

    /**
     * Called when the audio state changes.
     *
     * @param audioState The new {@link CallAudioState}.
     */
    protected abstract void onAudioStateChanged(CallAudioState audioState);

    /**
     * Indicates to the in-call app that the specified call is active but in a "post-dial" state
     * where Telecomm is now sending some dual-tone multi-frequency signaling (DTMF) tones appended
     * to the dialed number. Normal transitions are to {@link #setPostDialWait(String,String)} when
     * the post-dial string requires user confirmation to proceed, {@link #setActive(String)} when
     * the post-dial tones are completed, or {@link #setDisconnected(String,int)} if the call is
     * disconnected.
     *
     * @param callId The identifier of the call changing state.
     * @param remaining The remaining postdial string to be dialed.
     */
    protected abstract void setPostDial(String callId, String remaining);

    /**
     * Indicates to the in-call app that the specified call was in the
     * {@link #setPostDial(String,String)} state but is now waiting for user confirmation before the
     * remaining digits can be sent. Normal transitions are to {@link #setPostDial(String,String)}
     * when the user asks Telecomm to proceed with the post-dial sequence and the in-call app
     * informs Telecomm of this by invoking {@link InCallAdapter#postDialContinue(String)}.
     *
     * @param callId The identifier of the call changing state.
     * @param remaining The remaining postdial string to be dialed.
     */
    protected abstract void setPostDialWait(String callId, String remaining);

    /**
     * Called when the call's handoff state has changed.
     *
     * @param callId The identifier of the call whose handoff state was changed.
     * @param isHandoffEnabled True if handoff is enabled.
     */
    protected abstract void setHandoffEnabled(String callId, boolean isHandoffEnabled);
}