blob: 571670d522a9628cab0518084cd7ec3d43986247 [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 {
199 getRegistration().addRegistrationCallback(mRegistrationCallbackAdapter);
200 } 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
354 public IImsServiceFeatureCallback getListener() {
355 return mListenerBinder;
356 }
357
358 public void setBinder(IBinder binder) {
359 mBinder = binder;
360 }
361
362 /**
363 * Opens the connection to the {@link MmTelFeature} and establishes a listener back to the
364 * framework. Calling this method multiple times will reset the listener attached to the
365 * {@link MmTelFeature}.
366 * @param listener A {@link MmTelFeature.Listener} that will be used by the {@link MmTelFeature}
367 * to notify the framework of updates.
368 */
369 public void openConnection(MmTelFeature.Listener listener) throws RemoteException {
370 synchronized (mLock) {
371 checkServiceIsReady();
372 mMmTelFeatureListener = listener;
373 getServiceInterface(mBinder).setListener(mMmTelFeatureListener);
374 }
375 }
376
377 public void closeConnection() {
378 mRegistrationCallbackManager.close();
379 mCapabilityCallbackManager.close();
380 try {
381 getServiceInterface(mBinder).setListener(null);
382 } catch (RemoteException e) {
383 Log.w(TAG, "closeConnection: couldn't remove listener!");
384 }
385 }
386
387 public void addRegistrationCallback(ImsRegistrationImplBase.Callback callback)
388 throws RemoteException {
389 mRegistrationCallbackManager.addCallback(callback);
390 }
391
392 public void removeRegistrationCallback(ImsRegistrationImplBase.Callback callback)
393 throws RemoteException {
394 mRegistrationCallbackManager.removeCallback(callback);
395 }
396
397 public void addCapabilityCallback(ImsFeature.CapabilityCallback callback)
398 throws RemoteException {
399 mCapabilityCallbackManager.addCallback(callback);
400 }
401
402 public void removeCapabilityCallback(ImsFeature.CapabilityCallback callback)
403 throws RemoteException {
404 mCapabilityCallbackManager.removeCallback(callback);
405 }
406
407 public void changeEnabledCapabilities(CapabilityChangeRequest request,
408 ImsFeature.CapabilityCallback callback) throws RemoteException {
409 synchronized (mLock) {
410 checkServiceIsReady();
411 getServiceInterface(mBinder).changeCapabilitiesConfiguration(request, callback);
412 }
413 }
414
415 public void queryEnabledCapabilities(int capability, int radioTech,
416 ImsFeature.CapabilityCallback callback) throws RemoteException {
417 synchronized (mLock) {
418 checkServiceIsReady();
419 getServiceInterface(mBinder).queryCapabilityConfiguration(capability, radioTech,
420 callback);
421 }
422 }
423
424 public MmTelFeature.MmTelCapabilities queryCapabilityStatus() throws RemoteException {
425 synchronized (mLock) {
426 checkServiceIsReady();
427 return new MmTelFeature.MmTelCapabilities(
428 getServiceInterface(mBinder).queryCapabilityStatus());
429 }
430 }
431
432 public ImsCallProfile createCallProfile(int callServiceType, int callType)
433 throws RemoteException {
434 synchronized (mLock) {
435 checkServiceIsReady();
436 return getServiceInterface(mBinder).createCallProfile(callServiceType, callType);
437 }
438 }
439
440 public IImsCallSession createCallSession(ImsCallProfile profile)
441 throws RemoteException {
442 synchronized (mLock) {
443 checkServiceIsReady();
444 return getServiceInterface(mBinder).createCallSession(profile);
445 }
446 }
447
448 public IImsUt getUtInterface() throws RemoteException {
449 synchronized (mLock) {
450 checkServiceIsReady();
451 return getServiceInterface(mBinder).getUtInterface();
452 }
453 }
454
455 public IImsConfig getConfigInterface() throws RemoteException {
456 synchronized (mLock) {
457 checkServiceIsReady();
458 return getConfig();
459 }
460 }
461
462 public @ImsRegistrationImplBase.ImsRegistrationTech int getRegistrationTech()
463 throws RemoteException {
464 synchronized (mLock) {
465 checkServiceIsReady();
466 IImsRegistration registration = getRegistration();
467 if (registration != null) {
468 return registration.getRegistrationTechnology();
469 } else {
470 return ImsRegistrationImplBase.REGISTRATION_TECH_NONE;
471 }
472 }
473 }
474
475 public IImsEcbm getEcbmInterface() throws RemoteException {
476 synchronized (mLock) {
477 checkServiceIsReady();
478 return getServiceInterface(mBinder).getEcbmInterface();
479 }
480 }
481
482 public void setUiTTYMode(int uiTtyMode, Message onComplete)
483 throws RemoteException {
484 synchronized (mLock) {
485 checkServiceIsReady();
486 getServiceInterface(mBinder).setUiTtyMode(uiTtyMode, onComplete);
487 }
488 }
489
490 public IImsMultiEndpoint getMultiEndpointInterface() throws RemoteException {
491 synchronized (mLock) {
492 checkServiceIsReady();
493 return getServiceInterface(mBinder).getMultiEndpointInterface();
494 }
495 }
496
497 public void sendSms(int token, int messageRef, String format, String smsc, boolean isRetry,
498 byte[] pdu) throws RemoteException {
499 synchronized (mLock) {
500 checkServiceIsReady();
501 getServiceInterface(mBinder).sendSms(token, messageRef, format, smsc, isRetry,
502 pdu);
503 }
504 }
505
506 public void acknowledgeSms(int token, int messageRef,
507 @ImsSmsImplBase.SendStatusResult int result) throws RemoteException {
508 synchronized (mLock) {
509 checkServiceIsReady();
510 getServiceInterface(mBinder).acknowledgeSms(token, messageRef, result);
511 }
512 }
513
514 public void acknowledgeSmsReport(int token, int messageRef,
515 @ImsSmsImplBase.StatusReportResult int result) throws RemoteException {
516 synchronized (mLock) {
517 checkServiceIsReady();
518 getServiceInterface(mBinder).acknowledgeSmsReport(token, messageRef, result);
519 }
520 }
521
522 public String getSmsFormat() throws RemoteException {
523 synchronized (mLock) {
524 checkServiceIsReady();
525 return getServiceInterface(mBinder).getSmsFormat();
526 }
527 }
528
Brad Ebingered690772018-01-23 13:41:32 -0800529 public void onSmsReady() throws RemoteException {
530 synchronized (mLock) {
531 checkServiceIsReady();
532 getServiceInterface(mBinder).onSmsReady();
533 }
534 }
535
Brad Ebinger936a7d12018-01-16 09:36:56 -0800536 public void setSmsListener(IImsSmsListener listener) throws RemoteException {
537 synchronized (mLock) {
538 checkServiceIsReady();
539 getServiceInterface(mBinder).setSmsListener(listener);
540 }
541 }
542
543 /**
544 * @return an integer describing the current Feature Status, defined in
545 * {@link ImsFeature.ImsState}.
546 */
547 public int getFeatureState() {
548 synchronized (mLock) {
549 if (isBinderAlive() && mFeatureStateCached != null) {
550 Log.i(TAG, "getFeatureState - returning cached: " + mFeatureStateCached);
551 return mFeatureStateCached;
552 }
553 }
554 // Don't synchronize on Binder call.
555 Integer status = retrieveFeatureState();
556 synchronized (mLock) {
557 if (status == null) {
558 return ImsFeature.STATE_UNAVAILABLE;
559 }
560 // Cache only non-null value for feature status.
561 mFeatureStateCached = status;
562 }
563 Log.i(TAG, "getFeatureState - returning " + status);
564 return status;
565 }
566
567 /**
568 * Internal method used to retrieve the feature status from the corresponding ImsService.
569 */
570 private Integer retrieveFeatureState() {
571 if (mBinder != null) {
572 try {
573 return getServiceInterface(mBinder).getFeatureState();
574 } catch (RemoteException e) {
575 // Status check failed, don't update cache
576 }
577 }
578 return null;
579 }
580
581 /**
582 * @param c Callback that will fire when the feature status has changed.
583 */
584 public void setStatusCallback(IFeatureUpdate c) {
585 mStatusCallback = c;
586 }
587
588 /**
589 * @return Returns true if the ImsService is ready to take commands, false otherwise. If this
590 * method returns false, it doesn't mean that the Binder connection is not available (use
591 * {@link #isBinderReady()} to check that), but that the ImsService is not accepting commands
592 * at this time.
593 *
594 * For example, for DSDS devices, only one slot can be {@link ImsFeature#STATE_READY} to take
595 * commands at a time, so the other slot must stay at {@link ImsFeature#STATE_UNAVAILABLE}.
596 */
597 public boolean isBinderReady() {
598 return isBinderAlive() && getFeatureState() == ImsFeature.STATE_READY;
599 }
600
601 /**
602 * @return false if the binder connection is no longer alive.
603 */
604 public boolean isBinderAlive() {
605 return mIsAvailable && mBinder != null && mBinder.isBinderAlive();
606 }
607
608 protected void checkServiceIsReady() throws RemoteException {
609 if (!isBinderReady()) {
610 throw new RemoteException("ImsServiceProxy is not ready to accept commands.");
611 }
612 }
613
614 private IImsMmTelFeature getServiceInterface(IBinder b) {
615 return IImsMmTelFeature.Stub.asInterface(b);
616 }
617
618 protected void checkBinderConnection() throws RemoteException {
619 if (!isBinderAlive()) {
620 throw new RemoteException("ImsServiceProxy is not available for that feature.");
621 }
622 }
623}