Home | History | Annotate | Download | only in telecom
      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 
     17 package com.android.server.telecom;
     18 
     19 import android.Manifest;
     20 import android.content.ComponentName;
     21 import android.content.Context;
     22 import android.content.Intent;
     23 import android.content.ServiceConnection;
     24 import android.content.pm.PackageManager;
     25 import android.content.pm.ResolveInfo;
     26 import android.content.pm.ServiceInfo;
     27 import android.content.res.Resources;
     28 import android.os.Bundle;
     29 import android.os.Handler;
     30 import android.os.IBinder;
     31 import android.os.Looper;
     32 import android.os.RemoteException;
     33 import android.os.Trace;
     34 import android.os.UserHandle;
     35 import android.telecom.CallAudioState;
     36 import android.telecom.ConnectionService;
     37 import android.telecom.DefaultDialerManager;
     38 import android.telecom.InCallService;
     39 import android.telecom.Log;
     40 import android.telecom.Logging.Runnable;
     41 import android.telecom.ParcelableCall;
     42 import android.telecom.TelecomManager;
     43 import android.text.TextUtils;
     44 import android.util.ArrayMap;
     45 
     46 import com.android.internal.annotations.VisibleForTesting;
     47 // TODO: Needed for move to system service: import com.android.internal.R;
     48 import com.android.internal.telecom.IInCallService;
     49 import com.android.internal.util.IndentingPrintWriter;
     50 import com.android.server.telecom.SystemStateProvider.SystemStateListener;
     51 
     52 import java.util.ArrayList;
     53 import java.util.Collection;
     54 import java.util.LinkedList;
     55 import java.util.List;
     56 import java.util.Map;
     57 import java.util.Objects;
     58 
     59 /**
     60  * Binds to {@link IInCallService} and provides the service to {@link CallsManager} through which it
     61  * can send updates to the in-call app. This class is created and owned by CallsManager and retains
     62  * a binding to the {@link IInCallService} (implemented by the in-call app).
     63  */
     64 public class InCallController extends CallsManagerListenerBase {
     65 
     66     public class InCallServiceConnection {
     67         /**
     68          * Indicates that a call to {@link #connect(Call)} has succeeded and resulted in a
     69          * connection to an InCallService.
     70          */
     71         public static final int CONNECTION_SUCCEEDED = 1;
     72         /**
     73          * Indicates that a call to {@link #connect(Call)} has failed because of a binding issue.
     74          */
     75         public static final int CONNECTION_FAILED = 2;
     76         /**
     77          * Indicates that a call to {@link #connect(Call)} has been skipped because the
     78          * IncallService does not support the type of call..
     79          */
     80         public static final int CONNECTION_NOT_SUPPORTED = 3;
     81 
     82         public class Listener {
     83             public void onDisconnect(InCallServiceConnection conn) {}
     84         }
     85 
     86         protected Listener mListener;
     87 
     88         public int connect(Call call) { return CONNECTION_FAILED; }
     89         public void disconnect() {}
     90         public boolean isConnected() { return false; }
     91         public void setHasEmergency(boolean hasEmergency) {}
     92         public void setListener(Listener l) {
     93             mListener = l;
     94         }
     95         public InCallServiceInfo getInfo() { return null; }
     96         public void dump(IndentingPrintWriter pw) {}
     97     }
     98 
     99     private class InCallServiceInfo {
    100         private final ComponentName mComponentName;
    101         private boolean mIsExternalCallsSupported;
    102         private boolean mIsSelfManagedCallsSupported;
    103         private final int mType;
    104 
    105         public InCallServiceInfo(ComponentName componentName,
    106                 boolean isExternalCallsSupported,
    107                 boolean isSelfManageCallsSupported,
    108                 int type) {
    109             mComponentName = componentName;
    110             mIsExternalCallsSupported = isExternalCallsSupported;
    111             mIsSelfManagedCallsSupported = isSelfManageCallsSupported;
    112             mType = type;
    113         }
    114 
    115         public ComponentName getComponentName() {
    116             return mComponentName;
    117         }
    118 
    119         public boolean isExternalCallsSupported() {
    120             return mIsExternalCallsSupported;
    121         }
    122 
    123         public boolean isSelfManagedCallsSupported() {
    124             return mIsSelfManagedCallsSupported;
    125         }
    126 
    127         public int getType() {
    128             return mType;
    129         }
    130 
    131         @Override
    132         public boolean equals(Object o) {
    133             if (this == o) {
    134                 return true;
    135             }
    136             if (o == null || getClass() != o.getClass()) {
    137                 return false;
    138             }
    139 
    140             InCallServiceInfo that = (InCallServiceInfo) o;
    141 
    142             if (mIsExternalCallsSupported != that.mIsExternalCallsSupported) {
    143                 return false;
    144             }
    145             if (mIsSelfManagedCallsSupported != that.mIsSelfManagedCallsSupported) {
    146                 return false;
    147             }
    148             return mComponentName.equals(that.mComponentName);
    149 
    150         }
    151 
    152         @Override
    153         public int hashCode() {
    154             return Objects.hash(mComponentName, mIsExternalCallsSupported,
    155                     mIsSelfManagedCallsSupported);
    156         }
    157 
    158         @Override
    159         public String toString() {
    160             return "[" + mComponentName + " supportsExternal? " + mIsExternalCallsSupported +
    161                     " supportsSelfMg?" + mIsSelfManagedCallsSupported + "]";
    162         }
    163     }
    164 
    165     private class InCallServiceBindingConnection extends InCallServiceConnection {
    166 
    167         private final ServiceConnection mServiceConnection = new ServiceConnection() {
    168             @Override
    169             public void onServiceConnected(ComponentName name, IBinder service) {
    170                 Log.startSession("ICSBC.oSC");
    171                 synchronized (mLock) {
    172                     try {
    173                         Log.d(this, "onServiceConnected: %s %b %b", name, mIsBound, mIsConnected);
    174                         mIsBound = true;
    175                         if (mIsConnected) {
    176                             // Only proceed if we are supposed to be connected.
    177                             onConnected(service);
    178                         }
    179                     } finally {
    180                         Log.endSession();
    181                     }
    182                 }
    183             }
    184 
    185             @Override
    186             public void onServiceDisconnected(ComponentName name) {
    187                 Log.startSession("ICSBC.oSD");
    188                 synchronized (mLock) {
    189                     try {
    190                         Log.d(this, "onDisconnected: %s", name);
    191                         mIsBound = false;
    192                         onDisconnected();
    193                     } finally {
    194                         Log.endSession();
    195                     }
    196                 }
    197             }
    198         };
    199 
    200         private final InCallServiceInfo mInCallServiceInfo;
    201         private boolean mIsConnected = false;
    202         private boolean mIsBound = false;
    203 
    204         public InCallServiceBindingConnection(InCallServiceInfo info) {
    205             mInCallServiceInfo = info;
    206         }
    207 
    208         @Override
    209         public int connect(Call call) {
    210             if (mIsConnected) {
    211                 Log.addEvent(call, LogUtils.Events.INFO, "Already connected, ignoring request.");
    212                 return CONNECTION_SUCCEEDED;
    213             }
    214 
    215             if (call != null && call.isSelfManaged() &&
    216                     !mInCallServiceInfo.isSelfManagedCallsSupported()) {
    217                 Log.i(this, "Skipping binding to %s - doesn't support self-mgd calls",
    218                         mInCallServiceInfo);
    219                 mIsConnected = false;
    220                 return CONNECTION_NOT_SUPPORTED;
    221             }
    222 
    223             Intent intent = new Intent(InCallService.SERVICE_INTERFACE);
    224             intent.setComponent(mInCallServiceInfo.getComponentName());
    225             if (call != null && !call.isIncoming() && !call.isExternalCall()){
    226                 intent.putExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS,
    227                         call.getIntentExtras());
    228                 intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
    229                         call.getTargetPhoneAccount());
    230             }
    231 
    232             Log.i(this, "Attempting to bind to InCall %s, with %s", mInCallServiceInfo, intent);
    233             mIsConnected = true;
    234             if (!mContext.bindServiceAsUser(intent, mServiceConnection,
    235                         Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
    236                         UserHandle.CURRENT)) {
    237                 Log.w(this, "Failed to connect.");
    238                 mIsConnected = false;
    239             }
    240 
    241             if (call != null && mIsConnected) {
    242                 call.getAnalytics().addInCallService(
    243                         mInCallServiceInfo.getComponentName().flattenToShortString(),
    244                         mInCallServiceInfo.getType());
    245             }
    246 
    247             return mIsConnected ? CONNECTION_SUCCEEDED : CONNECTION_FAILED;
    248         }
    249 
    250         @Override
    251         public InCallServiceInfo getInfo() {
    252             return mInCallServiceInfo;
    253         }
    254 
    255         @Override
    256         public void disconnect() {
    257             if (mIsConnected) {
    258                 mContext.unbindService(mServiceConnection);
    259                 mIsConnected = false;
    260             } else {
    261                 Log.addEvent(null, LogUtils.Events.INFO, "Already disconnected, ignoring request.");
    262             }
    263         }
    264 
    265         @Override
    266         public boolean isConnected() {
    267             return mIsConnected;
    268         }
    269 
    270         @Override
    271         public void dump(IndentingPrintWriter pw) {
    272             pw.append("BindingConnection [");
    273             pw.append(mIsConnected ? "" : "not ").append("connected, ");
    274             pw.append(mIsBound ? "" : "not ").append("bound]\n");
    275         }
    276 
    277         protected void onConnected(IBinder service) {
    278             boolean shouldRemainConnected =
    279                     InCallController.this.onConnected(mInCallServiceInfo, service);
    280             if (!shouldRemainConnected) {
    281                 // Sometimes we can opt to disconnect for certain reasons, like if the
    282                 // InCallService rejected our initialization step, or the calls went away
    283                 // in the time it took us to bind to the InCallService. In such cases, we go
    284                 // ahead and disconnect ourselves.
    285                 disconnect();
    286             }
    287         }
    288 
    289         protected void onDisconnected() {
    290             InCallController.this.onDisconnected(mInCallServiceInfo);
    291             disconnect();  // Unbind explicitly if we get disconnected.
    292             if (mListener != null) {
    293                 mListener.onDisconnect(InCallServiceBindingConnection.this);
    294             }
    295         }
    296     }
    297 
    298     /**
    299      * A version of the InCallServiceBindingConnection that proxies all calls to a secondary
    300      * connection until it finds an emergency call, or the other connection dies. When one of those
    301      * two things happen, this class instance will take over the connection.
    302      */
    303     private class EmergencyInCallServiceConnection extends InCallServiceBindingConnection {
    304         private boolean mIsProxying = true;
    305         private boolean mIsConnected = false;
    306         private final InCallServiceConnection mSubConnection;
    307 
    308         private Listener mSubListener = new Listener() {
    309             @Override
    310             public void onDisconnect(InCallServiceConnection subConnection) {
    311                 if (subConnection == mSubConnection) {
    312                     if (mIsConnected && mIsProxying) {
    313                         // At this point we know that we need to be connected to the InCallService
    314                         // and we are proxying to the sub connection.  However, the sub-connection
    315                         // just died so we need to stop proxying and connect to the system in-call
    316                         // service instead.
    317                         mIsProxying = false;
    318                         connect(null);
    319                     }
    320                 }
    321             }
    322         };
    323 
    324         public EmergencyInCallServiceConnection(
    325                 InCallServiceInfo info, InCallServiceConnection subConnection) {
    326 
    327             super(info);
    328             mSubConnection = subConnection;
    329             if (mSubConnection != null) {
    330                 mSubConnection.setListener(mSubListener);
    331             }
    332             mIsProxying = (mSubConnection != null);
    333         }
    334 
    335         @Override
    336         public int connect(Call call) {
    337             mIsConnected = true;
    338             if (mIsProxying) {
    339                 int result = mSubConnection.connect(call);
    340                 mIsConnected = result == CONNECTION_SUCCEEDED;
    341                 if (result != CONNECTION_FAILED) {
    342                     return result;
    343                 }
    344                 // Could not connect to child, stop proxying.
    345                 mIsProxying = false;
    346             }
    347 
    348             mEmergencyCallHelper.maybeGrantTemporaryLocationPermission(call,
    349                 mCallsManager.getCurrentUserHandle());
    350 
    351             if (call != null && call.isIncoming()
    352                 && mEmergencyCallHelper.getLastEmergencyCallTimeMillis() > 0) {
    353               // Add the last emergency call time to the call
    354               Bundle extras = new Bundle();
    355               extras.putLong(android.telecom.Call.EXTRA_LAST_EMERGENCY_CALLBACK_TIME_MILLIS,
    356                       mEmergencyCallHelper.getLastEmergencyCallTimeMillis());
    357               call.putExtras(Call.SOURCE_CONNECTION_SERVICE, extras);
    358             }
    359 
    360             // If we are here, we didn't or could not connect to child. So lets connect ourselves.
    361             return super.connect(call);
    362         }
    363 
    364         @Override
    365         public void disconnect() {
    366             Log.i(this, "Disconnect forced!");
    367             if (mIsProxying) {
    368                 mSubConnection.disconnect();
    369             } else {
    370                 super.disconnect();
    371                 mEmergencyCallHelper.maybeRevokeTemporaryLocationPermission();
    372             }
    373             mIsConnected = false;
    374         }
    375 
    376         @Override
    377         public void setHasEmergency(boolean hasEmergency) {
    378             if (hasEmergency) {
    379                 takeControl();
    380             }
    381         }
    382 
    383         @Override
    384         public InCallServiceInfo getInfo() {
    385             if (mIsProxying) {
    386                 return mSubConnection.getInfo();
    387             } else {
    388                 return super.getInfo();
    389             }
    390         }
    391         @Override
    392         protected void onDisconnected() {
    393             // Save this here because super.onDisconnected() could force us to explicitly
    394             // disconnect() as a cleanup step and that sets mIsConnected to false.
    395             boolean shouldReconnect = mIsConnected;
    396             super.onDisconnected();
    397             // We just disconnected.  Check if we are expected to be connected, and reconnect.
    398             if (shouldReconnect && !mIsProxying) {
    399                 connect(null);  // reconnect
    400             }
    401         }
    402 
    403         @Override
    404         public void dump(IndentingPrintWriter pw) {
    405             pw.print("Emergency ICS Connection [");
    406             pw.append(mIsProxying ? "" : "not ").append("proxying, ");
    407             pw.append(mIsConnected ? "" : "not ").append("connected]\n");
    408             pw.increaseIndent();
    409             pw.print("Emergency: ");
    410             super.dump(pw);
    411             if (mSubConnection != null) {
    412                 pw.print("Default-Dialer: ");
    413                 mSubConnection.dump(pw);
    414             }
    415             pw.decreaseIndent();
    416         }
    417 
    418         /**
    419          * Forces the connection to take control from it's subConnection.
    420          */
    421         private void takeControl() {
    422             if (mIsProxying) {
    423                 mIsProxying = false;
    424                 if (mIsConnected) {
    425                     mSubConnection.disconnect();
    426                     super.connect(null);
    427                 }
    428             }
    429         }
    430     }
    431 
    432     /**
    433      * A version of InCallServiceConnection which switches UI between two separate sub-instances of
    434      * InCallServicesConnections.
    435      */
    436     private class CarSwappingInCallServiceConnection extends InCallServiceConnection {
    437         private final InCallServiceConnection mDialerConnection;
    438         private final InCallServiceConnection mCarModeConnection;
    439         private InCallServiceConnection mCurrentConnection;
    440         private boolean mIsCarMode = false;
    441         private boolean mIsConnected = false;
    442 
    443         public CarSwappingInCallServiceConnection(
    444                 InCallServiceConnection dialerConnection,
    445                 InCallServiceConnection carModeConnection) {
    446             mDialerConnection = dialerConnection;
    447             mCarModeConnection = carModeConnection;
    448             mCurrentConnection = getCurrentConnection();
    449         }
    450 
    451         public synchronized void setCarMode(boolean isCarMode) {
    452             Log.i(this, "carmodechange: " + mIsCarMode + " => " + isCarMode);
    453             if (isCarMode != mIsCarMode) {
    454                 mIsCarMode = isCarMode;
    455                 InCallServiceConnection newConnection = getCurrentConnection();
    456                 if (newConnection != mCurrentConnection) {
    457                     if (mIsConnected) {
    458                         mCurrentConnection.disconnect();
    459                         int result = newConnection.connect(null);
    460                         mIsConnected = result == CONNECTION_SUCCEEDED;
    461                     }
    462                     mCurrentConnection = newConnection;
    463                 }
    464             }
    465         }
    466 
    467         @Override
    468         public int connect(Call call) {
    469             if (mIsConnected) {
    470                 Log.i(this, "already connected");
    471                 return CONNECTION_SUCCEEDED;
    472             } else {
    473                 int result = mCurrentConnection.connect(call);
    474                 if (result != CONNECTION_FAILED) {
    475                     mIsConnected = result == CONNECTION_SUCCEEDED;
    476                     return result;
    477                 }
    478             }
    479 
    480             return CONNECTION_FAILED;
    481         }
    482 
    483         @Override
    484         public void disconnect() {
    485             if (mIsConnected) {
    486                 mCurrentConnection.disconnect();
    487                 mIsConnected = false;
    488             } else {
    489                 Log.i(this, "already disconnected");
    490             }
    491         }
    492 
    493         @Override
    494         public boolean isConnected() {
    495             return mIsConnected;
    496         }
    497 
    498         @Override
    499         public void setHasEmergency(boolean hasEmergency) {
    500             if (mDialerConnection != null) {
    501                 mDialerConnection.setHasEmergency(hasEmergency);
    502             }
    503             if (mCarModeConnection != null) {
    504                 mCarModeConnection.setHasEmergency(hasEmergency);
    505             }
    506         }
    507 
    508         @Override
    509         public InCallServiceInfo getInfo() {
    510             return mCurrentConnection.getInfo();
    511         }
    512 
    513         @Override
    514         public void dump(IndentingPrintWriter pw) {
    515             pw.print("Car Swapping ICS [");
    516             pw.append(mIsConnected ? "" : "not ").append("connected]\n");
    517             pw.increaseIndent();
    518             if (mDialerConnection != null) {
    519                 pw.print("Dialer: ");
    520                 mDialerConnection.dump(pw);
    521             }
    522             if (mCarModeConnection != null) {
    523                 pw.print("Car Mode: ");
    524                 mCarModeConnection.dump(pw);
    525             }
    526         }
    527 
    528         private InCallServiceConnection getCurrentConnection() {
    529             if (mIsCarMode && mCarModeConnection != null) {
    530                 return mCarModeConnection;
    531             } else {
    532                 return mDialerConnection;
    533             }
    534         }
    535     }
    536 
    537     private class NonUIInCallServiceConnectionCollection extends InCallServiceConnection {
    538         private final List<InCallServiceBindingConnection> mSubConnections;
    539 
    540         public NonUIInCallServiceConnectionCollection(
    541                 List<InCallServiceBindingConnection> subConnections) {
    542             mSubConnections = subConnections;
    543         }
    544 
    545         @Override
    546         public int connect(Call call) {
    547             for (InCallServiceBindingConnection subConnection : mSubConnections) {
    548                 subConnection.connect(call);
    549             }
    550             return CONNECTION_SUCCEEDED;
    551         }
    552 
    553         @Override
    554         public void disconnect() {
    555             for (InCallServiceBindingConnection subConnection : mSubConnections) {
    556                 if (subConnection.isConnected()) {
    557                     subConnection.disconnect();
    558                 }
    559             }
    560         }
    561 
    562         @Override
    563         public boolean isConnected() {
    564             boolean connected = false;
    565             for (InCallServiceBindingConnection subConnection : mSubConnections) {
    566                 connected = connected || subConnection.isConnected();
    567             }
    568             return connected;
    569         }
    570 
    571         @Override
    572         public void dump(IndentingPrintWriter pw) {
    573             pw.println("Non-UI Connections:");
    574             pw.increaseIndent();
    575             for (InCallServiceBindingConnection subConnection : mSubConnections) {
    576                 subConnection.dump(pw);
    577             }
    578             pw.decreaseIndent();
    579         }
    580     }
    581 
    582     private final Call.Listener mCallListener = new Call.ListenerBase() {
    583         @Override
    584         public void onConnectionCapabilitiesChanged(Call call) {
    585             updateCall(call);
    586         }
    587 
    588         @Override
    589         public void onConnectionPropertiesChanged(Call call, boolean didRttChange) {
    590             updateCall(call, false /* includeVideoProvider */, didRttChange);
    591         }
    592 
    593         @Override
    594         public void onCannedSmsResponsesLoaded(Call call) {
    595             updateCall(call);
    596         }
    597 
    598         @Override
    599         public void onVideoCallProviderChanged(Call call) {
    600             updateCall(call, true /* videoProviderChanged */, false);
    601         }
    602 
    603         @Override
    604         public void onStatusHintsChanged(Call call) {
    605             updateCall(call);
    606         }
    607 
    608         /**
    609          * Listens for changes to extras reported by a Telecom {@link Call}.
    610          *
    611          * Extras changes can originate from a {@link ConnectionService} or an {@link InCallService}
    612          * so we will only trigger an update of the call information if the source of the extras
    613          * change was a {@link ConnectionService}.
    614          *
    615          * @param call The call.
    616          * @param source The source of the extras change ({@link Call#SOURCE_CONNECTION_SERVICE} or
    617          *               {@link Call#SOURCE_INCALL_SERVICE}).
    618          * @param extras The extras.
    619          */
    620         @Override
    621         public void onExtrasChanged(Call call, int source, Bundle extras) {
    622             // Do not inform InCallServices of changes which originated there.
    623             if (source == Call.SOURCE_INCALL_SERVICE) {
    624                 return;
    625             }
    626             updateCall(call);
    627         }
    628 
    629         /**
    630          * Listens for changes to extras reported by a Telecom {@link Call}.
    631          *
    632          * Extras changes can originate from a {@link ConnectionService} or an {@link InCallService}
    633          * so we will only trigger an update of the call information if the source of the extras
    634          * change was a {@link ConnectionService}.
    635          *  @param call The call.
    636          * @param source The source of the extras change ({@link Call#SOURCE_CONNECTION_SERVICE} or
    637          *               {@link Call#SOURCE_INCALL_SERVICE}).
    638          * @param keys The extra key removed
    639          */
    640         @Override
    641         public void onExtrasRemoved(Call call, int source, List<String> keys) {
    642             // Do not inform InCallServices of changes which originated there.
    643             if (source == Call.SOURCE_INCALL_SERVICE) {
    644                 return;
    645             }
    646             updateCall(call);
    647         }
    648 
    649         @Override
    650         public void onHandleChanged(Call call) {
    651             updateCall(call);
    652         }
    653 
    654         @Override
    655         public void onCallerDisplayNameChanged(Call call) {
    656             updateCall(call);
    657         }
    658 
    659         @Override
    660         public void onVideoStateChanged(Call call, int previousVideoState, int newVideoState) {
    661             updateCall(call);
    662         }
    663 
    664         @Override
    665         public void onTargetPhoneAccountChanged(Call call) {
    666             updateCall(call);
    667         }
    668 
    669         @Override
    670         public void onConferenceableCallsChanged(Call call) {
    671             updateCall(call);
    672         }
    673 
    674         @Override
    675         public void onConnectionEvent(Call call, String event, Bundle extras) {
    676             notifyConnectionEvent(call, event, extras);
    677         }
    678 
    679         @Override
    680         public void onHandoverFailed(Call call, int error) {
    681             notifyHandoverFailed(call, error);
    682         }
    683 
    684         @Override
    685         public void onHandoverComplete(Call call) {
    686             notifyHandoverComplete(call);
    687         }
    688 
    689         @Override
    690         public void onRttInitiationFailure(Call call, int reason) {
    691             notifyRttInitiationFailure(call, reason);
    692             updateCall(call, false, true);
    693         }
    694 
    695         @Override
    696         public void onRemoteRttRequest(Call call, int requestId) {
    697             notifyRemoteRttRequest(call, requestId);
    698         }
    699     };
    700 
    701     private final SystemStateListener mSystemStateListener = new SystemStateListener() {
    702         @Override
    703         public void onCarModeChanged(boolean isCarMode) {
    704             if (mInCallServiceConnection != null) {
    705                 mInCallServiceConnection.setCarMode(shouldUseCarModeUI());
    706             }
    707         }
    708     };
    709 
    710     private static final int IN_CALL_SERVICE_TYPE_INVALID = 0;
    711     private static final int IN_CALL_SERVICE_TYPE_DIALER_UI = 1;
    712     private static final int IN_CALL_SERVICE_TYPE_SYSTEM_UI = 2;
    713     private static final int IN_CALL_SERVICE_TYPE_CAR_MODE_UI = 3;
    714     private static final int IN_CALL_SERVICE_TYPE_NON_UI = 4;
    715 
    716     /** The in-call app implementations, see {@link IInCallService}. */
    717     private final Map<InCallServiceInfo, IInCallService> mInCallServices = new ArrayMap<>();
    718 
    719     /**
    720      * The {@link ComponentName} of the bound In-Call UI Service.
    721      */
    722     private ComponentName mInCallUIComponentName;
    723 
    724     private final CallIdMapper mCallIdMapper = new CallIdMapper(Call::getId);
    725 
    726     /** The {@link ComponentName} of the default InCall UI. */
    727     private final ComponentName mSystemInCallComponentName;
    728 
    729     private final Context mContext;
    730     private final TelecomSystem.SyncRoot mLock;
    731     private final CallsManager mCallsManager;
    732     private final SystemStateProvider mSystemStateProvider;
    733     private final Timeouts.Adapter mTimeoutsAdapter;
    734     private final DefaultDialerCache mDefaultDialerCache;
    735     private final EmergencyCallHelper mEmergencyCallHelper;
    736     private CarSwappingInCallServiceConnection mInCallServiceConnection;
    737     private NonUIInCallServiceConnectionCollection mNonUIInCallServiceConnections;
    738 
    739     public InCallController(Context context, TelecomSystem.SyncRoot lock, CallsManager callsManager,
    740             SystemStateProvider systemStateProvider,
    741             DefaultDialerCache defaultDialerCache, Timeouts.Adapter timeoutsAdapter,
    742             EmergencyCallHelper emergencyCallHelper) {
    743         mContext = context;
    744         mLock = lock;
    745         mCallsManager = callsManager;
    746         mSystemStateProvider = systemStateProvider;
    747         mTimeoutsAdapter = timeoutsAdapter;
    748         mDefaultDialerCache = defaultDialerCache;
    749         mEmergencyCallHelper = emergencyCallHelper;
    750 
    751         Resources resources = mContext.getResources();
    752         mSystemInCallComponentName = new ComponentName(
    753                 resources.getString(R.string.ui_default_package),
    754                 resources.getString(R.string.incall_default_class));
    755 
    756         mSystemStateProvider.addListener(mSystemStateListener);
    757     }
    758 
    759     @Override
    760     public void onCallAdded(Call call) {
    761         if (!isBoundAndConnectedToServices()) {
    762             Log.i(this, "onCallAdded: %s; not bound or connected.", call);
    763             // We are not bound, or we're not connected.
    764             bindToServices(call);
    765         } else {
    766             // We are bound, and we are connected.
    767             adjustServiceBindingsForEmergency();
    768 
    769             // This is in case an emergency call is added while there is an existing call.
    770             mEmergencyCallHelper.maybeGrantTemporaryLocationPermission(call,
    771                     mCallsManager.getCurrentUserHandle());
    772 
    773             Log.i(this, "onCallAdded: %s", call);
    774             // Track the call if we don't already know about it.
    775             addCall(call);
    776 
    777             Log.i(this, "mInCallServiceConnection isConnected=%b",
    778                     mInCallServiceConnection.isConnected());
    779 
    780             List<ComponentName> componentsUpdated = new ArrayList<>();
    781             for (Map.Entry<InCallServiceInfo, IInCallService> entry : mInCallServices.entrySet()) {
    782                 InCallServiceInfo info = entry.getKey();
    783 
    784                 if (call.isExternalCall() && !info.isExternalCallsSupported()) {
    785                     continue;
    786                 }
    787 
    788                 if (call.isSelfManaged() && !info.isSelfManagedCallsSupported()) {
    789                     continue;
    790                 }
    791 
    792                 // Only send the RTT call if it's a UI in-call service
    793                 boolean includeRttCall = info.equals(mInCallServiceConnection.getInfo());
    794 
    795                 componentsUpdated.add(info.getComponentName());
    796                 IInCallService inCallService = entry.getValue();
    797 
    798                 ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall(call,
    799                         true /* includeVideoProvider */, mCallsManager.getPhoneAccountRegistrar(),
    800                         info.isExternalCallsSupported(), includeRttCall);
    801                 try {
    802                     inCallService.addCall(parcelableCall);
    803                 } catch (RemoteException ignored) {
    804                 }
    805             }
    806             Log.i(this, "Call added to components: %s", componentsUpdated);
    807         }
    808     }
    809 
    810     @Override
    811     public void onCallRemoved(Call call) {
    812         Log.i(this, "onCallRemoved: %s", call);
    813         if (mCallsManager.getCalls().isEmpty()) {
    814             /** Let's add a 2 second delay before we send unbind to the services to hopefully
    815              *  give them enough time to process all the pending messages.
    816              */
    817             Handler handler = new Handler(Looper.getMainLooper());
    818             handler.postDelayed(new Runnable("ICC.oCR", mLock) {
    819                 @Override
    820                 public void loggedRun() {
    821                     // Check again to make sure there are no active calls.
    822                     if (mCallsManager.getCalls().isEmpty()) {
    823                         unbindFromServices();
    824 
    825                         mEmergencyCallHelper.maybeRevokeTemporaryLocationPermission();
    826                     }
    827                 }
    828             }.prepare(), mTimeoutsAdapter.getCallRemoveUnbindInCallServicesDelay(
    829                             mContext.getContentResolver()));
    830         }
    831         call.removeListener(mCallListener);
    832         mCallIdMapper.removeCall(call);
    833     }
    834 
    835     @Override
    836     public void onExternalCallChanged(Call call, boolean isExternalCall) {
    837         Log.i(this, "onExternalCallChanged: %s -> %b", call, isExternalCall);
    838 
    839         List<ComponentName> componentsUpdated = new ArrayList<>();
    840         if (!isExternalCall) {
    841             // The call was external but it is no longer external.  We must now add it to any
    842             // InCallServices which do not support external calls.
    843             for (Map.Entry<InCallServiceInfo, IInCallService> entry : mInCallServices.entrySet()) {
    844                 InCallServiceInfo info = entry.getKey();
    845 
    846                 if (info.isExternalCallsSupported()) {
    847                     // For InCallServices which support external calls, the call will have already
    848                     // been added to the connection service, so we do not need to add it again.
    849                     continue;
    850                 }
    851 
    852                 if (call.isSelfManaged() && !info.isSelfManagedCallsSupported()) {
    853                     continue;
    854                 }
    855 
    856                 componentsUpdated.add(info.getComponentName());
    857                 IInCallService inCallService = entry.getValue();
    858 
    859                 // Only send the RTT call if it's a UI in-call service
    860                 boolean includeRttCall = info.equals(mInCallServiceConnection.getInfo());
    861 
    862                 ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall(call,
    863                         true /* includeVideoProvider */, mCallsManager.getPhoneAccountRegistrar(),
    864                         info.isExternalCallsSupported(), includeRttCall);
    865                 try {
    866                     inCallService.addCall(parcelableCall);
    867                 } catch (RemoteException ignored) {
    868                 }
    869             }
    870             Log.i(this, "Previously external call added to components: %s", componentsUpdated);
    871         } else {
    872             // The call was regular but it is now external.  We must now remove it from any
    873             // InCallServices which do not support external calls.
    874             // Remove the call by sending a call update indicating the call was disconnected.
    875             ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall(
    876                     call,
    877                     false /* includeVideoProvider */,
    878                     mCallsManager.getPhoneAccountRegistrar(),
    879                     false /* supportsExternalCalls */,
    880                     android.telecom.Call.STATE_DISCONNECTED /* overrideState */,
    881                     false /* includeRttCall */);
    882 
    883             Log.i(this, "Removing external call %s ==> %s", call, parcelableCall);
    884             for (Map.Entry<InCallServiceInfo, IInCallService> entry : mInCallServices.entrySet()) {
    885                 InCallServiceInfo info = entry.getKey();
    886                 if (info.isExternalCallsSupported()) {
    887                     // For InCallServices which support external calls, we do not need to remove
    888                     // the call.
    889                     continue;
    890                 }
    891 
    892                 componentsUpdated.add(info.getComponentName());
    893                 IInCallService inCallService = entry.getValue();
    894 
    895                 try {
    896                     inCallService.updateCall(parcelableCall);
    897                 } catch (RemoteException ignored) {
    898                 }
    899             }
    900             Log.i(this, "External call removed from components: %s", componentsUpdated);
    901         }
    902     }
    903 
    904     @Override
    905     public void onCallStateChanged(Call call, int oldState, int newState) {
    906         updateCall(call);
    907     }
    908 
    909     @Override
    910     public void onConnectionServiceChanged(
    911             Call call,
    912             ConnectionServiceWrapper oldService,
    913             ConnectionServiceWrapper newService) {
    914         updateCall(call);
    915     }
    916 
    917     @Override
    918     public void onCallAudioStateChanged(CallAudioState oldCallAudioState,
    919             CallAudioState newCallAudioState) {
    920         if (!mInCallServices.isEmpty()) {
    921             Log.i(this, "Calling onAudioStateChanged, audioState: %s -> %s", oldCallAudioState,
    922                     newCallAudioState);
    923             for (IInCallService inCallService : mInCallServices.values()) {
    924                 try {
    925                     inCallService.onCallAudioStateChanged(newCallAudioState);
    926                 } catch (RemoteException ignored) {
    927                 }
    928             }
    929         }
    930     }
    931 
    932     @Override
    933     public void onCanAddCallChanged(boolean canAddCall) {
    934         if (!mInCallServices.isEmpty()) {
    935             Log.i(this, "onCanAddCallChanged : %b", canAddCall);
    936             for (IInCallService inCallService : mInCallServices.values()) {
    937                 try {
    938                     inCallService.onCanAddCallChanged(canAddCall);
    939                 } catch (RemoteException ignored) {
    940                 }
    941             }
    942         }
    943     }
    944 
    945     void onPostDialWait(Call call, String remaining) {
    946         if (!mInCallServices.isEmpty()) {
    947             Log.i(this, "Calling onPostDialWait, remaining = %s", remaining);
    948             for (IInCallService inCallService : mInCallServices.values()) {
    949                 try {
    950                     inCallService.setPostDialWait(mCallIdMapper.getCallId(call), remaining);
    951                 } catch (RemoteException ignored) {
    952                 }
    953             }
    954         }
    955     }
    956 
    957     @Override
    958     public void onIsConferencedChanged(Call call) {
    959         Log.d(this, "onIsConferencedChanged %s", call);
    960         updateCall(call);
    961     }
    962 
    963     void bringToForeground(boolean showDialpad) {
    964         if (!mInCallServices.isEmpty()) {
    965             for (IInCallService inCallService : mInCallServices.values()) {
    966                 try {
    967                     inCallService.bringToForeground(showDialpad);
    968                 } catch (RemoteException ignored) {
    969                 }
    970             }
    971         } else {
    972             Log.w(this, "Asking to bring unbound in-call UI to foreground.");
    973         }
    974     }
    975 
    976     void silenceRinger() {
    977         if (!mInCallServices.isEmpty()) {
    978             for (IInCallService inCallService : mInCallServices.values()) {
    979                 try {
    980                     inCallService.silenceRinger();
    981                 } catch (RemoteException ignored) {
    982                 }
    983             }
    984         }
    985     }
    986 
    987     private void notifyConnectionEvent(Call call, String event, Bundle extras) {
    988         if (!mInCallServices.isEmpty()) {
    989             for (IInCallService inCallService : mInCallServices.values()) {
    990                 try {
    991                     Log.i(this, "notifyConnectionEvent {Call: %s, Event: %s, Extras:[%s]}",
    992                             (call != null ? call.toString() :"null"),
    993                             (event != null ? event : "null") ,
    994                             (extras != null ? extras.toString() : "null"));
    995                     inCallService.onConnectionEvent(mCallIdMapper.getCallId(call), event, extras);
    996                 } catch (RemoteException ignored) {
    997                 }
    998             }
    999         }
   1000     }
   1001 
   1002     private void notifyRttInitiationFailure(Call call, int reason) {
   1003         if (!mInCallServices.isEmpty()) {
   1004              mInCallServices.entrySet().stream()
   1005                     .filter((entry) -> entry.getKey().equals(mInCallServiceConnection.getInfo()))
   1006                     .forEach((entry) -> {
   1007                         try {
   1008                             Log.i(this, "notifyRttFailure, call %s, incall %s",
   1009                                     call, entry.getKey());
   1010                             entry.getValue().onRttInitiationFailure(mCallIdMapper.getCallId(call),
   1011                                     reason);
   1012                         } catch (RemoteException ignored) {
   1013                         }
   1014                     });
   1015         }
   1016     }
   1017 
   1018     private void notifyRemoteRttRequest(Call call, int requestId) {
   1019         if (!mInCallServices.isEmpty()) {
   1020             mInCallServices.entrySet().stream()
   1021                     .filter((entry) -> entry.getKey().equals(mInCallServiceConnection.getInfo()))
   1022                     .forEach((entry) -> {
   1023                         try {
   1024                             Log.i(this, "notifyRemoteRttRequest, call %s, incall %s",
   1025                                     call, entry.getKey());
   1026                             entry.getValue().onRttUpgradeRequest(
   1027                                     mCallIdMapper.getCallId(call), requestId);
   1028                         } catch (RemoteException ignored) {
   1029                         }
   1030                     });
   1031         }
   1032     }
   1033 
   1034     private void notifyHandoverFailed(Call call, int error) {
   1035         if (!mInCallServices.isEmpty()) {
   1036             for (IInCallService inCallService : mInCallServices.values()) {
   1037                 try {
   1038                     inCallService.onHandoverFailed(mCallIdMapper.getCallId(call), error);
   1039                 } catch (RemoteException ignored) {
   1040                 }
   1041             }
   1042         }
   1043     }
   1044 
   1045     private void notifyHandoverComplete(Call call) {
   1046         if (!mInCallServices.isEmpty()) {
   1047             for (IInCallService inCallService : mInCallServices.values()) {
   1048                 try {
   1049                     inCallService.onHandoverComplete(mCallIdMapper.getCallId(call));
   1050                 } catch (RemoteException ignored) {
   1051                 }
   1052             }
   1053         }
   1054     }
   1055 
   1056     /**
   1057      * Unbinds an existing bound connection to the in-call app.
   1058      */
   1059     private void unbindFromServices() {
   1060         if (mInCallServiceConnection != null) {
   1061             mInCallServiceConnection.disconnect();
   1062             mInCallServiceConnection = null;
   1063         }
   1064         if (mNonUIInCallServiceConnections != null) {
   1065             mNonUIInCallServiceConnections.disconnect();
   1066             mNonUIInCallServiceConnections = null;
   1067         }
   1068         mInCallServices.clear();
   1069     }
   1070 
   1071     /**
   1072      * Binds to all the UI-providing InCallService as well as system-implemented non-UI
   1073      * InCallServices. Method-invoker must check {@link #isBoundAndConnectedToServices()} before invoking.
   1074      *
   1075      * @param call The newly added call that triggered the binding to the in-call services.
   1076      */
   1077     @VisibleForTesting
   1078     public void bindToServices(Call call) {
   1079         if (mInCallServiceConnection == null) {
   1080             InCallServiceConnection dialerInCall = null;
   1081             InCallServiceInfo defaultDialerComponentInfo = getDefaultDialerComponent();
   1082             Log.i(this, "defaultDialer: " + defaultDialerComponentInfo);
   1083             if (defaultDialerComponentInfo != null &&
   1084                     !defaultDialerComponentInfo.getComponentName().equals(
   1085                             mSystemInCallComponentName)) {
   1086                 dialerInCall = new InCallServiceBindingConnection(defaultDialerComponentInfo);
   1087             }
   1088             Log.i(this, "defaultDialer: " + dialerInCall);
   1089 
   1090             InCallServiceInfo systemInCallInfo = getInCallServiceComponent(
   1091                     mSystemInCallComponentName, IN_CALL_SERVICE_TYPE_SYSTEM_UI);
   1092             EmergencyInCallServiceConnection systemInCall =
   1093                     new EmergencyInCallServiceConnection(systemInCallInfo, dialerInCall);
   1094             systemInCall.setHasEmergency(mCallsManager.hasEmergencyCall());
   1095 
   1096             InCallServiceConnection carModeInCall = null;
   1097             InCallServiceInfo carModeComponentInfo = getCarModeComponent();
   1098             if (carModeComponentInfo != null &&
   1099                     !carModeComponentInfo.getComponentName().equals(mSystemInCallComponentName)) {
   1100                 carModeInCall = new InCallServiceBindingConnection(carModeComponentInfo);
   1101             }
   1102 
   1103             mInCallServiceConnection =
   1104                     new CarSwappingInCallServiceConnection(systemInCall, carModeInCall);
   1105         }
   1106 
   1107         mInCallServiceConnection.setCarMode(shouldUseCarModeUI());
   1108 
   1109         // Actually try binding to the UI InCallService.  If the response
   1110         if (mInCallServiceConnection.connect(call) ==
   1111                 InCallServiceConnection.CONNECTION_SUCCEEDED) {
   1112             // Only connect to the non-ui InCallServices if we actually connected to the main UI
   1113             // one.
   1114             connectToNonUiInCallServices(call);
   1115         } else {
   1116             Log.i(this, "bindToServices: current UI doesn't support call; not binding.");
   1117         }
   1118     }
   1119 
   1120     private void connectToNonUiInCallServices(Call call) {
   1121         List<InCallServiceInfo> nonUIInCallComponents =
   1122                 getInCallServiceComponents(IN_CALL_SERVICE_TYPE_NON_UI);
   1123         List<InCallServiceBindingConnection> nonUIInCalls = new LinkedList<>();
   1124         for (InCallServiceInfo serviceInfo : nonUIInCallComponents) {
   1125             nonUIInCalls.add(new InCallServiceBindingConnection(serviceInfo));
   1126         }
   1127         mNonUIInCallServiceConnections = new NonUIInCallServiceConnectionCollection(nonUIInCalls);
   1128         mNonUIInCallServiceConnections.connect(call);
   1129     }
   1130 
   1131     private InCallServiceInfo getDefaultDialerComponent() {
   1132         String packageName = mDefaultDialerCache.getDefaultDialerApplication(
   1133                 mCallsManager.getCurrentUserHandle().getIdentifier());
   1134         Log.d(this, "Default Dialer package: " + packageName);
   1135 
   1136         return getInCallServiceComponent(packageName, IN_CALL_SERVICE_TYPE_DIALER_UI);
   1137     }
   1138 
   1139     private InCallServiceInfo getCarModeComponent() {
   1140         // Seems strange to cast a String to null, but the signatures of getInCallServiceComponent
   1141         // differ in the types of the first parameter, and passing in null is inherently ambiguous.
   1142         return getInCallServiceComponent((String) null, IN_CALL_SERVICE_TYPE_CAR_MODE_UI);
   1143     }
   1144 
   1145     private InCallServiceInfo getInCallServiceComponent(ComponentName componentName, int type) {
   1146         List<InCallServiceInfo> list = getInCallServiceComponents(componentName, type);
   1147         if (list != null && !list.isEmpty()) {
   1148             return list.get(0);
   1149         } else {
   1150             // Last Resort: Try to bind to the ComponentName given directly.
   1151             Log.e(this, new Exception(), "Package Manager could not find ComponentName: "
   1152                     + componentName +". Trying to bind anyway.");
   1153             return new InCallServiceInfo(componentName, false, false, type);
   1154         }
   1155     }
   1156 
   1157     private InCallServiceInfo getInCallServiceComponent(String packageName, int type) {
   1158         List<InCallServiceInfo> list = getInCallServiceComponents(packageName, type);
   1159         if (list != null && !list.isEmpty()) {
   1160             return list.get(0);
   1161         }
   1162         return null;
   1163     }
   1164 
   1165     private List<InCallServiceInfo> getInCallServiceComponents(int type) {
   1166         return getInCallServiceComponents(null, null, type);
   1167     }
   1168 
   1169     private List<InCallServiceInfo> getInCallServiceComponents(String packageName, int type) {
   1170         return getInCallServiceComponents(packageName, null, type);
   1171     }
   1172 
   1173     private List<InCallServiceInfo> getInCallServiceComponents(ComponentName componentName,
   1174             int type) {
   1175         return getInCallServiceComponents(null, componentName, type);
   1176     }
   1177 
   1178     private List<InCallServiceInfo> getInCallServiceComponents(String packageName,
   1179             ComponentName componentName, int requestedType) {
   1180 
   1181         List<InCallServiceInfo> retval = new LinkedList<>();
   1182 
   1183         Intent serviceIntent = new Intent(InCallService.SERVICE_INTERFACE);
   1184         if (packageName != null) {
   1185             serviceIntent.setPackage(packageName);
   1186         }
   1187         if (componentName != null) {
   1188             serviceIntent.setComponent(componentName);
   1189         }
   1190 
   1191         PackageManager packageManager = mContext.getPackageManager();
   1192         for (ResolveInfo entry : packageManager.queryIntentServicesAsUser(
   1193                 serviceIntent,
   1194                 PackageManager.GET_META_DATA,
   1195                 mCallsManager.getCurrentUserHandle().getIdentifier())) {
   1196             ServiceInfo serviceInfo = entry.serviceInfo;
   1197 
   1198             if (serviceInfo != null) {
   1199                 boolean isExternalCallsSupported = serviceInfo.metaData != null &&
   1200                         serviceInfo.metaData.getBoolean(
   1201                                 TelecomManager.METADATA_INCLUDE_EXTERNAL_CALLS, false);
   1202                 boolean isSelfManageCallsSupported = serviceInfo.metaData != null &&
   1203                         serviceInfo.metaData.getBoolean(
   1204                                 TelecomManager.METADATA_INCLUDE_SELF_MANAGED_CALLS, false);
   1205 
   1206                 int currentType = getInCallServiceType(entry.serviceInfo, packageManager);
   1207                 if (requestedType == 0 || requestedType == currentType) {
   1208                     if (requestedType == IN_CALL_SERVICE_TYPE_NON_UI) {
   1209                         // We enforce the rule that self-managed calls are not supported by non-ui
   1210                         // InCallServices.
   1211                         isSelfManageCallsSupported = false;
   1212                     }
   1213                     retval.add(new InCallServiceInfo(
   1214                             new ComponentName(serviceInfo.packageName, serviceInfo.name),
   1215                             isExternalCallsSupported, isSelfManageCallsSupported, requestedType));
   1216                 }
   1217             }
   1218         }
   1219 
   1220         return retval;
   1221     }
   1222 
   1223     private boolean shouldUseCarModeUI() {
   1224         return mSystemStateProvider.isCarMode();
   1225     }
   1226 
   1227     /**
   1228      * Returns the type of InCallService described by the specified serviceInfo.
   1229      */
   1230     private int getInCallServiceType(ServiceInfo serviceInfo, PackageManager packageManager) {
   1231         // Verify that the InCallService requires the BIND_INCALL_SERVICE permission which
   1232         // enforces that only Telecom can bind to it.
   1233         boolean hasServiceBindPermission = serviceInfo.permission != null &&
   1234                 serviceInfo.permission.equals(
   1235                         Manifest.permission.BIND_INCALL_SERVICE);
   1236         if (!hasServiceBindPermission) {
   1237             Log.w(this, "InCallService does not require BIND_INCALL_SERVICE permission: " +
   1238                     serviceInfo.packageName);
   1239             return IN_CALL_SERVICE_TYPE_INVALID;
   1240         }
   1241 
   1242         if (mSystemInCallComponentName.getPackageName().equals(serviceInfo.packageName) &&
   1243                 mSystemInCallComponentName.getClassName().equals(serviceInfo.name)) {
   1244             return IN_CALL_SERVICE_TYPE_SYSTEM_UI;
   1245         }
   1246 
   1247         // Check to see if the service is a car-mode UI type by checking that it has the
   1248         // CONTROL_INCALL_EXPERIENCE (to verify it is a system app) and that it has the
   1249         // car-mode UI metadata.
   1250         boolean hasControlInCallPermission = packageManager.checkPermission(
   1251                 Manifest.permission.CONTROL_INCALL_EXPERIENCE,
   1252                 serviceInfo.packageName) == PackageManager.PERMISSION_GRANTED;
   1253         boolean isCarModeUIService = serviceInfo.metaData != null &&
   1254                 serviceInfo.metaData.getBoolean(
   1255                         TelecomManager.METADATA_IN_CALL_SERVICE_CAR_MODE_UI, false) &&
   1256                 hasControlInCallPermission;
   1257         if (isCarModeUIService) {
   1258             return IN_CALL_SERVICE_TYPE_CAR_MODE_UI;
   1259         }
   1260 
   1261         // Check to see that it is the default dialer package
   1262         boolean isDefaultDialerPackage = Objects.equals(serviceInfo.packageName,
   1263                 mDefaultDialerCache.getDefaultDialerApplication(
   1264                     mCallsManager.getCurrentUserHandle().getIdentifier()));
   1265         boolean isUIService = serviceInfo.metaData != null &&
   1266                 serviceInfo.metaData.getBoolean(
   1267                         TelecomManager.METADATA_IN_CALL_SERVICE_UI, false);
   1268         if (isDefaultDialerPackage && isUIService) {
   1269             return IN_CALL_SERVICE_TYPE_DIALER_UI;
   1270         }
   1271 
   1272         // Also allow any in-call service that has the control-experience permission (to ensure
   1273         // that it is a system app) and doesn't claim to show any UI.
   1274         if (hasControlInCallPermission && !isUIService) {
   1275             return IN_CALL_SERVICE_TYPE_NON_UI;
   1276         }
   1277 
   1278         // Anything else that remains, we will not bind to.
   1279         Log.i(this, "Skipping binding to %s:%s, control: %b, car-mode: %b, ui: %b",
   1280                 serviceInfo.packageName, serviceInfo.name, hasControlInCallPermission,
   1281                 isCarModeUIService, isUIService);
   1282         return IN_CALL_SERVICE_TYPE_INVALID;
   1283     }
   1284 
   1285     private void adjustServiceBindingsForEmergency() {
   1286         // The connected UI is not the system UI, so lets check if we should switch them
   1287         // if there exists an emergency number.
   1288         if (mCallsManager.hasEmergencyCall()) {
   1289             mInCallServiceConnection.setHasEmergency(true);
   1290         }
   1291     }
   1292 
   1293     /**
   1294      * Persists the {@link IInCallService} instance and starts the communication between
   1295      * this class and in-call app by sending the first update to in-call app. This method is
   1296      * called after a successful binding connection is established.
   1297      *
   1298      * @param info Info about the service, including its {@link ComponentName}.
   1299      * @param service The {@link IInCallService} implementation.
   1300      * @return True if we successfully connected.
   1301      */
   1302     private boolean onConnected(InCallServiceInfo info, IBinder service) {
   1303         Trace.beginSection("onConnected: " + info.getComponentName());
   1304         Log.i(this, "onConnected to %s", info.getComponentName());
   1305 
   1306         IInCallService inCallService = IInCallService.Stub.asInterface(service);
   1307         mInCallServices.put(info, inCallService);
   1308 
   1309         try {
   1310             inCallService.setInCallAdapter(
   1311                     new InCallAdapter(
   1312                             mCallsManager,
   1313                             mCallIdMapper,
   1314                             mLock,
   1315                             info.getComponentName().getPackageName()));
   1316         } catch (RemoteException e) {
   1317             Log.e(this, e, "Failed to set the in-call adapter.");
   1318             Trace.endSection();
   1319             return false;
   1320         }
   1321 
   1322         // Upon successful connection, send the state of the world to the service.
   1323         List<Call> calls = orderCallsWithChildrenFirst(mCallsManager.getCalls());
   1324         Log.i(this, "Adding %s calls to InCallService after onConnected: %s, including external " +
   1325                 "calls", calls.size(), info.getComponentName());
   1326         int numCallsSent = 0;
   1327         for (Call call : calls) {
   1328             try {
   1329                 if ((call.isSelfManaged() && !info.isSelfManagedCallsSupported()) ||
   1330                         (call.isExternalCall() && !info.isExternalCallsSupported())) {
   1331                     continue;
   1332                 }
   1333 
   1334                 // Only send the RTT call if it's a UI in-call service
   1335                 boolean includeRttCall = info.equals(mInCallServiceConnection.getInfo());
   1336 
   1337                 // Track the call if we don't already know about it.
   1338                 addCall(call);
   1339                 numCallsSent += 1;
   1340                 inCallService.addCall(ParcelableCallUtils.toParcelableCall(
   1341                         call,
   1342                         true /* includeVideoProvider */,
   1343                         mCallsManager.getPhoneAccountRegistrar(),
   1344                         info.isExternalCallsSupported(),
   1345                         includeRttCall));
   1346             } catch (RemoteException ignored) {
   1347             }
   1348         }
   1349         try {
   1350             inCallService.onCallAudioStateChanged(mCallsManager.getAudioState());
   1351             inCallService.onCanAddCallChanged(mCallsManager.canAddCall());
   1352         } catch (RemoteException ignored) {
   1353         }
   1354         Log.i(this, "%s calls sent to InCallService.", numCallsSent);
   1355         Trace.endSection();
   1356         return true;
   1357     }
   1358 
   1359     /**
   1360      * Cleans up an instance of in-call app after the service has been unbound.
   1361      *
   1362      * @param disconnectedInfo The {@link InCallServiceInfo} of the service which disconnected.
   1363      */
   1364     private void onDisconnected(InCallServiceInfo disconnectedInfo) {
   1365         Log.i(this, "onDisconnected from %s", disconnectedInfo.getComponentName());
   1366 
   1367         mInCallServices.remove(disconnectedInfo);
   1368     }
   1369 
   1370     /**
   1371      * Informs all {@link InCallService} instances of the updated call information.
   1372      *
   1373      * @param call The {@link Call}.
   1374      */
   1375     private void updateCall(Call call) {
   1376         updateCall(call, false /* videoProviderChanged */, false);
   1377     }
   1378 
   1379     /**
   1380      * Informs all {@link InCallService} instances of the updated call information.
   1381      *
   1382      * @param call The {@link Call}.
   1383      * @param videoProviderChanged {@code true} if the video provider changed, {@code false}
   1384      *      otherwise.
   1385      * @param rttInfoChanged {@code true} if any information about the RTT session changed,
   1386      * {@code false} otherwise.
   1387      */
   1388     private void updateCall(Call call, boolean videoProviderChanged, boolean rttInfoChanged) {
   1389         if (!mInCallServices.isEmpty()) {
   1390             Log.i(this, "Sending updateCall %s", call);
   1391             List<ComponentName> componentsUpdated = new ArrayList<>();
   1392             for (Map.Entry<InCallServiceInfo, IInCallService> entry : mInCallServices.entrySet()) {
   1393                 InCallServiceInfo info = entry.getKey();
   1394                 if (call.isExternalCall() && !info.isExternalCallsSupported()) {
   1395                     continue;
   1396                 }
   1397 
   1398                 if (call.isSelfManaged() && !info.isSelfManagedCallsSupported()) {
   1399                     continue;
   1400                 }
   1401 
   1402                 ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall(
   1403                         call,
   1404                         videoProviderChanged /* includeVideoProvider */,
   1405                         mCallsManager.getPhoneAccountRegistrar(),
   1406                         info.isExternalCallsSupported(),
   1407                         rttInfoChanged && info.equals(mInCallServiceConnection.getInfo()));
   1408                 ComponentName componentName = info.getComponentName();
   1409                 IInCallService inCallService = entry.getValue();
   1410                 componentsUpdated.add(componentName);
   1411 
   1412                 try {
   1413                     inCallService.updateCall(parcelableCall);
   1414                 } catch (RemoteException ignored) {
   1415                 }
   1416             }
   1417             Log.i(this, "Components updated: %s", componentsUpdated);
   1418         }
   1419     }
   1420 
   1421     /**
   1422      * Adds the call to the list of calls tracked by the {@link InCallController}.
   1423      * @param call The call to add.
   1424      */
   1425     private void addCall(Call call) {
   1426         if (mCallIdMapper.getCallId(call) == null) {
   1427             mCallIdMapper.addCall(call);
   1428             call.addListener(mCallListener);
   1429         }
   1430     }
   1431 
   1432     /**
   1433      * @return true if we are bound to the UI InCallService and it is connected.
   1434      */
   1435     private boolean isBoundAndConnectedToServices() {
   1436         return mInCallServiceConnection != null && mInCallServiceConnection.isConnected();
   1437     }
   1438 
   1439     /**
   1440      * Dumps the state of the {@link InCallController}.
   1441      *
   1442      * @param pw The {@code IndentingPrintWriter} to write the state to.
   1443      */
   1444     public void dump(IndentingPrintWriter pw) {
   1445         pw.println("mInCallServices (InCalls registered):");
   1446         pw.increaseIndent();
   1447         for (InCallServiceInfo info : mInCallServices.keySet()) {
   1448             pw.println(info);
   1449         }
   1450         pw.decreaseIndent();
   1451 
   1452         pw.println("ServiceConnections (InCalls bound):");
   1453         pw.increaseIndent();
   1454         if (mInCallServiceConnection != null) {
   1455             mInCallServiceConnection.dump(pw);
   1456         }
   1457         pw.decreaseIndent();
   1458     }
   1459 
   1460     public boolean doesConnectedDialerSupportRinging() {
   1461         String ringingPackage =  null;
   1462         if (mInCallUIComponentName != null) {
   1463             ringingPackage = mInCallUIComponentName.getPackageName().trim();
   1464         }
   1465 
   1466         if (TextUtils.isEmpty(ringingPackage)) {
   1467             // The current in-call UI returned nothing, so lets use the default dialer.
   1468             ringingPackage = DefaultDialerManager.getDefaultDialerApplication(
   1469                     mContext, UserHandle.USER_CURRENT);
   1470         }
   1471         if (TextUtils.isEmpty(ringingPackage)) {
   1472             return false;
   1473         }
   1474 
   1475         Intent intent = new Intent(InCallService.SERVICE_INTERFACE)
   1476             .setPackage(ringingPackage);
   1477         List<ResolveInfo> entries = mContext.getPackageManager().queryIntentServicesAsUser(
   1478                 intent, PackageManager.GET_META_DATA,
   1479                 mCallsManager.getCurrentUserHandle().getIdentifier());
   1480         if (entries.isEmpty()) {
   1481             return false;
   1482         }
   1483 
   1484         ResolveInfo info = entries.get(0);
   1485         if (info.serviceInfo == null || info.serviceInfo.metaData == null) {
   1486             return false;
   1487         }
   1488 
   1489         return info.serviceInfo.metaData
   1490                 .getBoolean(TelecomManager.METADATA_IN_CALL_SERVICE_RINGING, false);
   1491     }
   1492 
   1493     private List<Call> orderCallsWithChildrenFirst(Collection<Call> calls) {
   1494         LinkedList<Call> parentCalls = new LinkedList<>();
   1495         LinkedList<Call> childCalls = new LinkedList<>();
   1496         for (Call call : calls) {
   1497             if (call.getChildCalls().size() > 0) {
   1498                 parentCalls.add(call);
   1499             } else {
   1500                 childCalls.add(call);
   1501             }
   1502         }
   1503         childCalls.addAll(parentCalls);
   1504         return childCalls;
   1505     }
   1506 }
   1507