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 com.android.contacts.common.CallUtil;
20
21import android.content.Context;
22import android.net.Uri;
23import android.telecom.CallProperties;
24import android.telecom.DisconnectCause;
25import android.telecom.PhoneCapabilities;
26import android.telecom.GatewayInfo;
27import android.telecom.InCallService.VideoCall;
28import android.telecom.PhoneAccountHandle;
29import android.telecom.VideoProfile;
30
31import java.util.ArrayList;
32import java.util.List;
33import java.util.Locale;
34
35/**
36 * Describes a single call and its state.
37 */
38public final class Call {
39    /* Defines different states of this call */
40    public static class State {
41        public static final int INVALID = 0;
42        public static final int IDLE = 1;           /* The call is idle.  Nothing active */
43        public static final int ACTIVE = 2;         /* There is an active call */
44        public static final int INCOMING = 3;       /* A normal incoming phone call */
45        public static final int CALL_WAITING = 4;   /* Incoming call while another is active */
46        public static final int DIALING = 5;        /* An outgoing call during dial phase */
47        public static final int REDIALING = 6;      /* Subsequent dialing attempt after a failure */
48        public static final int ONHOLD = 7;         /* An active phone call placed on hold */
49        public static final int DISCONNECTING = 8;  /* A call is being ended. */
50        public static final int DISCONNECTED = 9;   /* State after a call disconnects */
51        public static final int CONFERENCED = 10;   /* Call part of a conference call */
52        public static final int PRE_DIAL_WAIT = 11; /* Waiting for user before outgoing call */
53        public static final int CONNECTING = 12;    /* Waiting for Telecomm broadcast to finish */
54
55        public static boolean isConnectingOrConnected(int state) {
56            switch(state) {
57                case ACTIVE:
58                case INCOMING:
59                case CALL_WAITING:
60                case CONNECTING:
61                case DIALING:
62                case REDIALING:
63                case ONHOLD:
64                case CONFERENCED:
65                    return true;
66                default:
67            }
68            return false;
69        }
70
71        public static boolean isDialing(int state) {
72            return state == DIALING || state == REDIALING;
73        }
74
75        public static String toString(int state) {
76            switch (state) {
77                case INVALID:
78                    return "INVALID";
79                case IDLE:
80                    return "IDLE";
81                case ACTIVE:
82                    return "ACTIVE";
83                case INCOMING:
84                    return "INCOMING";
85                case CALL_WAITING:
86                    return "CALL_WAITING";
87                case DIALING:
88                    return "DIALING";
89                case REDIALING:
90                    return "REDIALING";
91                case ONHOLD:
92                    return "ONHOLD";
93                case DISCONNECTING:
94                    return "DISCONNECTING";
95                case DISCONNECTED:
96                    return "DISCONNECTED";
97                case CONFERENCED:
98                    return "CONFERENCED";
99                case PRE_DIAL_WAIT:
100                    return "PRE_DIAL_WAIT";
101                case CONNECTING:
102                    return "CONNECTING";
103                default:
104                    return "UNKNOWN";
105            }
106        }
107    }
108
109    /**
110     * Defines different states of session modify requests, which are used to upgrade to video, or
111     * downgrade to audio.
112     */
113    public static class SessionModificationState {
114        public static final int NO_REQUEST = 0;
115        public static final int WAITING_FOR_RESPONSE = 1;
116        public static final int REQUEST_FAILED = 2;
117        public static final int RECEIVED_UPGRADE_TO_VIDEO_REQUEST = 3;
118    }
119
120    private static final String ID_PREFIX = Call.class.getSimpleName() + "_";
121    private static int sIdCounter = 0;
122
123    private android.telecom.Call.Listener mTelecommCallListener =
124            new android.telecom.Call.Listener() {
125                @Override
126                public void onStateChanged(android.telecom.Call call, int newState) {
127                    update();
128                }
129
130                @Override
131                public void onParentChanged(android.telecom.Call call,
132                        android.telecom.Call newParent) {
133                    update();
134                }
135
136                @Override
137                public void onChildrenChanged(android.telecom.Call call,
138                        List<android.telecom.Call> children) {
139                    update();
140                }
141
142                @Override
143                public void onDetailsChanged(android.telecom.Call call,
144                        android.telecom.Call.Details details) {
145                    update();
146                }
147
148                @Override
149                public void onCannedTextResponsesLoaded(android.telecom.Call call,
150                        List<String> cannedTextResponses) {
151                    update();
152                }
153
154                @Override
155                public void onPostDialWait(android.telecom.Call call,
156                        String remainingPostDialSequence) {
157                    update();
158                }
159
160                @Override
161                public void onVideoCallChanged(android.telecom.Call call,
162                        VideoCall videoCall) {
163                    update();
164                }
165
166                @Override
167                public void onCallDestroyed(android.telecom.Call call) {
168                    call.removeListener(mTelecommCallListener);
169                }
170
171                @Override
172                public void onConferenceableCallsChanged(android.telecom.Call call,
173                        List<android.telecom.Call> conferenceableCalls) {
174                    update();
175                }
176            };
177
178    private final android.telecom.Call mTelecommCall;
179    private final String mId;
180    private int mState = State.INVALID;
181    private DisconnectCause mDisconnectCause;
182    private int mSessionModificationState;
183    private final List<String> mChildCallIds = new ArrayList<>();
184
185    private InCallVideoCallListener mVideoCallListener;
186
187    public Call(android.telecom.Call telecommCall) {
188        mTelecommCall = telecommCall;
189        mId = ID_PREFIX + Integer.toString(sIdCounter++);
190        updateFromTelecommCall();
191        mTelecommCall.addListener(mTelecommCallListener);
192    }
193
194    public android.telecom.Call getTelecommCall() {
195        return mTelecommCall;
196    }
197
198    private void update() {
199        int oldState = getState();
200        updateFromTelecommCall();
201        if (oldState != getState() && getState() == Call.State.DISCONNECTED) {
202            CallList.getInstance().onDisconnect(this);
203        } else {
204            CallList.getInstance().onUpdate(this);
205        }
206    }
207
208    private void updateFromTelecommCall() {
209        Log.d(this, "updateFromTelecommCall: " + mTelecommCall);
210        setState(translateState(mTelecommCall.getState()));
211        setDisconnectCause(mTelecommCall.getDetails().getDisconnectCause());
212
213        if (mTelecommCall.getVideoCall() != null) {
214            if (mVideoCallListener == null) {
215                mVideoCallListener = new InCallVideoCallListener(this);
216            }
217            mTelecommCall.getVideoCall().setVideoCallListener(mVideoCallListener);
218        }
219
220        mChildCallIds.clear();
221        for (int i = 0; i < mTelecommCall.getChildren().size(); i++) {
222            mChildCallIds.add(
223                    CallList.getInstance().getCallByTelecommCall(
224                            mTelecommCall.getChildren().get(i)).getId());
225        }
226    }
227
228    private static int translateState(int state) {
229        switch (state) {
230            case android.telecom.Call.STATE_CONNECTING:
231                return Call.State.CONNECTING;
232            case android.telecom.Call.STATE_PRE_DIAL_WAIT:
233                return Call.State.PRE_DIAL_WAIT;
234            case android.telecom.Call.STATE_DIALING:
235            case android.telecom.Call.STATE_NEW:
236                return Call.State.DIALING;
237            case android.telecom.Call.STATE_RINGING:
238                return Call.State.INCOMING;
239            case android.telecom.Call.STATE_ACTIVE:
240                return Call.State.ACTIVE;
241            case android.telecom.Call.STATE_HOLDING:
242                return Call.State.ONHOLD;
243            case android.telecom.Call.STATE_DISCONNECTED:
244                return Call.State.DISCONNECTED;
245            case android.telecom.Call.STATE_DISCONNECTING:
246                return Call.State.DISCONNECTING;
247            default:
248                return Call.State.INVALID;
249        }
250    }
251
252    public String getId() {
253        return mId;
254    }
255
256    public String getNumber() {
257        if (mTelecommCall.getDetails().getGatewayInfo() != null) {
258            return mTelecommCall.getDetails().getGatewayInfo()
259                    .getOriginalAddress().getSchemeSpecificPart();
260        }
261        return getHandle() == null ? null : getHandle().getSchemeSpecificPart();
262    }
263
264    public Uri getHandle() {
265        return mTelecommCall.getDetails().getHandle();
266    }
267
268    public int getState() {
269        if (mTelecommCall.getParent() != null) {
270            return State.CONFERENCED;
271        } else {
272            return mState;
273        }
274    }
275
276    public void setState(int state) {
277        mState = state;
278    }
279
280    public int getNumberPresentation() {
281        return getTelecommCall().getDetails().getHandlePresentation();
282    }
283
284    public int getCnapNamePresentation() {
285        return getTelecommCall().getDetails().getCallerDisplayNamePresentation();
286    }
287
288    public String getCnapName() {
289        return getTelecommCall().getDetails().getCallerDisplayName();
290    }
291
292    /** Returns call disconnect cause, defined by {@link DisconnectCause}. */
293    public DisconnectCause getDisconnectCause() {
294        if (mState == State.DISCONNECTED || mState == State.IDLE) {
295            return mDisconnectCause;
296        }
297
298        return new DisconnectCause(DisconnectCause.UNKNOWN);
299    }
300
301    public void setDisconnectCause(DisconnectCause disconnectCause) {
302        mDisconnectCause = disconnectCause;
303    }
304
305    /** Returns the possible text message responses. */
306    public List<String> getCannedSmsResponses() {
307        return mTelecommCall.getCannedTextResponses();
308    }
309
310    /** Checks if the call supports the given set of capabilities supplied as a bit mask. */
311    public boolean can(int capabilities) {
312        int supportedCapabilities = mTelecommCall.getDetails().getCallCapabilities();
313
314        if ((capabilities & PhoneCapabilities.MERGE_CONFERENCE) != 0) {
315            // We allow you to merge if the capabilities allow it or if it is a call with
316            // conferenceable calls.
317            if (mTelecommCall.getConferenceableCalls().isEmpty() &&
318                    ((PhoneCapabilities.MERGE_CONFERENCE & supportedCapabilities) == 0)) {
319                // Cannot merge calls if there are no calls to merge with.
320                return false;
321            }
322            capabilities &= ~PhoneCapabilities.MERGE_CONFERENCE;
323        }
324        return (capabilities == (capabilities & mTelecommCall.getDetails().getCallCapabilities()));
325    }
326
327    private boolean hasProperty(int property) {
328        return property == (property & mTelecommCall.getDetails().getCallProperties());
329    }
330
331    /** Gets the time when the call first became active. */
332    public long getConnectTimeMillis() {
333        return mTelecommCall.getDetails().getConnectTimeMillis();
334    }
335
336    public boolean isConferenceCall() {
337        return hasProperty(CallProperties.CONFERENCE);
338    }
339
340    public GatewayInfo getGatewayInfo() {
341        return mTelecommCall.getDetails().getGatewayInfo();
342    }
343
344    public PhoneAccountHandle getAccountHandle() {
345        return mTelecommCall.getDetails().getAccountHandle();
346    }
347
348    public VideoCall getVideoCall() {
349        return mTelecommCall.getVideoCall();
350    }
351
352    public List<String> getChildCallIds() {
353        return mChildCallIds;
354    }
355
356    public String getParentId() {
357        android.telecom.Call parentCall = mTelecommCall.getParent();
358        if (parentCall != null) {
359            return CallList.getInstance().getCallByTelecommCall(parentCall).getId();
360        }
361        return null;
362    }
363
364    public int getVideoState() {
365        return mTelecommCall.getDetails().getVideoState();
366    }
367
368    public boolean isVideoCall(Context context) {
369        return CallUtil.isVideoEnabled(context) &&
370                VideoProfile.VideoState.isBidirectional(getVideoState());
371    }
372
373    public void setSessionModificationState(int state) {
374        boolean hasChanged = mSessionModificationState != state;
375        mSessionModificationState = state;
376
377        if (hasChanged) {
378            update();
379        }
380    }
381
382    public static boolean areSame(Call call1, Call call2) {
383        if (call1 == null && call2 == null) {
384            return true;
385        } else if (call1 == null || call2 == null) {
386            return false;
387        }
388
389        // otherwise compare call Ids
390        return call1.getId().equals(call2.getId());
391    }
392
393    public int getSessionModificationState() {
394        return mSessionModificationState;
395    }
396
397    @Override
398    public String toString() {
399        return String.format(Locale.US, "[%s, %s, %s, children:%s, parent:%s, videoState:%d]",
400                mId,
401                State.toString(getState()),
402                PhoneCapabilities.toString(mTelecommCall.getDetails().getCallCapabilities()),
403                mChildCallIds,
404                getParentId(),
405                mTelecommCall.getDetails().getVideoState());
406    }
407}
408