310 lines
12 KiB
Java
310 lines
12 KiB
Java
/*
|
|
* Copyright (c) 2019 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package com.android.ims;
|
|
|
|
import android.annotation.IntDef;
|
|
import android.content.Context;
|
|
import android.content.pm.PackageManager;
|
|
import android.os.RemoteException;
|
|
import android.telephony.ims.ImsReasonInfo;
|
|
import android.telephony.ims.ImsService;
|
|
import android.telephony.ims.feature.ImsFeature;
|
|
|
|
import com.android.ims.internal.IImsServiceFeatureCallback;
|
|
import com.android.internal.annotations.VisibleForTesting;
|
|
import com.android.telephony.Rlog;
|
|
|
|
import java.lang.annotation.Retention;
|
|
import java.lang.annotation.RetentionPolicy;
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
import java.util.concurrent.Executor;
|
|
|
|
/**
|
|
* Helper class for managing a connection to the ImsFeature manager.
|
|
*/
|
|
public class FeatureConnector<U extends FeatureUpdates> {
|
|
private static final String TAG = "FeatureConnector";
|
|
private static final boolean DBG = false;
|
|
|
|
/**
|
|
* This Connection has become unavailable due to the ImsService being disconnected due to
|
|
* an event such as SIM Swap, carrier configuration change, etc...
|
|
*
|
|
* {@link Listener#connectionReady} will be called when a new Manager is available.
|
|
*/
|
|
public static final int UNAVAILABLE_REASON_DISCONNECTED = 0;
|
|
|
|
/**
|
|
* This Connection has become unavailable due to the ImsService moving to the NOT_READY state.
|
|
*
|
|
* {@link Listener#connectionReady} will be called when the manager moves back to ready.
|
|
*/
|
|
public static final int UNAVAILABLE_REASON_NOT_READY = 1;
|
|
|
|
/**
|
|
* IMS is not supported on this device. This should be considered a permanent error and
|
|
* a Manager will never become available.
|
|
*/
|
|
public static final int UNAVAILABLE_REASON_IMS_UNSUPPORTED = 2;
|
|
|
|
/**
|
|
* The server of this information has crashed or otherwise generated an error that will require
|
|
* a retry to connect. This is rare, however in this case, {@link #disconnect()} and
|
|
* {@link #connect()} will need to be called again to recreate the connection with the server.
|
|
* <p>
|
|
* Only applicable if this is used outside of the server's own process.
|
|
*/
|
|
public static final int UNAVAILABLE_REASON_SERVER_UNAVAILABLE = 3;
|
|
|
|
/**
|
|
* @hide
|
|
*/
|
|
@Retention(RetentionPolicy.SOURCE)
|
|
@IntDef(prefix = "UNAVAILABLE_REASON_", value = {
|
|
UNAVAILABLE_REASON_DISCONNECTED,
|
|
UNAVAILABLE_REASON_NOT_READY,
|
|
UNAVAILABLE_REASON_IMS_UNSUPPORTED,
|
|
UNAVAILABLE_REASON_SERVER_UNAVAILABLE
|
|
})
|
|
public @interface UnavailableReason {}
|
|
|
|
/**
|
|
* Factory used to create a new instance of the manager that this FeatureConnector is waiting
|
|
* to connect the FeatureConnection to.
|
|
* @param <U> The Manager that this FeatureConnector has been created for.
|
|
*/
|
|
public interface ManagerFactory<U extends FeatureUpdates> {
|
|
/**
|
|
* Create a manager instance, which will connect to the FeatureConnection.
|
|
*/
|
|
U createManager(Context context, int phoneId);
|
|
}
|
|
|
|
/**
|
|
* Listener interface used by Listeners of FeatureConnector that are waiting for a Manager
|
|
* interface for a specific ImsFeature.
|
|
* @param <U> The Manager that the listener is listening for.
|
|
*/
|
|
public interface Listener<U extends FeatureUpdates> {
|
|
/**
|
|
* ImsFeature manager is connected to the underlying IMS implementation.
|
|
*/
|
|
void connectionReady(U manager, int subId) throws ImsException;
|
|
|
|
/**
|
|
* The underlying IMS implementation is unavailable and can not be used to communicate.
|
|
*/
|
|
void connectionUnavailable(@UnavailableReason int reason);
|
|
}
|
|
|
|
private final IImsServiceFeatureCallback mCallback = new IImsServiceFeatureCallback.Stub() {
|
|
|
|
@Override
|
|
public void imsFeatureCreated(ImsFeatureContainer c, int subId) {
|
|
log("imsFeatureCreated: " + c + ", subId: " + subId);
|
|
synchronized (mLock) {
|
|
mManager.associate(c, subId);
|
|
mManager.updateFeatureCapabilities(c.getCapabilities());
|
|
mDisconnectedReason = null;
|
|
}
|
|
// Notifies executor, so notify outside of lock
|
|
imsStatusChanged(c.getState(), subId);
|
|
}
|
|
|
|
@Override
|
|
public void imsFeatureRemoved(@UnavailableReason int reason) {
|
|
log("imsFeatureRemoved: reason=" + reason);
|
|
synchronized (mLock) {
|
|
// only generate new events if the disconnect event isn't the same as before, except
|
|
// for UNAVAILABLE_REASON_SERVER_UNAVAILABLE, which indicates a local issue and
|
|
// each event is actionable.
|
|
if (mDisconnectedReason != null
|
|
&& (mDisconnectedReason == reason
|
|
&& mDisconnectedReason != UNAVAILABLE_REASON_SERVER_UNAVAILABLE)) {
|
|
log("imsFeatureRemoved: ignore");
|
|
return;
|
|
}
|
|
mDisconnectedReason = reason;
|
|
// Ensure that we set ready state back to false so that we do not miss setting ready
|
|
// later if the initial state when recreated is READY.
|
|
mLastReadyState = false;
|
|
}
|
|
// Allow the listener to do cleanup while the connection still potentially valid (unless
|
|
// the process crashed).
|
|
mExecutor.execute(() -> mListener.connectionUnavailable(reason));
|
|
mManager.invalidate();
|
|
}
|
|
|
|
@Override
|
|
public void imsStatusChanged(int status, int subId) {
|
|
log("imsStatusChanged: status=" + ImsFeature.STATE_LOG_MAP.get(status));
|
|
final U manager;
|
|
final boolean isReady;
|
|
synchronized (mLock) {
|
|
if (mDisconnectedReason != null) {
|
|
log("imsStatusChanged: ignore");
|
|
return;
|
|
}
|
|
mManager.updateFeatureState(status);
|
|
manager = mManager;
|
|
isReady = mReadyFilter.contains(status);
|
|
boolean didReadyChange = isReady ^ mLastReadyState;
|
|
mLastReadyState = isReady;
|
|
if (!didReadyChange) {
|
|
log("imsStatusChanged: ready didn't change, ignore");
|
|
return;
|
|
}
|
|
}
|
|
mExecutor.execute(() -> {
|
|
try {
|
|
if (isReady) {
|
|
notifyReady(manager, subId);
|
|
} else {
|
|
notifyNotReady();
|
|
}
|
|
} catch (ImsException e) {
|
|
if (e.getCode()
|
|
== ImsReasonInfo.CODE_LOCAL_IMS_NOT_SUPPORTED_ON_DEVICE) {
|
|
mListener.connectionUnavailable(UNAVAILABLE_REASON_IMS_UNSUPPORTED);
|
|
} else {
|
|
notifyNotReady();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
@Override
|
|
public void updateCapabilities(long caps) {
|
|
log("updateCapabilities: capabilities=" + ImsService.getCapabilitiesString(caps));
|
|
synchronized (mLock) {
|
|
if (mDisconnectedReason != null) {
|
|
log("updateCapabilities: ignore");
|
|
return;
|
|
}
|
|
mManager.updateFeatureCapabilities(caps);
|
|
}
|
|
}
|
|
};
|
|
|
|
private final int mPhoneId;
|
|
private final Context mContext;
|
|
private final ManagerFactory<U> mFactory;
|
|
private final Listener<U> mListener;
|
|
private final Executor mExecutor;
|
|
private final Object mLock = new Object();
|
|
private final String mLogPrefix;
|
|
// A List of integers, each corresponding to an ImsFeature.ImsState, that the FeatureConnector
|
|
// will use to call Listener#connectionReady when the ImsFeature that this connector is waiting
|
|
// for changes into one of the states in this list.
|
|
private final List<Integer> mReadyFilter = new ArrayList<>();
|
|
|
|
private U mManager;
|
|
// Start in disconnected state;
|
|
private Integer mDisconnectedReason = UNAVAILABLE_REASON_DISCONNECTED;
|
|
// Stop redundant connectionAvailable if the ready filter contains multiple states.
|
|
// Also, do not send the first unavailable until after we have moved to available once.
|
|
private boolean mLastReadyState = false;
|
|
|
|
|
|
|
|
@VisibleForTesting
|
|
public FeatureConnector(Context context, int phoneId, ManagerFactory<U> factory,
|
|
String logPrefix, List<Integer> readyFilter, Listener<U> listener, Executor executor) {
|
|
mContext = context;
|
|
mPhoneId = phoneId;
|
|
mFactory = factory;
|
|
mLogPrefix = logPrefix;
|
|
mReadyFilter.addAll(readyFilter);
|
|
mListener = listener;
|
|
mExecutor = executor;
|
|
}
|
|
|
|
/**
|
|
* Start the creation of a connection to the underlying ImsService implementation. When the
|
|
* service is connected, {@link FeatureConnector.Listener#connectionReady} will be
|
|
* called with an active instance.
|
|
*
|
|
* If this device does not support an ImsStack (i.e. doesn't support
|
|
* {@link PackageManager#FEATURE_TELEPHONY_IMS} feature), this method will do nothing.
|
|
*/
|
|
public void connect() {
|
|
if (DBG) log("connect");
|
|
if (!isSupported()) {
|
|
mExecutor.execute(() -> mListener.connectionUnavailable(
|
|
UNAVAILABLE_REASON_IMS_UNSUPPORTED));
|
|
logw("connect: not supported.");
|
|
return;
|
|
}
|
|
synchronized (mLock) {
|
|
if (mManager == null) {
|
|
mManager = mFactory.createManager(mContext, mPhoneId);
|
|
}
|
|
}
|
|
mManager.registerFeatureCallback(mPhoneId, mCallback);
|
|
}
|
|
|
|
// Check if this ImsFeature is supported or not.
|
|
private boolean isSupported() {
|
|
return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY_IMS);
|
|
}
|
|
|
|
/**
|
|
* Disconnect from the ImsService Implementation and clean up. When this is complete,
|
|
* {@link FeatureConnector.Listener#connectionUnavailable(int)} will be called one last time.
|
|
*/
|
|
public void disconnect() {
|
|
if (DBG) log("disconnect");
|
|
final U manager;
|
|
synchronized (mLock) {
|
|
manager = mManager;
|
|
}
|
|
if (manager == null) return;
|
|
|
|
manager.unregisterFeatureCallback(mCallback);
|
|
try {
|
|
mCallback.imsFeatureRemoved(UNAVAILABLE_REASON_DISCONNECTED);
|
|
} catch (RemoteException ignore) {} // local call
|
|
}
|
|
|
|
// Should be called on executor
|
|
private void notifyReady(U manager, int subId) throws ImsException {
|
|
try {
|
|
if (DBG) log("notifyReady");
|
|
mListener.connectionReady(manager, subId);
|
|
}
|
|
catch (ImsException e) {
|
|
if(DBG) log("notifyReady exception: " + e.getMessage());
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
// Should be called on executor.
|
|
private void notifyNotReady() {
|
|
if (DBG) log("notifyNotReady");
|
|
mListener.connectionUnavailable(UNAVAILABLE_REASON_NOT_READY);
|
|
}
|
|
|
|
private void log(String message) {
|
|
Rlog.d(TAG, "[" + mLogPrefix + ", " + mPhoneId + "] " + message);
|
|
}
|
|
|
|
private void logw(String message) {
|
|
Rlog.w(TAG, "[" + mLogPrefix + ", " + mPhoneId + "] " + message);
|
|
}
|
|
}
|