blob: 6028f6f3b7e3922a8625bbc9a46f5bbeb5a831a1 [file] [log] [blame]
Brad Ebinger936a7d12018-01-16 09:36:56 -08001/*
2 * Copyright (C) 2017 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.ims;
18
19import android.annotation.Nullable;
20import android.content.Context;
21import android.net.Uri;
22import android.os.IBinder;
23import android.os.Message;
24import android.os.RemoteException;
25import android.telephony.Rlog;
26import android.telephony.TelephonyManager;
Brad Ebingered690772018-01-23 13:41:32 -080027import android.telephony.ims.ImsCallProfile;
28import android.telephony.ims.ImsReasonInfo;
Brad Ebinger936a7d12018-01-16 09:36:56 -080029import android.telephony.ims.aidl.IImsConfig;
30import android.telephony.ims.aidl.IImsMmTelFeature;
31import android.telephony.ims.aidl.IImsRegistration;
32import android.telephony.ims.aidl.IImsRegistrationCallback;
33import android.telephony.ims.aidl.IImsSmsListener;
34import android.telephony.ims.feature.CapabilityChangeRequest;
35import android.telephony.ims.feature.ImsFeature;
36import android.telephony.ims.feature.MmTelFeature;
37import android.telephony.ims.stub.ImsRegistrationImplBase;
38import android.telephony.ims.stub.ImsSmsImplBase;
39import android.util.Log;
40
41import com.android.ims.internal.IImsCallSession;
42import com.android.ims.internal.IImsEcbm;
43import com.android.ims.internal.IImsMultiEndpoint;
44import com.android.ims.internal.IImsServiceFeatureCallback;
45import com.android.ims.internal.IImsUt;
46
47import java.util.HashSet;
48import java.util.Set;
49
50/**
51 * A container of the IImsServiceController binder, which implements all of the ImsFeatures that
52 * the platform currently supports: MMTel and RCS.
53 * @hide
54 */
55
56public class MmTelFeatureConnection {
57
58 protected static final String TAG = "MmTelFeatureConnection";
59 protected final int mSlotId;
60 protected IBinder mBinder;
61 private Context mContext;
62
63 // Start by assuming the proxy is available for usage.
64 private volatile boolean mIsAvailable = true;
65 // ImsFeature Status from the ImsService. Cached.
66 private Integer mFeatureStateCached = null;
67 private IFeatureUpdate mStatusCallback;
68 private final Object mLock = new Object();
69
70 private MmTelFeature.Listener mMmTelFeatureListener;
71
72 private abstract class CallbackAdapterManager<T> {
73 private static final String TAG = "CallbackAdapterManager";
74
75 protected final Set<T> mLocalCallbacks = new HashSet<>();
76 private boolean mHasConnected = false;
77
78 public void addCallback(T localCallback) throws RemoteException {
79 // We only one one binding to the ImsService per process.
80 // Store any more locally.
81 synchronized (mLock) {
82 if(!mHasConnected) {
83 // throws a RemoteException if a connection can not be established.
84 if(createConnection()) {
85 mHasConnected = true;
86 } else {
87 throw new RemoteException("Can not create connection!");
88 }
89 }
90 Log.i(TAG, "Local callback added: " + localCallback);
91 mLocalCallbacks.add(localCallback);
92 }
93 }
94
95 public void removeCallback(T localCallback) {
96 // We only maintain one binding to the ImsService per process.
97 synchronized (mLock) {
98 Log.i(TAG, "Local callback removed: " + localCallback);
99 mLocalCallbacks.remove(localCallback);
100 // If we have removed all local callbacks, remove callback to ImsService.
101 if(mHasConnected) {
102 if (mLocalCallbacks.isEmpty()) {
103 removeConnection();
104 mHasConnected = false;
105 }
106 }
107 }
108 }
109
110 public void close() {
111 synchronized (mLock) {
112 if (mHasConnected) {
113 removeConnection();
114 // Still mark the connection as disconnected, even if this fails.
115 mHasConnected = false;
116 }
117 Log.i(TAG, "Closing connection and clearing callbacks");
118 mLocalCallbacks.clear();
119 }
120 }
121
122 abstract boolean createConnection() throws RemoteException;
123
124 abstract void removeConnection();
125 }
126 private ImsRegistrationCallbackAdapter mRegistrationCallbackManager
127 = new ImsRegistrationCallbackAdapter();
128 private class ImsRegistrationCallbackAdapter
129 extends CallbackAdapterManager<ImsRegistrationImplBase.Callback> {
130 private final RegistrationCallbackAdapter mRegistrationCallbackAdapter
131 = new RegistrationCallbackAdapter();
132
133 private class RegistrationCallbackAdapter extends IImsRegistrationCallback.Stub {
134
135 @Override
136 public void onRegistered(int imsRadioTech) {
137 Log.i(TAG, "onRegistered ::");
138
139 synchronized (mLock) {
140 mLocalCallbacks.forEach(l -> l.onRegistered(imsRadioTech));
141 }
142 }
143
144 @Override
145 public void onRegistering(int imsRadioTech) {
146 Log.i(TAG, "onRegistering ::");
147
148 synchronized (mLock) {
149 mLocalCallbacks.forEach(l -> l.onRegistering(imsRadioTech));
150 }
151 }
152
153 @Override
154 public void onDeregistered(ImsReasonInfo imsReasonInfo) {
155 Log.i(TAG, "onDeregistered ::");
156
157 synchronized (mLock) {
158 mLocalCallbacks.forEach(l -> l.onDeregistered(imsReasonInfo));
159 }
160 }
161
162 @Override
163 public void onTechnologyChangeFailed(int targetRadioTech, ImsReasonInfo imsReasonInfo) {
164 Log.i(TAG, "onTechnologyChangeFailed :: targetAccessTech=" + targetRadioTech +
165 ", imsReasonInfo=" + imsReasonInfo);
166
167 synchronized (mLock) {
168 mLocalCallbacks.forEach(l -> l.onTechnologyChangeFailed(targetRadioTech,
169 imsReasonInfo));
170 }
171 }
172
173 @Override
174 public void onSubscriberAssociatedUriChanged(Uri[] uris) {
175 Log.i(TAG, "onSubscriberAssociatedUriChanged");
176 synchronized (mLock) {
177 mLocalCallbacks.forEach(l -> l.onSubscriberAssociatedUriChanged(uris));
178 }
179 }
180 }
181
182 @Override
183 boolean createConnection() throws RemoteException {
184 IImsRegistration imsRegistration = getRegistration();
185 if (imsRegistration != null) {
186 getRegistration().addRegistrationCallback(mRegistrationCallbackAdapter);
187 return true;
188 } else {
189 Log.e(TAG, "ImsRegistration is null");
190 return false;
191 }
192 }
193
194 @Override
195 void removeConnection() {
196 IImsRegistration imsRegistration = getRegistration();
197 if (imsRegistration != null) {
198 try {
Brad Ebingerd1b1a3c2018-03-08 11:37:35 -0800199 getRegistration().removeRegistrationCallback(mRegistrationCallbackAdapter);
Brad Ebinger936a7d12018-01-16 09:36:56 -0800200 } catch (RemoteException e) {
201 Log.w(TAG, "removeConnection: couldn't remove registration callback");
202 }
203 } else {
204 Log.e(TAG, "ImsRegistration is null");
205 }
206 }
207 }
208
209 private final CapabilityCallbackManager mCapabilityCallbackManager
210 = new CapabilityCallbackManager();
211 private class CapabilityCallbackManager
212 extends CallbackAdapterManager<ImsFeature.CapabilityCallback> {
213 private final CapabilityCallbackAdapter mCallbackAdapter = new CapabilityCallbackAdapter();
214
215 private class CapabilityCallbackAdapter extends ImsFeature.CapabilityCallback {
216 // Called when the Capabilities Status on this connection have changed.
217 @Override
218 public void onCapabilitiesStatusChanged(ImsFeature.Capabilities config) {
219 synchronized (mLock) {
220 mLocalCallbacks.forEach(
221 callback -> callback.onCapabilitiesStatusChanged(config));
222 }
223 }
224 }
225
226 @Override
227 boolean createConnection() throws RemoteException {
228 IImsMmTelFeature binder = getServiceInterface(mBinder);
229 if (binder != null) {
230 binder.addCapabilityCallback(mCallbackAdapter);
231 return true;
232 } else {
233 Log.w(TAG, "create: Couldn't get IImsMmTelFeature binder");
234 return false;
235 }
236 }
237
238 @Override
239 void removeConnection() {
240 IImsMmTelFeature binder = getServiceInterface(mBinder);
241 if (binder != null) {
242 try {
243 binder.removeCapabilityCallback(mCallbackAdapter);
244 } catch (RemoteException e) {
245 Log.w(TAG, "remove: IImsMmTelFeature binder is dead");
246 }
247 } else {
248 Log.w(TAG, "remove: Couldn't get IImsMmTelFeature binder");
249 }
250 }
251 }
252
253
254 public static MmTelFeatureConnection create(Context context , int slotId) {
255 MmTelFeatureConnection serviceProxy = new MmTelFeatureConnection(context, slotId);
256
257 TelephonyManager tm = getTelephonyManager(context);
258 if (tm == null) {
259 Rlog.w(TAG, "create: TelephonyManager is null!");
260 // Binder can be unset in this case because it will be torn down/recreated as part of
261 // a retry mechanism until the serviceProxy binder is set successfully.
262 return serviceProxy;
263 }
264
265 IImsMmTelFeature binder = tm.getImsMmTelFeatureAndListen(slotId,
266 serviceProxy.getListener());
267 if (binder != null) {
268 serviceProxy.setBinder(binder.asBinder());
269 // Trigger the cache to be updated for feature status.
270 serviceProxy.getFeatureState();
271 } else {
272 Rlog.w(TAG, "create: binder is null! Slot Id: " + slotId);
273 }
274 return serviceProxy;
275 }
276
277 public static TelephonyManager getTelephonyManager(Context context) {
278 return (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
279 }
280
281 public interface IFeatureUpdate {
282 /**
283 * Called when the ImsFeature has changed its state. Use
284 * {@link ImsFeature#getFeatureState()} to get the new state.
285 */
286 void notifyStateChanged();
287
288 /**
289 * Called when the ImsFeature has become unavailable due to the binder switching or app
290 * crashing. A new ImsServiceProxy should be requested for that feature.
291 */
292 void notifyUnavailable();
293 }
294
295 private final IImsServiceFeatureCallback mListenerBinder =
296 new IImsServiceFeatureCallback.Stub() {
297
298 @Override
299 public void imsFeatureCreated(int slotId, int feature) throws RemoteException {
300 // The feature has been re-enabled. This may happen when the service crashes.
301 synchronized (mLock) {
302 if (!mIsAvailable && mSlotId == slotId && feature == ImsFeature.FEATURE_MMTEL) {
303 Log.i(TAG, "Feature enabled on slotId: " + slotId + " for feature: " +
304 feature);
305 mIsAvailable = true;
306 }
307 }
308 }
309
310 @Override
311 public void imsFeatureRemoved(int slotId, int feature) throws RemoteException {
312 synchronized (mLock) {
313 if (mIsAvailable && mSlotId == slotId && feature == ImsFeature.FEATURE_MMTEL) {
314 Log.i(TAG, "Feature disabled on slotId: " + slotId + " for feature: " +
315 feature);
316 mIsAvailable = false;
317 if (mStatusCallback != null) {
318 mStatusCallback.notifyUnavailable();
319 }
320 }
321 }
322 }
323
324 @Override
325 public void imsStatusChanged(int slotId, int feature, int status) throws RemoteException {
326 synchronized (mLock) {
327 Log.i(TAG, "imsStatusChanged: slot: " + slotId + " feature: " + feature +
328 " status: " + status);
329 if (mSlotId == slotId && feature == ImsFeature.FEATURE_MMTEL) {
330 mFeatureStateCached = status;
331 if (mStatusCallback != null) {
332 mStatusCallback.notifyStateChanged();
333 }
334 }
335 }
336 }
337 };
338
339 public MmTelFeatureConnection(Context context, int slotId) {
340 mSlotId = slotId;
341 mContext = context;
342 }
343
344 private @Nullable IImsRegistration getRegistration() {
345 TelephonyManager tm = getTelephonyManager(mContext);
346 return tm != null ? tm.getImsRegistration(mSlotId, ImsFeature.FEATURE_MMTEL) : null;
347 }
348
349 private IImsConfig getConfig() {
350 TelephonyManager tm = getTelephonyManager(mContext);
351 return tm != null ? tm.getImsConfig(mSlotId, ImsFeature.FEATURE_MMTEL) : null;
352 }
353
Brad Ebingerd449b232018-02-12 14:33:05 -0800354 public boolean isEmergencyMmTelAvailable() {
355 TelephonyManager tm = getTelephonyManager(mContext);
356 return tm != null ? tm.isEmergencyMmTelAvailable(mSlotId) : false;
357 }
358
Brad Ebinger936a7d12018-01-16 09:36:56 -0800359 public IImsServiceFeatureCallback getListener() {
360 return mListenerBinder;
361 }
362
363 public void setBinder(IBinder binder) {
364 mBinder = binder;
365 }
366
367 /**
368 * Opens the connection to the {@link MmTelFeature} and establishes a listener back to the
369 * framework. Calling this method multiple times will reset the listener attached to the
370 * {@link MmTelFeature}.
371 * @param listener A {@link MmTelFeature.Listener} that will be used by the {@link MmTelFeature}
372 * to notify the framework of updates.
373 */
374 public void openConnection(MmTelFeature.Listener listener) throws RemoteException {
375 synchronized (mLock) {
376 checkServiceIsReady();
377 mMmTelFeatureListener = listener;
378 getServiceInterface(mBinder).setListener(mMmTelFeatureListener);
379 }
380 }
381
382 public void closeConnection() {
383 mRegistrationCallbackManager.close();
384 mCapabilityCallbackManager.close();
385 try {
386 getServiceInterface(mBinder).setListener(null);
387 } catch (RemoteException e) {
388 Log.w(TAG, "closeConnection: couldn't remove listener!");
389 }
390 }
391
392 public void addRegistrationCallback(ImsRegistrationImplBase.Callback callback)
393 throws RemoteException {
394 mRegistrationCallbackManager.addCallback(callback);
395 }
396
397 public void removeRegistrationCallback(ImsRegistrationImplBase.Callback callback)
398 throws RemoteException {
399 mRegistrationCallbackManager.removeCallback(callback);
400 }
401
402 public void addCapabilityCallback(ImsFeature.CapabilityCallback callback)
403 throws RemoteException {
404 mCapabilityCallbackManager.addCallback(callback);
405 }
406
407 public void removeCapabilityCallback(ImsFeature.CapabilityCallback callback)
408 throws RemoteException {
409 mCapabilityCallbackManager.removeCallback(callback);
410 }
411
412 public void changeEnabledCapabilities(CapabilityChangeRequest request,
413 ImsFeature.CapabilityCallback callback) throws RemoteException {
414 synchronized (mLock) {
415 checkServiceIsReady();
416 getServiceInterface(mBinder).changeCapabilitiesConfiguration(request, callback);
417 }
418 }
419
420 public void queryEnabledCapabilities(int capability, int radioTech,
421 ImsFeature.CapabilityCallback callback) throws RemoteException {
422 synchronized (mLock) {
423 checkServiceIsReady();
424 getServiceInterface(mBinder).queryCapabilityConfiguration(capability, radioTech,
425 callback);
426 }
427 }
428
429 public MmTelFeature.MmTelCapabilities queryCapabilityStatus() throws RemoteException {
430 synchronized (mLock) {
431 checkServiceIsReady();
432 return new MmTelFeature.MmTelCapabilities(
433 getServiceInterface(mBinder).queryCapabilityStatus());
434 }
435 }
436
437 public ImsCallProfile createCallProfile(int callServiceType, int callType)
438 throws RemoteException {
439 synchronized (mLock) {
440 checkServiceIsReady();
441 return getServiceInterface(mBinder).createCallProfile(callServiceType, callType);
442 }
443 }
444
445 public IImsCallSession createCallSession(ImsCallProfile profile)
446 throws RemoteException {
447 synchronized (mLock) {
448 checkServiceIsReady();
449 return getServiceInterface(mBinder).createCallSession(profile);
450 }
451 }
452
453 public IImsUt getUtInterface() throws RemoteException {
454 synchronized (mLock) {
455 checkServiceIsReady();
456 return getServiceInterface(mBinder).getUtInterface();
457 }
458 }
459
460 public IImsConfig getConfigInterface() throws RemoteException {
461 synchronized (mLock) {
462 checkServiceIsReady();
463 return getConfig();
464 }
465 }
466
467 public @ImsRegistrationImplBase.ImsRegistrationTech int getRegistrationTech()
468 throws RemoteException {
469 synchronized (mLock) {
470 checkServiceIsReady();
471 IImsRegistration registration = getRegistration();
472 if (registration != null) {
473 return registration.getRegistrationTechnology();
474 } else {
475 return ImsRegistrationImplBase.REGISTRATION_TECH_NONE;
476 }
477 }
478 }
479
480 public IImsEcbm getEcbmInterface() throws RemoteException {
481 synchronized (mLock) {
482 checkServiceIsReady();
483 return getServiceInterface(mBinder).getEcbmInterface();
484 }
485 }
486
487 public void setUiTTYMode(int uiTtyMode, Message onComplete)
488 throws RemoteException {
489 synchronized (mLock) {
490 checkServiceIsReady();
491 getServiceInterface(mBinder).setUiTtyMode(uiTtyMode, onComplete);
492 }
493 }
494
495 public IImsMultiEndpoint getMultiEndpointInterface() throws RemoteException {
496 synchronized (mLock) {
497 checkServiceIsReady();
498 return getServiceInterface(mBinder).getMultiEndpointInterface();
499 }
500 }
501
502 public void sendSms(int token, int messageRef, String format, String smsc, boolean isRetry,
503 byte[] pdu) throws RemoteException {
504 synchronized (mLock) {
505 checkServiceIsReady();
506 getServiceInterface(mBinder).sendSms(token, messageRef, format, smsc, isRetry,
507 pdu);
508 }
509 }
510
511 public void acknowledgeSms(int token, int messageRef,
512 @ImsSmsImplBase.SendStatusResult int result) throws RemoteException {
513 synchronized (mLock) {
514 checkServiceIsReady();
515 getServiceInterface(mBinder).acknowledgeSms(token, messageRef, result);
516 }
517 }
518
519 public void acknowledgeSmsReport(int token, int messageRef,
520 @ImsSmsImplBase.StatusReportResult int result) throws RemoteException {
521 synchronized (mLock) {
522 checkServiceIsReady();
523 getServiceInterface(mBinder).acknowledgeSmsReport(token, messageRef, result);
524 }
525 }
526
527 public String getSmsFormat() throws RemoteException {
528 synchronized (mLock) {
529 checkServiceIsReady();
530 return getServiceInterface(mBinder).getSmsFormat();
531 }
532 }
533
Brad Ebingered690772018-01-23 13:41:32 -0800534 public void onSmsReady() throws RemoteException {
535 synchronized (mLock) {
536 checkServiceIsReady();
537 getServiceInterface(mBinder).onSmsReady();
538 }
539 }
540
Brad Ebinger936a7d12018-01-16 09:36:56 -0800541 public void setSmsListener(IImsSmsListener listener) throws RemoteException {
542 synchronized (mLock) {
543 checkServiceIsReady();
544 getServiceInterface(mBinder).setSmsListener(listener);
545 }
546 }
547
Brad Ebingerd449b232018-02-12 14:33:05 -0800548 public @MmTelFeature.ProcessCallResult int shouldProcessCall(boolean isEmergency,
549 String[] numbers) throws RemoteException {
550 if (isEmergency && !isEmergencyMmTelAvailable()) {
551 // Don't query the ImsService if emergency calling is not available on the ImsService.
552 Log.i(TAG, "MmTel does not support emergency over IMS, fallback to CS.");
553 return MmTelFeature.PROCESS_CALL_CSFB;
554 }
555 synchronized (mLock) {
556 checkServiceIsReady();
557 return getServiceInterface(mBinder).shouldProcessCall(numbers);
558 }
559 }
560
Brad Ebinger936a7d12018-01-16 09:36:56 -0800561 /**
562 * @return an integer describing the current Feature Status, defined in
563 * {@link ImsFeature.ImsState}.
564 */
565 public int getFeatureState() {
566 synchronized (mLock) {
567 if (isBinderAlive() && mFeatureStateCached != null) {
568 Log.i(TAG, "getFeatureState - returning cached: " + mFeatureStateCached);
569 return mFeatureStateCached;
570 }
571 }
572 // Don't synchronize on Binder call.
573 Integer status = retrieveFeatureState();
574 synchronized (mLock) {
575 if (status == null) {
576 return ImsFeature.STATE_UNAVAILABLE;
577 }
578 // Cache only non-null value for feature status.
579 mFeatureStateCached = status;
580 }
581 Log.i(TAG, "getFeatureState - returning " + status);
582 return status;
583 }
584
585 /**
586 * Internal method used to retrieve the feature status from the corresponding ImsService.
587 */
588 private Integer retrieveFeatureState() {
589 if (mBinder != null) {
590 try {
591 return getServiceInterface(mBinder).getFeatureState();
592 } catch (RemoteException e) {
593 // Status check failed, don't update cache
594 }
595 }
596 return null;
597 }
598
599 /**
600 * @param c Callback that will fire when the feature status has changed.
601 */
602 public void setStatusCallback(IFeatureUpdate c) {
603 mStatusCallback = c;
604 }
605
606 /**
607 * @return Returns true if the ImsService is ready to take commands, false otherwise. If this
608 * method returns false, it doesn't mean that the Binder connection is not available (use
609 * {@link #isBinderReady()} to check that), but that the ImsService is not accepting commands
610 * at this time.
611 *
612 * For example, for DSDS devices, only one slot can be {@link ImsFeature#STATE_READY} to take
613 * commands at a time, so the other slot must stay at {@link ImsFeature#STATE_UNAVAILABLE}.
614 */
615 public boolean isBinderReady() {
616 return isBinderAlive() && getFeatureState() == ImsFeature.STATE_READY;
617 }
618
619 /**
620 * @return false if the binder connection is no longer alive.
621 */
622 public boolean isBinderAlive() {
623 return mIsAvailable && mBinder != null && mBinder.isBinderAlive();
624 }
625
626 protected void checkServiceIsReady() throws RemoteException {
627 if (!isBinderReady()) {
628 throw new RemoteException("ImsServiceProxy is not ready to accept commands.");
629 }
630 }
631
632 private IImsMmTelFeature getServiceInterface(IBinder b) {
633 return IImsMmTelFeature.Stub.asInterface(b);
634 }
635
636 protected void checkBinderConnection() throws RemoteException {
637 if (!isBinderAlive()) {
638 throw new RemoteException("ImsServiceProxy is not available for that feature.");
639 }
640 }
641}