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.content.SharedPreferences; 22 import android.telecom.Call; 23 24 import androidx.annotation.MainThread; 25 26 import com.android.car.dialer.Constants; 27 import com.android.car.dialer.R; 28 import com.android.car.dialer.log.L; 29 import com.android.car.dialer.notification.InCallNotificationController; 30 import com.android.car.dialer.ui.activecall.InCallActivity; 31 import com.android.car.dialer.ui.activecall.InCallViewModel; 32 33 import java.util.ArrayList; 34 35 import javax.inject.Inject; 36 37 import dagger.hilt.android.qualifiers.ApplicationContext; 38 import dagger.hilt.android.scopes.ServiceScoped; 39 40 /** 41 * Routes a call to different path depending on its state. If there is any {@link 42 * InCallServiceImpl.ActiveCallListChangedCallback} that already handles the call, i.e. the {@link 43 * InCallViewModel} that actively updates the in call page, then we don't show HUN for the ringing 44 * call or attempt to start the in call page again. 45 */ 46 @ServiceScoped 47 class InCallRouter { 48 49 private static final String TAG = "CD.InCallRouter"; 50 51 private final Context mContext; 52 private final SharedPreferences mSharedPreferences; 53 private final InCallNotificationController mInCallNotificationController; 54 private final ArrayList<InCallServiceImpl.ActiveCallListChangedCallback> 55 mActiveCallListChangedCallbacks = new ArrayList<>(); 56 private final ProjectionCallHandler mProjectionCallHandler; 57 58 @Inject InCallRouter( @pplicationContext Context context, SharedPreferences sharedPreferences, InCallNotificationController inCallNotificationController, ProjectionCallHandler projectionCallHandler)59 InCallRouter( 60 @ApplicationContext Context context, 61 SharedPreferences sharedPreferences, 62 InCallNotificationController inCallNotificationController, 63 ProjectionCallHandler projectionCallHandler) { 64 mContext = context; 65 mSharedPreferences = sharedPreferences; 66 mInCallNotificationController = inCallNotificationController; 67 mProjectionCallHandler = projectionCallHandler; 68 } 69 start()70 void start() { 71 mProjectionCallHandler.start(); 72 mActiveCallListChangedCallbacks.add(mProjectionCallHandler); 73 } 74 stop()75 void stop() { 76 mActiveCallListChangedCallbacks.remove(mProjectionCallHandler); 77 mProjectionCallHandler.stop(); 78 } 79 80 /** 81 * Routes the added call to the correct path: 82 * <ul> 83 * <li> First dispatches it to the {@link InCallServiceImpl.ActiveCallListChangedCallback}s. 84 * <li> If the ringing call is not handled by callbacks, it will show a HUN. 85 * <li> If the call is in other state and not handled by callbacks, it will try to launch the in 86 * call page. 87 */ onCallAdded(Call call)88 void onCallAdded(Call call) { 89 boolean isHandled = routeToActiveCallListChangedCallback(call); 90 if (isHandled) { 91 return; 92 } 93 94 int state = call.getState(); 95 if (state == Call.STATE_RINGING) { 96 routeToNotification(call); 97 // Otherwise, no operations. Incoming call will be displayed outside of Dialer app 98 // such as cluster. 99 } else if (state != Call.STATE_DISCONNECTED) { 100 // Don't launch the in call page if state is disconnected. 101 // Otherwise, the InCallActivity finishes right after onCreate() and flashes. 102 routeToFullScreenIncomingCallPage(false); 103 } 104 } 105 106 /** 107 * Called by {@link InCallServiceImpl#onCallRemoved(Call)}. It notifies the {@link 108 * InCallServiceImpl.ActiveCallListChangedCallback}s to update the active call list. 109 */ onCallRemoved(Call call)110 void onCallRemoved(Call call) { 111 for (InCallServiceImpl.ActiveCallListChangedCallback callback : 112 mActiveCallListChangedCallbacks) { 113 callback.onTelecomCallRemoved(call); 114 } 115 } 116 117 @MainThread registerActiveCallListChangedCallback( InCallServiceImpl.ActiveCallListChangedCallback callback)118 void registerActiveCallListChangedCallback( 119 InCallServiceImpl.ActiveCallListChangedCallback callback) { 120 mActiveCallListChangedCallbacks.add(callback); 121 } 122 123 @MainThread unregisterActiveCallHandler(InCallServiceImpl.ActiveCallListChangedCallback callback)124 void unregisterActiveCallHandler(InCallServiceImpl.ActiveCallListChangedCallback callback) { 125 mActiveCallListChangedCallbacks.remove(callback); 126 } 127 128 /** 129 * Dispatches the call to {@link InCallServiceImpl.ActiveCallListChangedCallback}. 130 */ routeToActiveCallListChangedCallback(Call call)131 private boolean routeToActiveCallListChangedCallback(Call call) { 132 boolean isHandled = false; 133 for (InCallServiceImpl.ActiveCallListChangedCallback callback : 134 mActiveCallListChangedCallbacks) { 135 if (callback.onTelecomCallAdded(call)) { 136 isHandled = true; 137 } 138 } 139 140 return isHandled; 141 } 142 143 /** 144 * Presents the ringing call in HUN. 145 */ routeToNotification(Call call)146 private void routeToNotification(Call call) { 147 if (shouldShowIncomingCallHun()) { 148 mInCallNotificationController.showInCallNotification(call); 149 } 150 call.registerCallback(new Call.Callback() { 151 @Override 152 public void onStateChanged(Call call, int state) { 153 L.d(TAG, "Ringing call state changed to %d", state); 154 if (call.getState() != Call.STATE_DISCONNECTED) { 155 // Don't launch the in call page if state is disconnected. Otherwise, the 156 // InCallActivity finishes right after onCreate() and flashes. 157 routeToFullScreenIncomingCallPage(false); 158 } 159 mInCallNotificationController.cancelInCallNotification(call); 160 call.unregisterCallback(this); 161 } 162 }); 163 } 164 165 /** 166 * Launches {@link InCallActivity} and presents the on going call in the in call page. 167 */ routeToFullScreenIncomingCallPage(boolean showDialpad)168 void routeToFullScreenIncomingCallPage(boolean showDialpad) { 169 // It has been configured not to show the fullscreen incall ui. 170 if (!shouldShowFullScreenUi()) { 171 return; 172 } 173 174 Intent launchIntent = new Intent(mContext, InCallActivity.class); 175 launchIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 176 launchIntent.putExtra(Constants.Intents.EXTRA_SHOW_DIALPAD, showDialpad); 177 mContext.startActivity(launchIntent); 178 } 179 shouldShowIncomingCallHun()180 private boolean shouldShowIncomingCallHun() { 181 boolean shouldSuppressHunByDefault = 182 mContext.getResources().getBoolean(R.bool.config_should_suppress_incoming_call_hun); 183 return !mSharedPreferences 184 .getBoolean(mContext.getString(R.string.pref_no_incoming_call_hun_key), 185 shouldSuppressHunByDefault); 186 } 187 shouldShowFullScreenUi()188 private boolean shouldShowFullScreenUi() { 189 boolean shouldShowFullScreenUiByDefault = 190 mContext.getResources().getBoolean(R.bool.config_show_fullscreen_incall_ui); 191 return mSharedPreferences 192 .getBoolean(mContext.getString(R.string.pref_show_fullscreen_active_call_ui_key), 193 shouldShowFullScreenUiByDefault); 194 } 195 } 196