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

Commit 52a8fdb7 authored by Hall Liu's avatar Hall Liu Committed by Gerrit Code Review
Browse files

Merge "Add the EMBMS group call API"

parents d7fe73d3 a7b0c1f0
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -506,11 +506,14 @@ java_defaults {
        "telephony/java/android/telephony/ims/aidl/IImsSmsListener.aidl",
        "telephony/java/android/telephony/mbms/IMbmsDownloadSessionCallback.aidl",
        "telephony/java/android/telephony/mbms/IMbmsStreamingSessionCallback.aidl",
        "telephony/java/android/telephony/mbms/IMbmsGroupCallSessionCallback.aidl",
        "telephony/java/android/telephony/mbms/IDownloadStatusListener.aidl",
        "telephony/java/android/telephony/mbms/IDownloadProgressListener.aidl",
        "telephony/java/android/telephony/mbms/IStreamingServiceCallback.aidl",
        "telephony/java/android/telephony/mbms/IGroupCallCallback.aidl",
        "telephony/java/android/telephony/mbms/vendor/IMbmsDownloadService.aidl",
        "telephony/java/android/telephony/mbms/vendor/IMbmsStreamingService.aidl",
        "telephony/java/android/telephony/mbms/vendor/IMbmsGroupCallService.aidl",
        "telephony/java/android/telephony/INetworkService.aidl",
        "telephony/java/android/telephony/INetworkServiceCallback.aidl",
        "telephony/java/com/android/ims/internal/IImsCallSession.aidl",
+38 −0
Original line number Diff line number Diff line
@@ -42250,6 +42250,13 @@ package android.telephony {
    field public static final int STATUS_UNKNOWN = 0; // 0x0
  }
  public class MbmsGroupCallSession implements java.lang.AutoCloseable {
    method public void close();
    method public static android.telephony.MbmsGroupCallSession create(android.content.Context, java.util.concurrent.Executor, int, android.telephony.mbms.MbmsGroupCallSessionCallback);
    method public static android.telephony.MbmsGroupCallSession create(android.content.Context, java.util.concurrent.Executor, android.telephony.mbms.MbmsGroupCallSessionCallback);
    method public android.telephony.mbms.GroupCall startGroupCall(java.util.concurrent.Executor, long, int[], int[], android.telephony.mbms.GroupCallCallback);
  }
  public class MbmsStreamingSession implements java.lang.AutoCloseable {
    method public void close();
    method public static android.telephony.MbmsStreamingSession create(android.content.Context, java.util.concurrent.Executor, int, android.telephony.mbms.MbmsStreamingSessionCallback);
@@ -43195,6 +43202,29 @@ package android.telephony.mbms {
    field public static final android.os.Parcelable.Creator<android.telephony.mbms.FileServiceInfo> CREATOR;
  }
  public class GroupCall implements java.lang.AutoCloseable {
    method public void close();
    method public long getTmgi();
    method public void updateGroupCall(int[], int[]);
    field public static final int REASON_BY_USER_REQUEST = 1; // 0x1
    field public static final int REASON_FREQUENCY_CONFLICT = 3; // 0x3
    field public static final int REASON_LEFT_MBMS_BROADCAST_AREA = 6; // 0x6
    field public static final int REASON_NONE = 0; // 0x0
    field public static final int REASON_NOT_CONNECTED_TO_HOMECARRIER_LTE = 5; // 0x5
    field public static final int REASON_OUT_OF_MEMORY = 4; // 0x4
    field public static final int STATE_STALLED = 3; // 0x3
    field public static final int STATE_STARTED = 2; // 0x2
    field public static final int STATE_STOPPED = 1; // 0x1
  }
  public class GroupCallCallback {
    ctor public GroupCallCallback();
    method public void onBroadcastSignalStrengthUpdated(int);
    method public void onError(int, java.lang.String);
    method public void onGroupCallStateChanged(int, int);
    field public static final int SIGNAL_STRENGTH_UNAVAILABLE = -1; // 0xffffffff
  }
  public class MbmsDownloadReceiver extends android.content.BroadcastReceiver {
    ctor public MbmsDownloadReceiver();
    method public void onReceive(android.content.Context, android.content.Intent);
@@ -43243,6 +43273,14 @@ package android.telephony.mbms {
    field public static final int ERROR_UNABLE_TO_START_SERVICE = 302; // 0x12e
  }
  public class MbmsGroupCallSessionCallback {
    ctor public MbmsGroupCallSessionCallback();
    method public void onAvailableSaisUpdated(java.util.List<java.lang.Integer>, java.util.List<java.util.List<java.lang.Integer>>);
    method public void onError(int, java.lang.String);
    method public void onMiddlewareReady();
    method public void onServiceInterfaceAvailable(java.lang.String, int);
  }
  public class MbmsStreamingSessionCallback {
    ctor public MbmsStreamingSessionCallback();
    method public void onError(int, java.lang.String);
+15 −0
Original line number Diff line number Diff line
@@ -5075,6 +5075,10 @@ package android.telephony {
    field public static final java.lang.String MBMS_DOWNLOAD_SERVICE_ACTION = "android.telephony.action.EmbmsDownload";
  }

  public class MbmsGroupCallSession implements java.lang.AutoCloseable {
    field public static final java.lang.String MBMS_GROUP_CALL_SERVICE_ACTION = "android.telephony.action.EmbmsGroupCall";
  }

  public class MbmsStreamingSession implements java.lang.AutoCloseable {
    field public static final java.lang.String MBMS_STREAMING_SERVICE_ACTION = "android.telephony.action.EmbmsStreaming";
  }
@@ -6375,6 +6379,17 @@ package android.telephony.mbms.vendor {
    method public int setTempFileRootDirectory(int, java.lang.String) throws android.os.RemoteException;
  }

  public class MbmsGroupCallServiceBase extends android.app.Service {
    ctor public MbmsGroupCallServiceBase();
    method public void dispose(int) throws android.os.RemoteException;
    method public int initialize(android.telephony.mbms.MbmsGroupCallSessionCallback, int) throws android.os.RemoteException;
    method public void onAppCallbackDied(int, int);
    method public android.os.IBinder onBind(android.content.Intent);
    method public int startGroupCall(int, long, int[], int[], android.telephony.mbms.GroupCallCallback);
    method public void stopGroupCall(int, long);
    method public void updateGroupCall(int, long, int[], int[]);
  }

  public class MbmsStreamingServiceBase extends android.os.Binder {
    ctor public MbmsStreamingServiceBase();
    method public void dispose(int) throws android.os.RemoteException;
+15 −0
Original line number Diff line number Diff line
@@ -966,6 +966,10 @@ package android.telephony {
    field public static final java.lang.String MBMS_DOWNLOAD_SERVICE_OVERRIDE_METADATA = "mbms-download-service-override";
  }

  public class MbmsGroupCallSession implements java.lang.AutoCloseable {
    field public static final java.lang.String MBMS_GROUP_CALL_SERVICE_OVERRIDE_METADATA = "mbms-group-call-service-override";
  }

  public class MbmsStreamingSession implements java.lang.AutoCloseable {
    field public static final java.lang.String MBMS_STREAMING_SERVICE_OVERRIDE_METADATA = "mbms-streaming-service-override";
  }
@@ -1035,6 +1039,17 @@ package android.telephony.mbms.vendor {
    method public int setTempFileRootDirectory(int, java.lang.String) throws android.os.RemoteException;
  }

  public class MbmsGroupCallServiceBase extends android.app.Service {
    ctor public MbmsGroupCallServiceBase();
    method public void dispose(int) throws android.os.RemoteException;
    method public int initialize(android.telephony.mbms.MbmsGroupCallSessionCallback, int) throws android.os.RemoteException;
    method public void onAppCallbackDied(int, int);
    method public android.os.IBinder onBind(android.content.Intent);
    method public int startGroupCall(int, long, int[], int[], android.telephony.mbms.GroupCallCallback);
    method public void stopGroupCall(int, long);
    method public void updateGroupCall(int, long, int[], int[]);
  }

  public class MbmsStreamingServiceBase extends android.os.Binder {
    ctor public MbmsStreamingServiceBase();
    method public void dispose(int) throws android.os.RemoteException;
+300 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 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;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SdkConstant;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.content.ComponentName;
import android.content.Context;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;
import android.telephony.mbms.GroupCall;
import android.telephony.mbms.GroupCallCallback;
import android.telephony.mbms.InternalGroupCallCallback;
import android.telephony.mbms.InternalGroupCallSessionCallback;
import android.telephony.mbms.MbmsErrors;
import android.telephony.mbms.MbmsGroupCallSessionCallback;
import android.telephony.mbms.MbmsUtils;
import android.telephony.mbms.vendor.IMbmsGroupCallService;
import android.util.ArraySet;
import android.util.Log;

import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;

/**
 * This class provides functionality for accessing group call functionality over MBMS.
 */
public class MbmsGroupCallSession implements AutoCloseable {
    private static final String LOG_TAG = "MbmsGroupCallSession";

    /**
     * Service action which must be handled by the middleware implementing the MBMS group call
     * interface.
     * @hide
     */
    @SystemApi
    @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
    public static final String MBMS_GROUP_CALL_SERVICE_ACTION =
            "android.telephony.action.EmbmsGroupCall";

    /**
     * Metadata key that specifies the component name of the service to bind to for group calls.
     * @hide
     */
    @TestApi
    public static final String MBMS_GROUP_CALL_SERVICE_OVERRIDE_METADATA =
            "mbms-group-call-service-override";

    private static AtomicBoolean sIsInitialized = new AtomicBoolean(false);

    private AtomicReference<IMbmsGroupCallService> mService = new AtomicReference<>(null);
    private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
        @Override
        public void binderDied() {
            sIsInitialized.set(false);
            mInternalCallback.onError(MbmsErrors.ERROR_MIDDLEWARE_LOST,
                    "Received death notification");
        }
    };

    private InternalGroupCallSessionCallback mInternalCallback;
    private Set<GroupCall> mKnownActiveGroupCalls = new ArraySet<>();

    private final Context mContext;
    private int mSubscriptionId;

    /** @hide */
    private MbmsGroupCallSession(Context context, Executor executor, int subscriptionId,
            MbmsGroupCallSessionCallback callback) {
        mContext = context;
        mSubscriptionId = subscriptionId;
        mInternalCallback = new InternalGroupCallSessionCallback(callback, executor);
    }

    /**
     * Create a new {@link MbmsGroupCallSession} using the given subscription ID.
     *
     * You may only have one instance of {@link MbmsGroupCallSession} per UID. If you call this
     * method while there is an active instance of {@link MbmsGroupCallSession} in your process
     * (in other words, one that has not had {@link #close()} called on it), this method will
     * throw an {@link IllegalStateException}. If you call this method in a different process
     * running under the same UID, an error will be indicated via
     * {@link MbmsGroupCallSessionCallback#onError(int, String)}.
     *
     * Note that initialization may fail asynchronously. If you wish to try again after you
     * receive such an asynchronous error, you must call {@link #close()} on the instance of
     * {@link MbmsGroupCallSession} that you received before calling this method again.
     *
     * @param context The {@link Context} to use.
     * @param executor The executor on which you wish to execute callbacks.
     * @param subscriptionId The subscription ID to use.
     * @param callback A callback object on which you wish to receive results of asynchronous
     *                 operations.
     * @return An instance of {@link MbmsGroupCallSession}, or null if an error occurred.
     */
    public static @Nullable MbmsGroupCallSession create(@NonNull Context context,
            @NonNull Executor executor, int subscriptionId,
            final @NonNull MbmsGroupCallSessionCallback callback) {
        if (!sIsInitialized.compareAndSet(false, true)) {
            throw new IllegalStateException("Cannot create two instances of MbmsGroupCallSession");
        }
        MbmsGroupCallSession session = new MbmsGroupCallSession(context, executor,
                subscriptionId, callback);

        final int result = session.bindAndInitialize();
        if (result != MbmsErrors.SUCCESS) {
            sIsInitialized.set(false);
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    callback.onError(result, null);
                }
            });
            return null;
        }
        return session;
    }

    /**
     * Create a new {@link MbmsGroupCallSession} using the system default data subscription ID.
     * See {@link #create(Context, Executor, int, MbmsGroupCallSessionCallback)}.
     */
    public static MbmsGroupCallSession create(@NonNull Context context,
            @NonNull Executor executor, @NonNull MbmsGroupCallSessionCallback callback) {
        return create(context, executor, SubscriptionManager.getDefaultSubscriptionId(), callback);
    }

    /**
     * Terminates this instance. Also terminates
     * any group calls spawned from this instance as if
     * {@link GroupCall#close()} had been called on them. After this method returns,
     * no further callbacks originating from the middleware will be enqueued on the provided
     * instance of {@link MbmsGroupCallSessionCallback}, but callbacks that have already been
     * enqueued will still be delivered.
     *
     * It is safe to call {@link #create(Context, Executor, int, MbmsGroupCallSessionCallback)} to
     * obtain another instance of {@link MbmsGroupCallSession} immediately after this method
     * returns.
     *
     * May throw an {@link IllegalStateException}
     */
    public void close() {
        try {
            IMbmsGroupCallService groupCallService = mService.get();
            if (groupCallService == null) {
                // Ignore and return, assume already disposed.
                return;
            }
            groupCallService.dispose(mSubscriptionId);
            for (GroupCall s : mKnownActiveGroupCalls) {
                s.getCallback().stop();
            }
            mKnownActiveGroupCalls.clear();
        } catch (RemoteException e) {
            // Ignore for now
        } finally {
            mService.set(null);
            sIsInitialized.set(false);
            mInternalCallback.stop();
        }
    }

    /**
     * Starts the requested group call, reporting status to the indicated callback.
     * Returns an object used to control that call.
     *
     * May throw an {@link IllegalArgumentException} or an {@link IllegalStateException}
     *
     * Asynchronous errors through the callback include any of the errors in
     * {@link MbmsErrors.GeneralErrors}.
     *
     * @param executor The executor on which you wish to execute callbacks for this stream.
     * @param tmgi The TMGI, an identifier for the group call you want to join.
     * @param saiArray An array of SAIs for the group call that should be negotiated separately with
     *                the carrier.
     * @param frequencyArray An array of frequencies for the group call that should be negotiated
     *                separately with the carrier.
     * @param callback The callback that you want to receive information about the call on.
     * @return An instance of {@link GroupCall} through which the call can be controlled.
     *         May be {@code null} if an error occurred.
     */
    public @Nullable GroupCall startGroupCall(@NonNull Executor executor, long tmgi, int[] saiArray,
            int[] frequencyArray, @NonNull GroupCallCallback callback) {
        IMbmsGroupCallService groupCallService = mService.get();
        if (groupCallService == null) {
            throw new IllegalStateException("Middleware not yet bound");
        }

        InternalGroupCallCallback serviceCallback = new InternalGroupCallCallback(
                callback, executor);

        GroupCall serviceForApp = new GroupCall(mSubscriptionId,
                groupCallService, this, tmgi, serviceCallback);
        mKnownActiveGroupCalls.add(serviceForApp);

        try {
            int returnCode = groupCallService.startGroupCall(
                    mSubscriptionId, tmgi, saiArray, frequencyArray, serviceCallback);
            if (returnCode == MbmsErrors.UNKNOWN) {
                // Unbind and throw an obvious error
                close();
                throw new IllegalStateException("Middleware must not return an unknown error code");
            }
            if (returnCode != MbmsErrors.SUCCESS) {
                mInternalCallback.onError(returnCode, null);
                return null;
            }
        } catch (RemoteException e) {
            Log.w(LOG_TAG, "Remote process died");
            mService.set(null);
            sIsInitialized.set(false);
            mInternalCallback.onError(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
            return null;
        }

        return serviceForApp;
    }

    /** @hide */
    public void onGroupCallStopped(GroupCall service) {
        mKnownActiveGroupCalls.remove(service);
    }

    private int bindAndInitialize() {
        return MbmsUtils.startBinding(mContext, MBMS_GROUP_CALL_SERVICE_ACTION,
                new ServiceConnection() {
                    @Override
                    public void onServiceConnected(ComponentName name, IBinder service) {
                        IMbmsGroupCallService groupCallService =
                                IMbmsGroupCallService.Stub.asInterface(service);
                        int result;
                        try {
                            result = groupCallService.initialize(mInternalCallback,
                                    mSubscriptionId);
                        } catch (RemoteException e) {
                            Log.e(LOG_TAG, "Service died before initialization");
                            mInternalCallback.onError(
                                    MbmsErrors.InitializationErrors.ERROR_UNABLE_TO_INITIALIZE,
                                    e.toString());
                            sIsInitialized.set(false);
                            return;
                        } catch (RuntimeException e) {
                            Log.e(LOG_TAG, "Runtime exception during initialization");
                            mInternalCallback.onError(
                                    MbmsErrors.InitializationErrors.ERROR_UNABLE_TO_INITIALIZE,
                                    e.toString());
                            sIsInitialized.set(false);
                            return;
                        }
                        if (result == MbmsErrors.UNKNOWN) {
                            // Unbind and throw an obvious error
                            close();
                            throw new IllegalStateException("Middleware must not return"
                                    + " an unknown error code");
                        }
                        if (result != MbmsErrors.SUCCESS) {
                            mInternalCallback.onError(result,
                                    "Error returned during initialization");
                            sIsInitialized.set(false);
                            return;
                        }
                        try {
                            groupCallService.asBinder().linkToDeath(mDeathRecipient, 0);
                        } catch (RemoteException e) {
                            mInternalCallback.onError(MbmsErrors.ERROR_MIDDLEWARE_LOST,
                                    "Middleware lost during initialization");
                            sIsInitialized.set(false);
                            return;
                        }
                        mService.set(groupCallService);
                    }

                    @Override
                    public void onServiceDisconnected(ComponentName name) {
                        sIsInitialized.set(false);
                        mService.set(null);
                    }
                });
    }
}
Loading