1 /* 2 * Copyright (C) 2019 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.car.dialer.telecom; 18 19 import android.content.Context; 20 import android.content.Intent; 21 import android.telecom.Call; 22 23 import androidx.annotation.MainThread; 24 25 import com.android.car.dialer.Constants; 26 import com.android.car.dialer.R; 27 import com.android.car.dialer.log.L; 28 import com.android.car.dialer.notification.InCallNotificationController; 29 import com.android.car.dialer.ui.activecall.InCallActivity; 30 import com.android.car.dialer.ui.activecall.InCallViewModel; 31 32 import java.util.ArrayList; 33 34 /** 35 * Routes a call to different path depending on its state. If there is any {@link 36 * InCallServiceImpl.ActiveCallListChangedCallback} that already handles the call, i.e. the {@link 37 * InCallViewModel} that actively updates the in call page, then we don't show HUN for the ringing 38 * call or attempt to start the in call page again. 39 */ 40 class InCallRouter { 41 42 private static final String TAG = "CD.InCallRouter"; 43 44 private final Context mContext; 45 private final InCallNotificationController mInCallNotificationController; 46 private final ArrayList<InCallServiceImpl.ActiveCallListChangedCallback> 47 mActiveCallListChangedCallbacks = new ArrayList<>(); 48 private final ProjectionCallHandler mProjectionCallHandler; 49 50 private final boolean mShowFullscreenIncallUi; 51 InCallRouter(Context context)52 InCallRouter(Context context) { 53 mContext = context; 54 mShowFullscreenIncallUi = context.getResources().getBoolean( 55 R.bool.config_show_fullscreen_incall_ui); 56 mInCallNotificationController = InCallNotificationController.get(); 57 mProjectionCallHandler = new ProjectionCallHandler(context); 58 } 59 start()60 void start() { 61 mProjectionCallHandler.start(); 62 mActiveCallListChangedCallbacks.add(mProjectionCallHandler); 63 } 64 stop()65 void stop() { 66 mActiveCallListChangedCallbacks.remove(mProjectionCallHandler); 67 mProjectionCallHandler.stop(); 68 } 69 70 /** 71 * Routes the added call to the correct path: 72 * <ul> 73 * <li> First dispatches it to the {@link InCallServiceImpl.ActiveCallListChangedCallback}s. 74 * <li> If the ringing call is not handled by callbacks, it will show a HUN. 75 * <li> If the call is in other state and not handled by callbacks, it will try to launch the in 76 * call page. 77 */ onCallAdded(Call call)78 void onCallAdded(Call call) { 79 boolean isHandled = routeToActiveCallListChangedCallback(call); 80 if (isHandled) { 81 return; 82 } 83 84 int state = call.getState(); 85 if (state == Call.STATE_RINGING) { 86 routeToNotification(call); 87 } else if (state != Call.STATE_DISCONNECTED) { 88 // Don't launch the in call page if state is disconnected. 89 // Otherwise, the InCallActivity finishes right after onCreate() and flashes. 90 routeToInCallPage(false); 91 } 92 } 93 94 /** 95 * Called by {@link InCallServiceImpl#onCallRemoved(Call)}. It notifies the {@link 96 * InCallServiceImpl.ActiveCallListChangedCallback}s to update the active call list. 97 */ onCallRemoved(Call call)98 void onCallRemoved(Call call) { 99 for (InCallServiceImpl.ActiveCallListChangedCallback callback : 100 mActiveCallListChangedCallbacks) { 101 callback.onTelecomCallRemoved(call); 102 } 103 } 104 105 @MainThread registerActiveCallListChangedCallback( InCallServiceImpl.ActiveCallListChangedCallback callback)106 void registerActiveCallListChangedCallback( 107 InCallServiceImpl.ActiveCallListChangedCallback callback) { 108 mActiveCallListChangedCallbacks.add(callback); 109 } 110 111 @MainThread unregisterActiveCallHandler(InCallServiceImpl.ActiveCallListChangedCallback callback)112 void unregisterActiveCallHandler(InCallServiceImpl.ActiveCallListChangedCallback callback) { 113 mActiveCallListChangedCallbacks.remove(callback); 114 } 115 116 /** 117 * Dispatches the call to {@link InCallServiceImpl.ActiveCallListChangedCallback}. 118 */ routeToActiveCallListChangedCallback(Call call)119 private boolean routeToActiveCallListChangedCallback(Call call) { 120 boolean isHandled = false; 121 for (InCallServiceImpl.ActiveCallListChangedCallback callback : 122 mActiveCallListChangedCallbacks) { 123 if (callback.onTelecomCallAdded(call)) { 124 isHandled = true; 125 } 126 } 127 128 return isHandled; 129 } 130 131 /** 132 * Presents the ringing call in HUN. 133 */ routeToNotification(Call call)134 private void routeToNotification(Call call) { 135 mInCallNotificationController.showInCallNotification(call); 136 call.registerCallback(new Call.Callback() { 137 @Override 138 public void onStateChanged(Call call, int state) { 139 L.d(TAG, "Ringing call state changed to %d", state); 140 if (call.getState() != Call.STATE_DISCONNECTED) { 141 // Don't launch the in call page if state is disconnected. Otherwise, the 142 // InCallActivity finishes right after onCreate() and flashes. 143 routeToInCallPage(false); 144 } 145 mInCallNotificationController.cancelInCallNotification(call); 146 call.unregisterCallback(this); 147 } 148 }); 149 } 150 151 /** 152 * Launches {@link InCallActivity} and presents the on going call in the in call page. 153 */ routeToInCallPage(boolean showDialpad)154 void routeToInCallPage(boolean showDialpad) { 155 // It has been configured not to show the fullscreen incall ui. 156 if (!mShowFullscreenIncallUi) { 157 return; 158 } 159 160 Intent launchIntent = new Intent(mContext, InCallActivity.class); 161 launchIntent.putExtra(Constants.Intents.EXTRA_SHOW_INCOMING_CALL, showDialpad); 162 mContext.startActivity(launchIntent); 163 } 164 } 165