Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 5c42bf44 authored by sungcheol ahn's avatar sungcheol ahn
Browse files

AOSP API support for Sip Dialog status callback

All active RCS services must be maintained from WiFi to RAN or from RAN to WiFi.
According to the carrier specification, QNS applied this API because it needs to know if the MSRP service is activated during handover.

Test: atest SipSessionTrackerTest
Test: cts SipDelegateManagerTest
Bug: b/244429207

Change-Id: Ic53b92232fe54bc57c16900be7a78743ac65d71c
parent ab86ac02
Loading
Loading
Loading
Loading
+18 −0
Original line number Diff line number Diff line
@@ -15716,8 +15716,10 @@ package android.telephony.ims {
    method @RequiresPermission(android.Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION) public void destroySipDelegate(@NonNull android.telephony.ims.SipDelegateConnection, int);
    method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION}) public boolean isSupported() throws android.telephony.ims.ImsException;
    method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION}) public void registerImsStateCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.ImsStateCallback) throws android.telephony.ims.ImsException;
    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerSipDialogStateCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.SipDialogStateCallback) throws android.telephony.ims.ImsException;
    method @RequiresPermission(android.Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION) public void triggerFullNetworkRegistration(@NonNull android.telephony.ims.SipDelegateConnection, @IntRange(from=100, to=699) int, @Nullable String);
    method public void unregisterImsStateCallback(@NonNull android.telephony.ims.ImsStateCallback);
    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void unregisterSipDialogStateCallback(@NonNull android.telephony.ims.SipDialogStateCallback) throws android.telephony.ims.ImsException;
    field public static final int DENIED_REASON_INVALID = 4; // 0x4
    field public static final int DENIED_REASON_IN_USE_BY_ANOTHER_DELEGATE = 1; // 0x1
    field public static final int DENIED_REASON_NOT_ALLOWED = 2; // 0x2
@@ -15742,6 +15744,22 @@ package android.telephony.ims {
    field public static final int SIP_DELEGATE_DESTROY_REASON_USER_DISABLED_RCS = 3; // 0x3
  }
  public final class SipDialogState implements android.os.Parcelable {
    method public int describeContents();
    method public int getState();
    method public void writeToParcel(@NonNull android.os.Parcel, int);
    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ims.SipDialogState> CREATOR;
    field public static final int STATE_CLOSED = 2; // 0x2
    field public static final int STATE_CONFIRMED = 1; // 0x1
    field public static final int STATE_EARLY = 0; // 0x0
  }
  public abstract class SipDialogStateCallback {
    ctor public SipDialogStateCallback();
    method public abstract void onActiveSipDialogsChanged(@NonNull java.util.List<android.telephony.ims.SipDialogState>);
    method public abstract void onError();
  }
  public final class SipMessage implements android.os.Parcelable {
    ctor public SipMessage(@NonNull String, @NonNull String, @NonNull byte[]);
    method public int describeContents();
+65 −0
Original line number Diff line number Diff line
@@ -513,4 +513,69 @@ public class SipDelegateManager {
            // ignore it
        }
    }

    /**
     * Register a new callback, which is used to notify the registrant of changes
     * to the state of the Sip Sessions managed remotely by the IMS stack.
     *
     * <p>Requires Permission:
     * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE}
     *
     * @param executor the Executor that will be used to call the {@link SipDialogStateCallback}.
     * @param callback The callback instance being registered.
     * @throws ImsException in the case that the callback can not be registered.
     * See {@link ImsException#getCode} for more information on when this is called.
     */
    @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
    public void registerSipDialogStateCallback(@NonNull Executor executor,
            @NonNull SipDialogStateCallback callback) throws ImsException {
        Objects.requireNonNull(callback, "Must include a non-null SipDialogStateCallback.");
        Objects.requireNonNull(executor, "Must include a non-null Executor.");

        callback.attachExecutor(executor);
        try {
            IImsRcsController controller = mBinderCache.listenOnBinder(
                    callback, callback::binderDied);
            if (controller == null) {
                throw new ImsException("Telephony server is down",
                        ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
            }
            controller.registerSipDialogStateCallback(mSubId, callback.getCallbackBinder());
        } catch (ServiceSpecificException e) {
            throw new ImsException(e.getMessage(), e.errorCode);
        } catch (RemoteException e) {
            throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
        } catch (IllegalStateException e) {
            throw new IllegalStateException(e.getMessage());
        }
    }

    /**
     * Unregisters a previously registered callback.
     *
     *  <p>Requires Permission:
     * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE}
     *
     * @param callback The callback instance to be unregistered.
     */
    @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
    public void unregisterSipDialogStateCallback(@NonNull SipDialogStateCallback callback)
            throws ImsException {
        Objects.requireNonNull(callback, "Must include a non-null SipDialogStateCallback.");

        IImsRcsController controller = mBinderCache.removeRunnable(callback);
        try {
            if (controller == null) {
                throw new ImsException("Telephony server is down",
                        ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
            }
            controller.unregisterSipDialogStateCallback(mSubId, callback.getCallbackBinder());
        } catch (ServiceSpecificException e) {
            throw new ImsException(e.getMessage(), e.errorCode);
        } catch (RemoteException e) {
            throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
        } catch (IllegalStateException e) {
            throw new IllegalStateException(e.getMessage());
        }
    }
}
+19 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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 android.telephony.ims;

parcelable SipDialogState;
 No newline at end of file
+135 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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 android.telephony.ims;

import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Objects;

/**
 * The state of an ongoing SIP dialog.
 * @hide
 */
@SystemApi
public final class SipDialogState implements Parcelable {

    /**@hide*/
    @Retention(RetentionPolicy.SOURCE)
    @IntDef(prefix = "STATE_", value = {STATE_EARLY, STATE_CONFIRMED, STATE_CLOSED})
    public @interface SipDialogStateCode {}
    /**
     * The device has sent out a dialog starting event and is awaiting a confirmation.
     */
    public static final int STATE_EARLY = 0;

    /**
     * The device has received a 2XX response to the early dialog.
     */
    public static final int STATE_CONFIRMED = 1;

    /**
     * The device has received either a 3XX+ response to a pending dialog request or a BYE
     * request has been sent on this dialog.
     */
    public static final int STATE_CLOSED = 2;

    private final int mState;

    /**
     * Builder for {@link SipDialogState}.
     * @hide
     */
    public static final class Builder {
        private int mState = STATE_EARLY;

        /**
         * constructor
         * @param state The state of SipDialog
         */
        public Builder(@SipDialogStateCode int state) {
            mState = state;
        }

        /**
         * Build the {@link SipDialogState}.
         * @return The {@link SipDialogState} instance.
         */
        public @NonNull SipDialogState build() {
            return new SipDialogState(this);
        }
    }

    /**
     * set Dialog state
     */
    private SipDialogState(@NonNull Builder builder) {
        this.mState = builder.mState;
    }

    private SipDialogState(Parcel in) {
        mState = in.readInt();
    }

    /**
     * @return The state of the SIP dialog
     */
    public @SipDialogStateCode int getState() {
        return mState;
    }

    public static final @NonNull Creator<SipDialogState> CREATOR = new Creator<SipDialogState>() {
        @Override
        public SipDialogState createFromParcel(@NonNull Parcel in) {
            return new SipDialogState(in);
        }

        @Override
        public SipDialogState[] newArray(int size) {
            return new SipDialogState[size];
        }
    };

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(@NonNull Parcel dest, int flags) {
        dest.writeInt(mState);
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        SipDialogState sipDialog = (SipDialogState) o;

        return mState == sipDialog.mState;
    }

    @Override
    public int hashCode() {
        return Objects.hash(mState);
    }
}
+103 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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 android.telephony.ims;

import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.os.Binder;

import com.android.internal.telephony.ISipDialogStateCallback;

import java.lang.ref.WeakReference;
import java.util.List;
import java.util.concurrent.Executor;

/**
 * This callback is used to notify listeners of SIP Dialog state changes.
 * @hide
 */
@SystemApi
public abstract class SipDialogStateCallback {

    private CallbackBinder mCallback;
    /**
    * @hide
    */
    public void attachExecutor(@NonNull @CallbackExecutor Executor executor) {
        if (executor == null) {
            throw new IllegalArgumentException("SipDialogStateCallback Executor must be non-null");
        }
        mCallback = new CallbackBinder(this, executor);
    }

    private static class CallbackBinder extends ISipDialogStateCallback.Stub {
        private WeakReference<SipDialogStateCallback> mSipDialogStateCallbackWeakRef;
        private Executor mExecutor;

        private CallbackBinder(SipDialogStateCallback callback, Executor executor) {
            mSipDialogStateCallbackWeakRef = new WeakReference<SipDialogStateCallback>(callback);
            mExecutor = executor;
        }

        Executor getExecutor() {
            return mExecutor;
        }

        @Override
        public void onActiveSipDialogsChanged(List<SipDialogState> dialogs) {
            SipDialogStateCallback callback = mSipDialogStateCallbackWeakRef.get();
            if (callback == null || dialogs == null) return;

            Binder.withCleanCallingIdentity(
                    () -> mExecutor.execute(() -> callback.onActiveSipDialogsChanged(dialogs)));
        }
    }

    /**
     * The state of one or more SIP dialogs has changed.
     *
     * @param dialogs A List of SipDialogState objects representing the state of the active
     *               SIP Dialogs.
     */
    public abstract void onActiveSipDialogsChanged(@NonNull List<SipDialogState> dialogs);

    /**
     * An unexpected error has occurred and the Telephony process has crashed. This
     * has caused this callback to be deregistered. The callback must be re-registered
     * in order to continue listening to the IMS service state.
     */
    public abstract void onError();

    /**
     * The callback to notify the death of telephony process
     * @hide
     */
    public final void binderDied() {
        if (mCallback != null) {
            mCallback.getExecutor().execute(() -> onError());
        }
    }

    /**
     * Return the callback binder
     * @hide
     */
    public CallbackBinder getCallbackBinder() {
        return mCallback;
    }
}
Loading