TelephonyConnection.java revision 0e40c46c113095d3c29bdb2882cfb4d1ea1d12f4
1/*
2 * Copyright (C) 2014 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.services.telephony;
18
19import android.net.Uri;
20import android.os.AsyncResult;
21import android.os.Handler;
22import android.os.Message;
23import android.telecom.AudioState;
24import android.telecom.Connection;
25import android.telecom.PhoneAccount;
26import android.telecom.PhoneCapabilities;
27
28import com.android.internal.telephony.Call;
29import com.android.internal.telephony.CallStateException;
30import com.android.internal.telephony.Connection.PostDialListener;
31import com.android.internal.telephony.Phone;
32import com.android.internal.telephony.imsphone.ImsPhoneConnection;
33
34import java.lang.Override;
35import java.util.Objects;
36
37/**
38 * Base class for CDMA and GSM connections.
39 */
40abstract class TelephonyConnection extends Connection {
41    private static final int MSG_PRECISE_CALL_STATE_CHANGED = 1;
42    private static final int MSG_RINGBACK_TONE = 2;
43    private static final int MSG_HANDOVER_STATE_CHANGED = 3;
44    private static final int MSG_DISCONNECT = 4;
45
46    private final Handler mHandler = new Handler() {
47        @Override
48        public void handleMessage(Message msg) {
49            switch (msg.what) {
50                case MSG_PRECISE_CALL_STATE_CHANGED:
51                    Log.v(TelephonyConnection.this, "MSG_PRECISE_CALL_STATE_CHANGED");
52                    updateState();
53                    break;
54                case MSG_HANDOVER_STATE_CHANGED:
55                    Log.v(TelephonyConnection.this, "MSG_HANDOVER_STATE_CHANGED");
56                    AsyncResult ar = (AsyncResult) msg.obj;
57                    com.android.internal.telephony.Connection connection =
58                         (com.android.internal.telephony.Connection) ar.result;
59                    setOriginalConnection(connection);
60                    break;
61                case MSG_RINGBACK_TONE:
62                    Log.v(TelephonyConnection.this, "MSG_RINGBACK_TONE");
63                    // TODO: This code assumes that there is only one connection in the foreground
64                    // call, in other words, it punts on network-mediated conference calling.
65                    if (getOriginalConnection() != getForegroundConnection()) {
66                        Log.v(TelephonyConnection.this, "handleMessage, original connection is " +
67                                "not foreground connection, skipping");
68                        return;
69                    }
70                    setRingbackRequested((Boolean) ((AsyncResult) msg.obj).result);
71                    break;
72                case MSG_DISCONNECT:
73                    updateState();
74                    break;
75            }
76        }
77    };
78
79    private final PostDialListener mPostDialListener = new PostDialListener() {
80        @Override
81        public void onPostDialWait() {
82            Log.v(TelephonyConnection.this, "onPostDialWait");
83            if (mOriginalConnection != null) {
84                setPostDialWait(mOriginalConnection.getRemainingPostDialString());
85            }
86        }
87    };
88
89    /**
90     * Listener for listening to events in the {@link com.android.internal.telephony.Connection}.
91     */
92    private final com.android.internal.telephony.Connection.Listener mOriginalConnectionListener =
93            new com.android.internal.telephony.Connection.ListenerBase() {
94        @Override
95        public void onVideoStateChanged(int videoState) {
96            setVideoState(videoState);
97        }
98
99        /**
100         * The {@link com.android.internal.telephony.Connection} has reported a change in local
101         * video capability.
102         *
103         * @param capable True if capable.
104         */
105        @Override
106        public void onLocalVideoCapabilityChanged(boolean capable) {
107            setLocalVideoCapable(capable);
108        }
109
110        /**
111         * The {@link com.android.internal.telephony.Connection} has reported a change in remote
112         * video capability.
113         *
114         * @param capable True if capable.
115         */
116        @Override
117        public void onRemoteVideoCapabilityChanged(boolean capable) {
118            setRemoteVideoCapable(capable);
119        }
120
121        /**
122         * The {@link com.android.internal.telephony.Connection} has reported a change in the
123         * video call provider.
124         *
125         * @param videoProvider The video call provider.
126         */
127        @Override
128        public void onVideoProviderChanged(VideoProvider videoProvider) {
129            setVideoProvider(videoProvider);
130        }
131
132        /**
133         * Used by the {@link com.android.internal.telephony.Connection} to report a change in the
134         * audio quality for the current call.
135         *
136         * @param audioQuality The audio quality.
137         */
138        @Override
139        public void onAudioQualityChanged(int audioQuality) {
140            setAudioQuality(audioQuality);
141        }
142    };
143
144    private com.android.internal.telephony.Connection mOriginalConnection;
145    private Call.State mOriginalConnectionState = Call.State.IDLE;
146
147    /**
148     * Determines if the {@link TelephonyConnection} has local video capabilities.
149     * This is used when {@link TelephonyConnection#updateCallCapabilities()}} is called,
150     * ensuring the appropriate {@link PhoneCapabilities} are set.  Since {@link PhoneCapabilities}
151     * can be rebuilt at any time it is necessary to track the video capabilities between rebuild.
152     * The {@link PhoneCapabilities} (including video capabilities) are communicated to the telecom
153     * layer.
154     */
155    private boolean mLocalVideoCapable;
156
157    /**
158     * Determines if the {@link TelephonyConnection} has remote video capabilities.
159     * This is used when {@link TelephonyConnection#updateCallCapabilities()}} is called,
160     * ensuring the appropriate {@link PhoneCapabilities} are set.  Since {@link PhoneCapabilities}
161     * can be rebuilt at any time it is necessary to track the video capabilities between rebuild.
162     * The {@link PhoneCapabilities} (including video capabilities) are communicated to the telecom
163     * layer.
164     */
165    private boolean mRemoteVideoCapable;
166
167    /**
168     * Determines the current audio quality for the {@link TelephonyConnection}.
169     * This is used when {@link TelephonyConnection#updateCallCapabilities}} is called to indicate
170     * whether a call has the {@link android.telecom.CallCapabilities#VoLTE} capability.
171     */
172    private int mAudioQuality;
173
174    protected TelephonyConnection(com.android.internal.telephony.Connection originalConnection) {
175        if (originalConnection != null) {
176            setOriginalConnection(originalConnection);
177        }
178    }
179
180    @Override
181    public void onAudioStateChanged(AudioState audioState) {
182        // TODO: update TTY mode.
183        if (getPhone() != null) {
184            getPhone().setEchoSuppressionEnabled();
185        }
186    }
187
188    @Override
189    public void onStateChanged(int state) {
190        Log.v(this, "onStateChanged, state: " + Connection.stateToString(state));
191    }
192
193    @Override
194    public void onDisconnect() {
195        Log.v(this, "onDisconnect");
196        hangup(android.telephony.DisconnectCause.LOCAL);
197    }
198
199    @Override
200    public void onSeparate() {
201        Log.v(this, "onSeparate");
202        if (mOriginalConnection != null) {
203            try {
204                mOriginalConnection.separate();
205            } catch (CallStateException e) {
206                Log.e(this, e, "Call to Connection.separate failed with exception");
207            }
208        }
209    }
210
211    @Override
212    public void onAbort() {
213        Log.v(this, "onAbort");
214        hangup(android.telephony.DisconnectCause.LOCAL);
215    }
216
217    @Override
218    public void onHold() {
219        performHold();
220    }
221
222    @Override
223    public void onUnhold() {
224        performUnhold();
225    }
226
227    @Override
228    public void onAnswer(int videoState) {
229        Log.v(this, "onAnswer");
230        if (isValidRingingCall() && getPhone() != null) {
231            try {
232                getPhone().acceptCall(videoState);
233            } catch (CallStateException e) {
234                Log.e(this, e, "Failed to accept call.");
235            }
236        }
237    }
238
239    @Override
240    public void onReject() {
241        Log.v(this, "onReject");
242        if (isValidRingingCall()) {
243            hangup(android.telephony.DisconnectCause.INCOMING_REJECTED);
244        }
245        super.onReject();
246    }
247
248    @Override
249    public void onPostDialContinue(boolean proceed) {
250        Log.v(this, "onPostDialContinue, proceed: " + proceed);
251        if (mOriginalConnection != null) {
252            if (proceed) {
253                mOriginalConnection.proceedAfterWaitChar();
254            } else {
255                mOriginalConnection.cancelPostDial();
256            }
257        }
258    }
259
260    public void performHold() {
261        Log.v(this, "performHold");
262        // TODO: Can dialing calls be put on hold as well since they take up the
263        // foreground call slot?
264        if (Call.State.ACTIVE == mOriginalConnectionState) {
265            Log.v(this, "Holding active call");
266            try {
267                Phone phone = mOriginalConnection.getCall().getPhone();
268                Call ringingCall = phone.getRingingCall();
269
270                // Although the method says switchHoldingAndActive, it eventually calls a RIL method
271                // called switchWaitingOrHoldingAndActive. What this means is that if we try to put
272                // a call on hold while a call-waiting call exists, it'll end up accepting the
273                // call-waiting call, which is bad if that was not the user's intention. We are
274                // cheating here and simply skipping it because we know any attempt to hold a call
275                // while a call-waiting call is happening is likely a request from Telecom prior to
276                // accepting the call-waiting call.
277                // TODO: Investigate a better solution. It would be great here if we
278                // could "fake" hold by silencing the audio and microphone streams for this call
279                // instead of actually putting it on hold.
280                if (ringingCall.getState() != Call.State.WAITING) {
281                    phone.switchHoldingAndActive();
282                }
283
284                // TODO: Cdma calls are slightly different.
285            } catch (CallStateException e) {
286                Log.e(this, e, "Exception occurred while trying to put call on hold.");
287            }
288        } else {
289            Log.w(this, "Cannot put a call that is not currently active on hold.");
290        }
291    }
292
293    public void performUnhold() {
294        Log.v(this, "performUnhold");
295        if (Call.State.HOLDING == mOriginalConnectionState) {
296            try {
297                // Here's the deal--Telephony hold/unhold is weird because whenever there exists
298                // more than one call, one of them must always be active. In other words, if you
299                // have an active call and holding call, and you put the active call on hold, it
300                // will automatically activate the holding call. This is weird with how Telecom
301                // sends its commands. When a user opts to "unhold" a background call, telecom
302                // issues hold commands to all active calls, and then the unhold command to the
303                // background call. This means that we get two commands...each of which reduces to
304                // switchHoldingAndActive(). The result is that they simply cancel each other out.
305                // To fix this so that it works well with telecom we add a minor hack. If we
306                // have one telephony call, everything works as normally expected. But if we have
307                // two or more calls, we will ignore all requests to "unhold" knowing that the hold
308                // requests already do what we want. If you've read up to this point, I'm very sorry
309                // that we are doing this. I didn't think of a better solution that wouldn't also
310                // make the Telecom APIs very ugly.
311
312                if (!hasMultipleTopLevelCalls()) {
313                    mOriginalConnection.getCall().getPhone().switchHoldingAndActive();
314                } else {
315                    Log.i(this, "Skipping unhold command for %s", this);
316                }
317            } catch (CallStateException e) {
318                Log.e(this, e, "Exception occurred while trying to release call from hold.");
319            }
320        } else {
321            Log.w(this, "Cannot release a call that is not already on hold from hold.");
322        }
323    }
324
325    public void performConference(TelephonyConnection otherConnection) {}
326
327    protected abstract int buildCallCapabilities();
328
329    protected final void updateCallCapabilities() {
330        int newCallCapabilities = buildCallCapabilities();
331        newCallCapabilities = applyVideoCapabilities(newCallCapabilities);
332        newCallCapabilities = applyAudioQualityCapabilities(newCallCapabilities);
333        newCallCapabilities = applyConferenceTerminationCapabilities(newCallCapabilities);
334
335        if (getCallCapabilities() != newCallCapabilities) {
336            setCallCapabilities(newCallCapabilities);
337        }
338    }
339
340    protected final void updateAddress() {
341        updateCallCapabilities();
342        if (mOriginalConnection != null) {
343            Uri address = getAddressFromNumber(mOriginalConnection.getAddress());
344            int presentation = mOriginalConnection.getNumberPresentation();
345            if (!Objects.equals(address, getAddress()) ||
346                    presentation != getAddressPresentation()) {
347                Log.v(this, "updateAddress, address changed");
348                setAddress(address, presentation);
349            }
350
351            String name = mOriginalConnection.getCnapName();
352            int namePresentation = mOriginalConnection.getCnapNamePresentation();
353            if (!Objects.equals(name, getCallerDisplayName()) ||
354                    namePresentation != getCallerDisplayNamePresentation()) {
355                Log.v(this, "updateAddress, caller display name changed");
356                setCallerDisplayName(name, namePresentation);
357            }
358        }
359    }
360
361    void onRemovedFromCallService() {
362        // Subclass can override this to do cleanup.
363    }
364
365    void setOriginalConnection(com.android.internal.telephony.Connection originalConnection) {
366        Log.v(this, "new TelephonyConnection, originalConnection: " + originalConnection);
367        if (mOriginalConnection != null) {
368            getPhone().unregisterForPreciseCallStateChanged(mHandler);
369            getPhone().unregisterForRingbackTone(mHandler);
370            getPhone().unregisterForHandoverStateChanged(mHandler);
371            getPhone().unregisterForDisconnect(mHandler);
372        }
373        mOriginalConnection = originalConnection;
374        getPhone().registerForPreciseCallStateChanged(
375                mHandler, MSG_PRECISE_CALL_STATE_CHANGED, null);
376        getPhone().registerForHandoverStateChanged(
377                mHandler, MSG_HANDOVER_STATE_CHANGED, null);
378        getPhone().registerForRingbackTone(mHandler, MSG_RINGBACK_TONE, null);
379        getPhone().registerForDisconnect(mHandler, MSG_DISCONNECT, null);
380        mOriginalConnection.addPostDialListener(mPostDialListener);
381        mOriginalConnection.addListener(mOriginalConnectionListener);
382
383        // Set video state and capabilities
384        setVideoState(mOriginalConnection.getVideoState());
385        setLocalVideoCapable(mOriginalConnection.isLocalVideoCapable());
386        setRemoteVideoCapable(mOriginalConnection.isRemoteVideoCapable());
387        setVideoProvider(mOriginalConnection.getVideoProvider());
388        setAudioQuality(mOriginalConnection.getAudioQuality());
389
390        updateAddress();
391    }
392
393    protected void hangup(int telephonyDisconnectCode) {
394        if (mOriginalConnection != null) {
395            try {
396                // Hanging up a ringing call requires that we invoke call.hangup() as opposed to
397                // connection.hangup(). Without this change, the party originating the call will not
398                // get sent to voicemail if the user opts to reject the call.
399                if (isValidRingingCall()) {
400                    Call call = getCall();
401                    if (call != null) {
402                        call.hangup();
403                    } else {
404                        Log.w(this, "Attempting to hangup a connection without backing call.");
405                    }
406                } else {
407                    // We still prefer to call connection.hangup() for non-ringing calls in order
408                    // to support hanging-up specific calls within a conference call. If we invoked
409                    // call.hangup() while in a conference, we would end up hanging up the entire
410                    // conference call instead of the specific connection.
411                    mOriginalConnection.hangup();
412                }
413            } catch (CallStateException e) {
414                Log.e(this, e, "Call to Connection.hangup failed with exception");
415            }
416        }
417    }
418
419    com.android.internal.telephony.Connection getOriginalConnection() {
420        return mOriginalConnection;
421    }
422
423    protected Call getCall() {
424        if (mOriginalConnection != null) {
425            return mOriginalConnection.getCall();
426        }
427        return null;
428    }
429
430    Phone getPhone() {
431        Call call = getCall();
432        if (call != null) {
433            return call.getPhone();
434        }
435        return null;
436    }
437
438    private boolean hasMultipleTopLevelCalls() {
439        int numCalls = 0;
440        Phone phone = getPhone();
441        if (phone != null) {
442            if (!phone.getRingingCall().isIdle()) {
443                numCalls++;
444            }
445            if (!phone.getForegroundCall().isIdle()) {
446                numCalls++;
447            }
448            if (!phone.getBackgroundCall().isIdle()) {
449                numCalls++;
450            }
451        }
452        return numCalls > 1;
453    }
454
455    private com.android.internal.telephony.Connection getForegroundConnection() {
456        if (getPhone() != null) {
457            return getPhone().getForegroundCall().getEarliestConnection();
458        }
459        return null;
460    }
461
462    /**
463     * Checks to see the original connection corresponds to an active incoming call. Returns false
464     * if there is no such actual call, or if the associated call is not incoming (See
465     * {@link Call.State#isRinging}).
466     */
467    private boolean isValidRingingCall() {
468        if (getPhone() == null) {
469            Log.v(this, "isValidRingingCall, phone is null");
470            return false;
471        }
472
473        Call ringingCall = getPhone().getRingingCall();
474        if (!ringingCall.getState().isRinging()) {
475            Log.v(this, "isValidRingingCall, ringing call is not in ringing state");
476            return false;
477        }
478
479        if (ringingCall.getEarliestConnection() != mOriginalConnection) {
480            Log.v(this, "isValidRingingCall, ringing call connection does not match");
481            return false;
482        }
483
484        Log.v(this, "isValidRingingCall, returning true");
485        return true;
486    }
487
488    void updateState() {
489        if (mOriginalConnection == null) {
490            return;
491        }
492
493        Call.State newState = mOriginalConnection.getState();
494        Log.v(this, "Update state from %s to %s for %s", mOriginalConnectionState, newState, this);
495        if (mOriginalConnectionState != newState) {
496            mOriginalConnectionState = newState;
497            switch (newState) {
498                case IDLE:
499                    break;
500                case ACTIVE:
501                    setActiveInternal();
502                    break;
503                case HOLDING:
504                    setOnHold();
505                    break;
506                case DIALING:
507                case ALERTING:
508                    setDialing();
509                    break;
510                case INCOMING:
511                case WAITING:
512                    setRinging();
513                    break;
514                case DISCONNECTED:
515                    setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause(
516                            mOriginalConnection.getDisconnectCause()));
517                    close();
518                    break;
519                case DISCONNECTING:
520                    break;
521            }
522        }
523        updateCallCapabilities();
524        updateAddress();
525    }
526
527    private void setActiveInternal() {
528        if (getState() == STATE_ACTIVE) {
529            Log.w(this, "Should not be called if this is already ACTIVE");
530            return;
531        }
532
533        // When we set a call to active, we need to make sure that there are no other active
534        // calls. However, the ordering of state updates to connections can be non-deterministic
535        // since all connections register for state changes on the phone independently.
536        // To "optimize", we check here to see if there already exists any active calls.  If so,
537        // we issue an update for those calls first to make sure we only have one top-level
538        // active call.
539        if (getConnectionService() != null) {
540            for (Connection current : getConnectionService().getAllConnections()) {
541                if (current != this && current instanceof TelephonyConnection) {
542                    TelephonyConnection other = (TelephonyConnection) current;
543                    if (other.getState() == STATE_ACTIVE) {
544                        other.updateState();
545                    }
546                }
547            }
548        }
549        setActive();
550    }
551
552    private void close() {
553        Log.v(this, "close");
554        if (getPhone() != null) {
555            getPhone().unregisterForPreciseCallStateChanged(mHandler);
556            getPhone().unregisterForRingbackTone(mHandler);
557            getPhone().unregisterForHandoverStateChanged(mHandler);
558        }
559        mOriginalConnection = null;
560        destroy();
561    }
562
563    /**
564     * Applies the video capability states to the CallCapabilities bit-mask.
565     *
566     * @param capabilities The CallCapabilities bit-mask.
567     * @return The capabilities with video capabilities applied.
568     */
569    private int applyVideoCapabilities(int capabilities) {
570        int currentCapabilities = capabilities;
571        if (mRemoteVideoCapable) {
572            currentCapabilities = applyCapability(currentCapabilities,
573                    PhoneCapabilities.SUPPORTS_VT_REMOTE);
574        } else {
575            currentCapabilities = removeCapability(currentCapabilities,
576                    PhoneCapabilities.SUPPORTS_VT_REMOTE);
577        }
578
579        if (mLocalVideoCapable) {
580            currentCapabilities = applyCapability(currentCapabilities,
581                    PhoneCapabilities.SUPPORTS_VT_LOCAL);
582        } else {
583            currentCapabilities = removeCapability(currentCapabilities,
584                    PhoneCapabilities.SUPPORTS_VT_LOCAL);
585        }
586        return currentCapabilities;
587    }
588
589    /**
590     * Applies the audio capabilities to the {@code CallCapabilities} bit-mask.  A call with high
591     * definition audio is considered to have the {@code VoLTE} call capability as VoLTE uses high
592     * definition audio.
593     *
594     * @param callCapabilities The {@code CallCapabilities} bit-mask.
595     * @return The capabilities with the audio capabilities applied.
596     */
597    private int applyAudioQualityCapabilities(int callCapabilities) {
598        int currentCapabilities = callCapabilities;
599
600        if (mAudioQuality ==
601                com.android.internal.telephony.Connection.AUDIO_QUALITY_HIGH_DEFINITION) {
602            currentCapabilities = applyCapability(currentCapabilities, PhoneCapabilities.VoLTE);
603        } else {
604            currentCapabilities = removeCapability(currentCapabilities, PhoneCapabilities.VoLTE);
605        }
606
607        return currentCapabilities;
608    }
609
610    /**
611     * Applies capabilities specific to conferences termination to the
612     * {@code CallCapabilities} bit-mask.
613     *
614     * @param callCapabilities The {@code CallCapabilities} bit-mask.
615     * @return The capabilities with the IMS conference capabilities applied.
616     */
617    private int applyConferenceTerminationCapabilities(int callCapabilities) {
618        int currentCapabilities = callCapabilities;
619
620        // An IMS call cannot be individually disconnected or separated from its parent conference
621        boolean isImsCall = getOriginalConnection() instanceof ImsPhoneConnection;
622        if (!isImsCall) {
623            currentCapabilities |=
624                    PhoneCapabilities.DISCONNECT_FROM_CONFERENCE
625                    | PhoneCapabilities.SEPARATE_FROM_CONFERENCE;
626        }
627
628        return currentCapabilities;
629    }
630
631    /**
632     * Returns the local video capability state for the connection.
633     *
634     * @return {@code True} if the connection has local video capabilities.
635     */
636    public boolean isLocalVideoCapable() {
637        return mLocalVideoCapable;
638    }
639
640    /**
641     * Returns the remote video capability state for the connection.
642     *
643     * @return {@code True} if the connection has remote video capabilities.
644     */
645    public boolean isRemoteVideoCapable() {
646        return mRemoteVideoCapable;
647    }
648
649    /**
650     * Sets whether video capability is present locally.  Used during rebuild of the
651     * {@link PhoneCapabilities} to set the video call capabilities.
652     *
653     * @param capable {@code True} if video capable.
654     */
655    public void setLocalVideoCapable(boolean capable) {
656        mLocalVideoCapable = capable;
657        updateCallCapabilities();
658    }
659
660    /**
661     * Sets whether video capability is present remotely.  Used during rebuild of the
662     * {@link PhoneCapabilities} to set the video call capabilities.
663     *
664     * @param capable {@code True} if video capable.
665     */
666    public void setRemoteVideoCapable(boolean capable) {
667        mRemoteVideoCapable = capable;
668        updateCallCapabilities();
669    }
670
671    /**
672     * Sets the current call audio quality.  Used during rebuild of the
673     * {@link PhoneCapabilities} to set or unset the {@link PhoneCapabilities#VoLTE} capability.
674     *
675     * @param audioQuality The audio quality.
676     */
677    public void setAudioQuality(int audioQuality) {
678        mAudioQuality = audioQuality;
679        updateCallCapabilities();
680    }
681
682    /**
683     * Obtains the current call audio quality.
684     */
685    public int getAudioQuality() {
686        return mAudioQuality;
687    }
688
689    void resetStateForConference() {
690        if (getState() == Connection.STATE_HOLDING) {
691            if (mOriginalConnection.getState() == Call.State.ACTIVE) {
692                setActive();
693            }
694        }
695    }
696
697    boolean setHoldingForConference() {
698        if (getState() == Connection.STATE_ACTIVE) {
699            setOnHold();
700            return true;
701        }
702        return false;
703    }
704
705    private static Uri getAddressFromNumber(String number) {
706        // Address can be null for blocked calls.
707        if (number == null) {
708            number = "";
709        }
710        return Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null);
711    }
712
713    /**
714     * Applies a capability to a capabilities bit-mask.
715     *
716     * @param capabilities The capabilities bit-mask.
717     * @param capability The capability to apply.
718     * @return The capabilities bit-mask with the capability applied.
719     */
720    private int applyCapability(int capabilities, int capability) {
721        int newCapabilities = capabilities | capability;
722        return newCapabilities;
723    }
724
725    /**
726     * Removes a capability from a capabilities bit-mask.
727     *
728     * @param capabilities The capabilities bit-mask.
729     * @param capability The capability to remove.
730     * @return The capabilities bit-mask with the capability removed.
731     */
732    private int removeCapability(int capabilities, int capability) {
733        int newCapabilities = capabilities & ~capability;
734        return newCapabilities;
735    }
736}
737