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.content.BroadcastReceiver; 20import android.content.Context; 21import android.content.Intent; 22import android.content.IntentFilter; 23import android.net.Uri; 24import android.os.AsyncResult; 25import android.os.Bundle; 26import android.os.Handler; 27import android.os.Message; 28import android.telecom.PhoneAccount; 29import android.telecom.TelecomManager; 30import android.telephony.TelephonyManager; 31import android.text.TextUtils; 32 33import com.android.internal.telephony.Call; 34import com.android.internal.telephony.Connection; 35import com.android.internal.telephony.Phone; 36import com.android.internal.telephony.PhoneConstants; 37import com.android.internal.telephony.PhoneProxy; 38import com.android.internal.telephony.TelephonyIntents; 39import com.android.internal.telephony.cdma.CdmaCallWaitingNotification; 40import com.android.phone.PhoneUtils; 41 42import com.google.common.base.Preconditions; 43 44import java.util.Objects; 45 46/** 47 * Listens to incoming-call events from the associated phone object and notifies Telecom upon each 48 * occurence. One instance of these exists for each of the telephony-based call services. 49 */ 50final class PstnIncomingCallNotifier { 51 /** New ringing connection event code. */ 52 private static final int EVENT_NEW_RINGING_CONNECTION = 100; 53 private static final int EVENT_CDMA_CALL_WAITING = 101; 54 private static final int EVENT_UNKNOWN_CONNECTION = 102; 55 56 /** The phone proxy object to listen to. */ 57 private final PhoneProxy mPhoneProxy; 58 59 /** 60 * The base phone implementation behind phone proxy. The underlying phone implementation can 61 * change underneath when the radio technology changes. We listen for these events and update 62 * the base phone in this variable. We save it so that when the change happens, we can 63 * unregister from the events we were listening to. 64 */ 65 private Phone mPhoneBase; 66 67 /** 68 * Used to listen to events from {@link #mPhoneBase}. 69 */ 70 private final Handler mHandler = new Handler() { 71 @Override 72 public void handleMessage(Message msg) { 73 switch(msg.what) { 74 case EVENT_NEW_RINGING_CONNECTION: 75 handleNewRingingConnection((AsyncResult) msg.obj); 76 break; 77 case EVENT_CDMA_CALL_WAITING: 78 handleCdmaCallWaiting((AsyncResult) msg.obj); 79 break; 80 case EVENT_UNKNOWN_CONNECTION: 81 handleNewUnknownConnection((AsyncResult) msg.obj); 82 break; 83 default: 84 break; 85 } 86 } 87 }; 88 89 /** 90 * Receiver to listen for radio technology change events. 91 */ 92 private final BroadcastReceiver mRATReceiver = new BroadcastReceiver() { 93 @Override 94 public void onReceive(Context context, Intent intent) { 95 String action = intent.getAction(); 96 if (TelephonyIntents.ACTION_RADIO_TECHNOLOGY_CHANGED.equals(action)) { 97 String newPhone = intent.getStringExtra(PhoneConstants.PHONE_NAME_KEY); 98 Log.d(this, "Radio technology switched. Now %s is active.", newPhone); 99 100 registerForNotifications(); 101 } 102 } 103 }; 104 105 /** 106 * Persists the specified parameters and starts listening to phone events. 107 * 108 * @param phoneProxy The phone object for listening to incoming calls. 109 */ 110 PstnIncomingCallNotifier(PhoneProxy phoneProxy) { 111 Preconditions.checkNotNull(phoneProxy); 112 113 mPhoneProxy = phoneProxy; 114 115 registerForNotifications(); 116 117 IntentFilter intentFilter = 118 new IntentFilter(TelephonyIntents.ACTION_RADIO_TECHNOLOGY_CHANGED); 119 mPhoneProxy.getContext().registerReceiver(mRATReceiver, intentFilter); 120 } 121 122 void teardown() { 123 unregisterForNotifications(); 124 mPhoneProxy.getContext().unregisterReceiver(mRATReceiver); 125 } 126 127 /** 128 * Register for notifications from the base phone. 129 * TODO: We should only need to interact with the phoneproxy directly. However, 130 * since the phoneproxy only interacts directly with CallManager we either listen to callmanager 131 * or we have to poke into the proxy like this. Neither is desirable. It would be better if 132 * this class and callManager could register generically with the phone proxy instead and get 133 * radio techonology changes directly. Or better yet, just register for the notifications 134 * directly with phone proxy and never worry about the technology changes. This requires a 135 * change in opt/telephony code. 136 */ 137 private void registerForNotifications() { 138 Phone newPhone = mPhoneProxy.getActivePhone(); 139 if (newPhone != mPhoneBase) { 140 unregisterForNotifications(); 141 142 if (newPhone != null) { 143 Log.i(this, "Registering: %s", newPhone); 144 mPhoneBase = newPhone; 145 mPhoneBase.registerForNewRingingConnection( 146 mHandler, EVENT_NEW_RINGING_CONNECTION, null); 147 mPhoneBase.registerForCallWaiting( 148 mHandler, EVENT_CDMA_CALL_WAITING, null); 149 mPhoneBase.registerForUnknownConnection(mHandler, EVENT_UNKNOWN_CONNECTION, 150 null); 151 } 152 } 153 } 154 155 private void unregisterForNotifications() { 156 if (mPhoneBase != null) { 157 Log.i(this, "Unregistering: %s", mPhoneBase); 158 mPhoneBase.unregisterForNewRingingConnection(mHandler); 159 mPhoneBase.unregisterForCallWaiting(mHandler); 160 mPhoneBase.unregisterForUnknownConnection(mHandler); 161 } 162 } 163 164 /** 165 * Verifies the incoming call and triggers sending the incoming-call intent to Telecom. 166 * 167 * @param asyncResult The result object from the new ringing event. 168 */ 169 private void handleNewRingingConnection(AsyncResult asyncResult) { 170 Log.d(this, "handleNewRingingConnection"); 171 Connection connection = (Connection) asyncResult.result; 172 if (connection != null) { 173 Call call = connection.getCall(); 174 175 // Final verification of the ringing state before sending the intent to Telecom. 176 if (call != null && call.getState().isRinging()) { 177 sendIncomingCallIntent(connection); 178 } 179 } 180 } 181 182 private void handleCdmaCallWaiting(AsyncResult asyncResult) { 183 Log.d(this, "handleCdmaCallWaiting"); 184 CdmaCallWaitingNotification ccwi = (CdmaCallWaitingNotification) asyncResult.result; 185 Call call = mPhoneBase.getRingingCall(); 186 if (call.getState() == Call.State.WAITING) { 187 Connection connection = call.getLatestConnection(); 188 if (connection != null) { 189 String number = connection.getAddress(); 190 if (number != null && Objects.equals(number, ccwi.number)) { 191 sendIncomingCallIntent(connection); 192 } 193 } 194 } 195 } 196 197 private void handleNewUnknownConnection(AsyncResult asyncResult) { 198 Log.i(this, "handleNewUnknownConnection"); 199 if (!(asyncResult.result instanceof Connection)) { 200 Log.w(this, "handleNewUnknownConnection called with non-Connection object"); 201 return; 202 } 203 Connection connection = (Connection) asyncResult.result; 204 if (connection != null) { 205 Call call = connection.getCall(); 206 if (call != null && call.getState().isAlive()) { 207 addNewUnknownCall(connection); 208 } 209 } 210 } 211 212 private void addNewUnknownCall(Connection connection) { 213 Log.i(this, "addNewUnknownCall, connection is: %s", connection); 214 if (!maybeSwapAnyWithUnknownConnection(connection)) { 215 Log.i(this, "determined new connection is: %s", connection); 216 Bundle extras = null; 217 if (connection.getNumberPresentation() == TelecomManager.PRESENTATION_ALLOWED && 218 !TextUtils.isEmpty(connection.getAddress())) { 219 extras = new Bundle(); 220 Uri uri = Uri.fromParts(PhoneAccount.SCHEME_TEL, connection.getAddress(), null); 221 extras.putParcelable(TelecomManager.EXTRA_UNKNOWN_CALL_HANDLE, uri); 222 } 223 TelecomManager.from(mPhoneProxy.getContext()).addNewUnknownCall( 224 PhoneUtils.makePstnPhoneAccountHandle(mPhoneProxy), extras); 225 } else { 226 Log.i(this, "swapped an old connection, new one is: %s", connection); 227 } 228 } 229 230 /** 231 * Sends the incoming call intent to telecom. 232 */ 233 private void sendIncomingCallIntent(Connection connection) { 234 Bundle extras = null; 235 if (connection.getNumberPresentation() == TelecomManager.PRESENTATION_ALLOWED && 236 !TextUtils.isEmpty(connection.getAddress())) { 237 extras = new Bundle(); 238 Uri uri = Uri.fromParts(PhoneAccount.SCHEME_TEL, connection.getAddress(), null); 239 extras.putParcelable(TelephonyManager.EXTRA_INCOMING_NUMBER, uri); 240 } 241 TelecomManager.from(mPhoneProxy.getContext()).addNewIncomingCall( 242 PhoneUtils.makePstnPhoneAccountHandle(mPhoneProxy), extras); 243 } 244 245 /** 246 * Define cait.Connection := com.android.internal.telephony.Connection 247 * 248 * Given a previously unknown cait.Connection, check to see if it's likely a replacement for 249 * another cait.Connnection we already know about. If it is, then we silently swap it out 250 * underneath within the relevant {@link TelephonyConnection}, using 251 * {@link TelephonyConnection#setOriginalConnection(Connection)}, and return {@code true}. 252 * Otherwise, we return {@code false}. 253 */ 254 private boolean maybeSwapAnyWithUnknownConnection(Connection unknown) { 255 if (!unknown.isIncoming()) { 256 TelecomAccountRegistry registry = TelecomAccountRegistry.getInstance(null); 257 if (registry != null) { 258 TelephonyConnectionService service = registry.getTelephonyConnectionService(); 259 if (service != null) { 260 for (android.telecom.Connection telephonyConnection : service 261 .getAllConnections()) { 262 if (maybeSwapWithUnknownConnection( 263 (TelephonyConnection) telephonyConnection, 264 unknown)) { 265 return true; 266 } 267 } 268 } 269 } 270 } 271 return false; 272 } 273 274 private boolean maybeSwapWithUnknownConnection( 275 TelephonyConnection telephonyConnection, 276 Connection unknown) { 277 Connection original = telephonyConnection.getOriginalConnection(); 278 if (original != null && !original.isIncoming() 279 && Objects.equals(original.getAddress(), unknown.getAddress())) { 280 telephonyConnection.setOriginalConnection(unknown); 281 return true; 282 } 283 return false; 284 } 285} 286