Merge "Add the EMBMS group call API"

am: 52a8fdb7bd

Change-Id: Id9bf8b76532001c56e12633d882357e8757202d6
This commit is contained in:
Hall Liu
2018-09-25 14:22:45 -07:00
committed by android-build-merger
15 changed files with 1322 additions and 0 deletions

View File

@@ -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",

View File

@@ -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);

View File

@@ -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;

View File

@@ -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;

View File

@@ -0,0 +1,300 @@
/*
* 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);
}
});
}
}

View File

@@ -0,0 +1,176 @@
/*
* 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.mbms;
import android.annotation.IntDef;
import android.os.RemoteException;
import android.telephony.MbmsGroupCallSession;
import android.telephony.mbms.vendor.IMbmsGroupCallService;
import android.util.Log;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Class used to represent a single MBMS group call. After a call has been started with
* {@link MbmsGroupCallSession#startGroupCall},
* this class is used to hold information about the call and control it.
*/
public class GroupCall implements AutoCloseable {
private static final String LOG_TAG = "MbmsGroupCall";
/**
* The state of a group call, reported via
* {@link GroupCallCallback#onGroupCallStateChanged(int, int)}
* @hide
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = { "STATE_" }, value = {STATE_STOPPED, STATE_STARTED, STATE_STALLED})
public @interface GroupCallState {}
public static final int STATE_STOPPED = 1;
public static final int STATE_STARTED = 2;
public static final int STATE_STALLED = 3;
/**
* The reason for a call state change, reported via
* {@link GroupCallCallback#onGroupCallStateChanged(int, int)}
* @hide
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = { "REASON_" },
value = {REASON_BY_USER_REQUEST, REASON_FREQUENCY_CONFLICT,
REASON_OUT_OF_MEMORY, REASON_NOT_CONNECTED_TO_HOMECARRIER_LTE,
REASON_LEFT_MBMS_BROADCAST_AREA, REASON_NONE})
public @interface GroupCallStateChangeReason {}
/**
* Indicates that the middleware does not have a reason to provide for the state change.
*/
public static final int REASON_NONE = 0;
/**
* State changed due to a call to {@link #close()} or
* {@link MbmsGroupCallSession#startGroupCall}
*/
public static final int REASON_BY_USER_REQUEST = 1;
// 2 is unused to match up with streaming.
/**
* State changed due to a frequency conflict with another requested call.
*/
public static final int REASON_FREQUENCY_CONFLICT = 3;
/**
* State changed due to the middleware running out of memory
*/
public static final int REASON_OUT_OF_MEMORY = 4;
/**
* State changed due to the device leaving the home carrier's LTE network.
*/
public static final int REASON_NOT_CONNECTED_TO_HOMECARRIER_LTE = 5;
/**
* State changed due to the device leaving the area where this call is being broadcast.
*/
public static final int REASON_LEFT_MBMS_BROADCAST_AREA = 6;
private final int mSubscriptionId;
private final long mTmgi;
private final MbmsGroupCallSession mParentSession;
private final InternalGroupCallCallback mCallback;
private IMbmsGroupCallService mService;
/**
* @hide
*/
public GroupCall(int subscriptionId,
IMbmsGroupCallService service,
MbmsGroupCallSession session,
long tmgi,
InternalGroupCallCallback callback) {
mSubscriptionId = subscriptionId;
mParentSession = session;
mService = service;
mTmgi = tmgi;
mCallback = callback;
}
/**
* Retrieve the TMGI (Temporary Mobile Group Identity) corresponding to this call.
*/
public long getTmgi() {
return mTmgi;
}
/**
* Send an update to the middleware when the SAI (Service Area Identifier) list and frequency
* information of the group call has * changed. Callers must obtain this information from the
* wireless carrier independently.
* @param saiArray New array of SAIs that the call is available on.
* @param frequencyArray New array of frequencies that the call is available on.
*/
public void updateGroupCall(int[] saiArray, int[] frequencyArray) {
if (mService == null) {
throw new IllegalStateException("No group call service attached");
}
try {
mService.updateGroupCall(mSubscriptionId, mTmgi, saiArray, frequencyArray);
} catch (RemoteException e) {
Log.w(LOG_TAG, "Remote process died");
mService = null;
sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
} finally {
mParentSession.onGroupCallStopped(this);
}
}
/**
* Stop this group call. Further operations on this object will fail with an
* {@link IllegalStateException}.
*
* May throw an {@link IllegalStateException}
*/
@Override
public void close() {
if (mService == null) {
throw new IllegalStateException("No group call service attached");
}
try {
mService.stopGroupCall(mSubscriptionId, mTmgi);
} catch (RemoteException e) {
Log.w(LOG_TAG, "Remote process died");
mService = null;
sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
} finally {
mParentSession.onGroupCallStopped(this);
}
}
/** @hide */
public InternalGroupCallCallback getCallback() {
return mCallback;
}
private void sendErrorToApp(int errorCode, String message) {
mCallback.onError(errorCode, message);
}
}

View File

@@ -0,0 +1,87 @@
/*
* 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.mbms;
import android.annotation.IntDef;
import android.annotation.Nullable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* A callback class for use when the application is in a group call. The middleware
* will provide updates on the status of the call via this callback.
*/
public class GroupCallCallback {
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(value = {
MbmsErrors.ERROR_NO_UNIQUE_MIDDLEWARE,
MbmsErrors.ERROR_MIDDLEWARE_LOST,
MbmsErrors.ERROR_MIDDLEWARE_NOT_BOUND,
MbmsErrors.GeneralErrors.ERROR_MIDDLEWARE_NOT_YET_READY,
MbmsErrors.GeneralErrors.ERROR_OUT_OF_MEMORY,
MbmsErrors.GeneralErrors.ERROR_MIDDLEWARE_TEMPORARILY_UNAVAILABLE,
MbmsErrors.GeneralErrors.ERROR_IN_E911,
MbmsErrors.GeneralErrors.ERROR_NOT_CONNECTED_TO_HOME_CARRIER_LTE,
MbmsErrors.GeneralErrors.ERROR_UNABLE_TO_READ_SIM,
MbmsErrors.GeneralErrors.ERROR_CARRIER_CHANGE_NOT_ALLOWED}, prefix = { "ERROR_" })
private @interface GroupCallError{}
/**
* Indicates broadcast signal strength is not available for this call.
*
* This may be due to the call no longer being available due to geography
* or timing (end of service)
*/
public static final int SIGNAL_STRENGTH_UNAVAILABLE = -1;
/**
* Called by the middleware when it has detected an error condition in this group call. The
* possible error codes are listed in {@link MbmsErrors}.
* @param errorCode The error code.
* @param message A human-readable message generated by the middleware for debugging purposes.
*/
public void onError(@GroupCallError int errorCode, @Nullable String message) {
// default implementation empty
}
/**
* Called to indicate this call has changed state.
*
* See {@link GroupCall#STATE_STOPPED}, {@link GroupCall#STATE_STARTED}
* and {@link GroupCall#STATE_STALLED}.
*/
public void onGroupCallStateChanged(@GroupCall.GroupCallState int state,
@GroupCall.GroupCallStateChangeReason int reason) {
// default implementation empty
}
/**
* Broadcast Signal Strength updated.
*
* This signal strength is the BROADCAST signal strength which,
* depending on technology in play and it's deployment, may be
* stronger or weaker than the traditional UNICAST signal
* strength. It a simple int from 0-4 for valid levels or
* {@link #SIGNAL_STRENGTH_UNAVAILABLE} if broadcast is not available
* for this call due to timing, geography or popularity.
*/
public void onBroadcastSignalStrengthUpdated(int signalStrength) {
// default implementation empty
}
}

View File

@@ -0,0 +1,26 @@
/*
* 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.mbms;
/**
* @hide
*/
oneway interface IGroupCallCallback {
void onError(int errorCode, String message);
void onGroupCallStateChanged(int state, int reason);
void onBroadcastSignalStrengthUpdated(int signalStrength);
}

View File

@@ -0,0 +1,33 @@
/*
** Copyright 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.mbms;
import java.util.List;
/**
* @hide
*/
oneway interface IMbmsGroupCallSessionCallback
{
void onError(int errorCode, String message);
void onAvailableSaisUpdated(in List currentSai, in List availableSais);
void onServiceInterfaceAvailable(String interfaceName, int index);
void onMiddlewareReady();
}

View File

@@ -0,0 +1,96 @@
/*
* 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.mbms;
import android.os.Binder;
import java.util.concurrent.Executor;
/** @hide */
public class InternalGroupCallCallback extends IGroupCallCallback.Stub {
private final GroupCallCallback mAppCallback;
private final Executor mExecutor;
private volatile boolean mIsStopped = false;
public InternalGroupCallCallback(GroupCallCallback appCallback,
Executor executor) {
mAppCallback = appCallback;
mExecutor = executor;
}
@Override
public void onError(final int errorCode, final String message) {
if (mIsStopped) {
return;
}
mExecutor.execute(new Runnable() {
@Override
public void run() {
long token = Binder.clearCallingIdentity();
try {
mAppCallback.onError(errorCode, message);
} finally {
Binder.restoreCallingIdentity(token);
}
}
});
}
@Override
public void onGroupCallStateChanged(final int state, final int reason) {
if (mIsStopped) {
return;
}
mExecutor.execute(new Runnable() {
@Override
public void run() {
long token = Binder.clearCallingIdentity();
try {
mAppCallback.onGroupCallStateChanged(state, reason);
} finally {
Binder.restoreCallingIdentity(token);
}
}
});
}
@Override
public void onBroadcastSignalStrengthUpdated(final int signalStrength) {
if (mIsStopped) {
return;
}
mExecutor.execute(new Runnable() {
@Override
public void run() {
long token = Binder.clearCallingIdentity();
try {
mAppCallback.onBroadcastSignalStrengthUpdated(signalStrength);
} finally {
Binder.restoreCallingIdentity(token);
}
}
});
}
/** Prevents this callback from calling the app */
public void stop() {
mIsStopped = true;
}
}

View File

@@ -0,0 +1,116 @@
/*
* 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.mbms;
import android.os.Binder;
import java.util.List;
import java.util.concurrent.Executor;
/** @hide */
public class InternalGroupCallSessionCallback extends IMbmsGroupCallSessionCallback.Stub {
private final Executor mExecutor;
private final MbmsGroupCallSessionCallback mAppCallback;
private volatile boolean mIsStopped = false;
public InternalGroupCallSessionCallback(MbmsGroupCallSessionCallback appCallback,
Executor executor) {
mAppCallback = appCallback;
mExecutor = executor;
}
@Override
public void onError(final int errorCode, final String message) {
if (mIsStopped) {
return;
}
mExecutor.execute(new Runnable() {
@Override
public void run() {
long token = Binder.clearCallingIdentity();
try {
mAppCallback.onError(errorCode, message);
} finally {
Binder.restoreCallingIdentity(token);
}
}
});
}
@Override
public void onAvailableSaisUpdated(final List currentSais, final List availableSais) {
if (mIsStopped) {
return;
}
mExecutor.execute(new Runnable() {
@Override
public void run() {
long token = Binder.clearCallingIdentity();
try {
mAppCallback.onAvailableSaisUpdated(currentSais, availableSais);
} finally {
Binder.restoreCallingIdentity(token);
}
}
});
}
@Override
public void onServiceInterfaceAvailable(final String interfaceName, final int index) {
if (mIsStopped) {
return;
}
mExecutor.execute(new Runnable() {
@Override
public void run() {
long token = Binder.clearCallingIdentity();
try {
mAppCallback.onServiceInterfaceAvailable(interfaceName, index);
} finally {
Binder.restoreCallingIdentity(token);
}
}
});
}
@Override
public void onMiddlewareReady() {
if (mIsStopped) {
return;
}
mExecutor.execute(new Runnable() {
@Override
public void run() {
long token = Binder.clearCallingIdentity();
try {
mAppCallback.onMiddlewareReady();
} finally {
Binder.restoreCallingIdentity(token);
}
}
});
}
/** Prevents this callback from calling the app */
public void stop() {
mIsStopped = true;
}
}

View File

@@ -0,0 +1,99 @@
/*
* 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.mbms;
import android.annotation.IntDef;
import android.annotation.Nullable;
import android.content.Context;
import android.telephony.MbmsGroupCallSession;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List;
import java.util.concurrent.Executor;
/**
* A callback class that is used to receive information from the middleware on MBMS group-call
* services. An instance of this object should be passed into
* {@link MbmsGroupCallSession#create(Context, Executor, int, MbmsGroupCallSessionCallback)}.
*/
public class MbmsGroupCallSessionCallback {
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(value = {
MbmsErrors.ERROR_NO_UNIQUE_MIDDLEWARE,
MbmsErrors.ERROR_MIDDLEWARE_LOST,
MbmsErrors.ERROR_MIDDLEWARE_NOT_BOUND,
MbmsErrors.InitializationErrors.ERROR_APP_PERMISSIONS_NOT_GRANTED,
MbmsErrors.InitializationErrors.ERROR_DUPLICATE_INITIALIZE,
MbmsErrors.InitializationErrors.ERROR_UNABLE_TO_INITIALIZE,
MbmsErrors.GeneralErrors.ERROR_MIDDLEWARE_NOT_YET_READY,
MbmsErrors.GeneralErrors.ERROR_OUT_OF_MEMORY,
MbmsErrors.GeneralErrors.ERROR_MIDDLEWARE_TEMPORARILY_UNAVAILABLE,
MbmsErrors.GeneralErrors.ERROR_IN_E911,
MbmsErrors.GeneralErrors.ERROR_NOT_CONNECTED_TO_HOME_CARRIER_LTE,
MbmsErrors.GeneralErrors.ERROR_UNABLE_TO_READ_SIM,
MbmsErrors.GeneralErrors.ERROR_CARRIER_CHANGE_NOT_ALLOWED}, prefix = { "ERROR_" })
private @interface GroupCallError{}
/**
* Called by the middleware when it has detected an error condition. The possible error codes
* are listed in {@link MbmsErrors}.
* @param errorCode The error code.
* @param message A human-readable message generated by the middleware for debugging purposes.
*/
public void onError(@GroupCallError int errorCode, @Nullable String message) {
}
/**
* Indicates that the list of currently available SAIs has been updated. The app may use this
* information to filter the list of group calls when displaying available group calls to
* the user by matching the SAIs with a list of group calls separately negotiated with the
* carrier. The app may also report the aggregate list of SAIs to the group call application
* server so that a network entity can determine when, and where to activate the broadcast of
* particular group calls.
* @param currentSais The available SAIs on the current cell.
* @param availableSais A list of lists of available SAIS in neighboring cells, where each list
* contains the available SAIs in an individual cell.
*/
public void onAvailableSaisUpdated(List<Integer> currentSais,
List<List<Integer>> availableSais) {
}
/**
* Called soon after the app calls {@link MbmsGroupCallSession#create}. The information supplied
* via this callback may be used to establish a data-link interface with the modem before the
* middleware is ready.
* Note that this method may be called before {@link #onMiddlewareReady()}.
*
* @param interfaceName The interface name for the data link.
* @param index The index for the data link.
*/
public void onServiceInterfaceAvailable(String interfaceName, int index) {
}
/**
* Called to indicate that the middleware has been initialized and is ready.
*
* Before this method is called, calling any method on an instance of
* {@link MbmsGroupCallSession} will result in an {@link IllegalStateException} or an error
* delivered via {@link #onError(int, String)} with error code
* {@link MbmsErrors.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY}.
*/
public void onMiddlewareReady() {
}
}

View File

@@ -23,6 +23,7 @@ import android.content.ServiceConnection;
import android.content.pm.*;
import android.content.pm.ServiceInfo;
import android.telephony.MbmsDownloadSession;
import android.telephony.MbmsGroupCallSession;
import android.telephony.MbmsStreamingSession;
import android.util.Log;
@@ -59,6 +60,9 @@ public class MbmsUtils {
case MbmsStreamingSession.MBMS_STREAMING_SERVICE_ACTION:
metaDataKey = MbmsStreamingSession.MBMS_STREAMING_SERVICE_OVERRIDE_METADATA;
break;
case MbmsGroupCallSession.MBMS_GROUP_CALL_SERVICE_ACTION:
metaDataKey = MbmsGroupCallSession.MBMS_GROUP_CALL_SERVICE_OVERRIDE_METADATA;
break;
}
if (metaDataKey == null) {
return null;

View File

@@ -0,0 +1,39 @@
/*
** Copyright 2017, 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.mbms.vendor;
import android.net.Uri;
import android.telephony.mbms.IMbmsGroupCallSessionCallback;
import android.telephony.mbms.IGroupCallCallback;
/**
* @hide
*/
interface IMbmsGroupCallService
{
int initialize(IMbmsGroupCallSessionCallback callback, int subId);
void stopGroupCall(int subId, long tmgi);
void updateGroupCall(int subscriptionId, long tmgi, in int[] saiArray,
in int[] frequencyArray);
int startGroupCall(int subscriptionId, long tmgi, in int[] saiArray,
in int[] frequencyArray, IGroupCallCallback callback);
void dispose(int subId);
}

View File

@@ -0,0 +1,275 @@
/*
* 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.mbms.vendor;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.telephony.mbms.GroupCallCallback;
import android.telephony.mbms.IGroupCallCallback;
import android.telephony.mbms.IMbmsGroupCallSessionCallback;
import android.telephony.mbms.MbmsErrors;
import android.telephony.mbms.MbmsGroupCallSessionCallback;
import android.telephony.mbms.vendor.IMbmsGroupCallService.Stub;
import java.util.List;
/**
* Base class for MBMS group-call services. The middleware should override this class to implement
* its {@link Service} for group calls
* Subclasses should call this class's {@link #onBind} method to obtain an {@link IBinder} if they
* override {@link #onBind}.
* @hide
*/
@SystemApi
@TestApi
public class MbmsGroupCallServiceBase extends Service {
private final IBinder mInterface = new Stub() {
@Override
public int initialize(final IMbmsGroupCallSessionCallback callback,
final int subscriptionId) throws RemoteException {
if (callback == null) {
throw new NullPointerException("Callback must not be null");
}
final int uid = Binder.getCallingUid();
int result = MbmsGroupCallServiceBase.this.initialize(
new MbmsGroupCallSessionCallback() {
@Override
public void onError(final int errorCode, final String message) {
try {
if (errorCode == MbmsErrors.UNKNOWN) {
throw new IllegalArgumentException(
"Middleware cannot send an unknown error.");
}
callback.onError(errorCode, message);
} catch (RemoteException e) {
onAppCallbackDied(uid, subscriptionId);
}
}
@Override
public void onAvailableSaisUpdated(final List currentSais,
final List availableSais) {
try {
callback.onAvailableSaisUpdated(currentSais, availableSais);
} catch (RemoteException e) {
onAppCallbackDied(uid, subscriptionId);
}
}
@Override
public void onServiceInterfaceAvailable(final String interfaceName,
final int index) {
try {
callback.onServiceInterfaceAvailable(interfaceName, index);
} catch (RemoteException e) {
onAppCallbackDied(uid, subscriptionId);
}
}
@Override
public void onMiddlewareReady() {
try {
callback.onMiddlewareReady();
} catch (RemoteException e) {
onAppCallbackDied(uid, subscriptionId);
}
}
}, subscriptionId);
if (result == MbmsErrors.SUCCESS) {
callback.asBinder().linkToDeath(new DeathRecipient() {
@Override
public void binderDied() {
onAppCallbackDied(uid, subscriptionId);
}
}, 0);
}
return result;
}
@Override
public void stopGroupCall(int subId, long tmgi) {
MbmsGroupCallServiceBase.this.stopGroupCall(subId, tmgi);
}
@Override
public void updateGroupCall(int subscriptionId, long tmgi, int[] saiArray,
int[] frequencyArray) {
MbmsGroupCallServiceBase.this.updateGroupCall(
subscriptionId, tmgi, saiArray, frequencyArray);
}
@Override
public int startGroupCall(final int subscriptionId, final long tmgi, final int[] saiArray,
final int[] frequencyArray, final IGroupCallCallback callback)
throws RemoteException {
if (callback == null) {
throw new NullPointerException("Callback must not be null");
}
final int uid = Binder.getCallingUid();
int result = MbmsGroupCallServiceBase.this.startGroupCall(
subscriptionId, tmgi, saiArray, frequencyArray, new GroupCallCallback() {
@Override
public void onError(final int errorCode, final String message) {
try {
if (errorCode == MbmsErrors.UNKNOWN) {
throw new IllegalArgumentException(
"Middleware cannot send an unknown error.");
}
callback.onError(errorCode, message);
} catch (RemoteException e) {
onAppCallbackDied(uid, subscriptionId);
}
}
public void onGroupCallStateChanged(int state, int reason) {
try {
callback.onGroupCallStateChanged(state, reason);
} catch (RemoteException e) {
onAppCallbackDied(uid, subscriptionId);
}
}
public void onBroadcastSignalStrengthUpdated(int signalStrength) {
try {
callback.onBroadcastSignalStrengthUpdated(signalStrength);
} catch (RemoteException e) {
onAppCallbackDied(uid, subscriptionId);
}
}
});
if (result == MbmsErrors.SUCCESS) {
callback.asBinder().linkToDeath(new DeathRecipient() {
@Override
public void binderDied() {
onAppCallbackDied(uid, subscriptionId);
}
}, 0);
}
return result;
}
@Override
public void dispose(int subId) throws RemoteException {
MbmsGroupCallServiceBase.this.dispose(subId);
}
};
/**
* Initialize the group call service for this app and subscription ID, registering the callback.
*
* May throw an {@link IllegalArgumentException} or a {@link SecurityException}, which
* will be intercepted and passed to the app as
* {@link MbmsErrors.InitializationErrors#ERROR_UNABLE_TO_INITIALIZE}
*
* May return any value from {@link MbmsErrors.InitializationErrors}
* or {@link MbmsErrors#SUCCESS}. Non-successful error codes will be passed to the app via
* {@link IMbmsGroupCallSessionCallback#onError(int, String)}.
*
* @param callback The callback to use to communicate with the app.
* @param subscriptionId The subscription ID to use.
*/
public int initialize(MbmsGroupCallSessionCallback callback, int subscriptionId)
throws RemoteException {
throw new UnsupportedOperationException("Not implemented");
}
/**
* Starts a particular group call. This method may perform asynchronous work. When
* the call is ready for consumption, the middleware should inform the app via
* {@link IGroupCallCallback#onGroupCallStateChanged(int, int)}.
*
* May throw an {@link IllegalArgumentException} or an {@link IllegalStateException}
*
* @param subscriptionId The subscription id to use.
* @param tmgi The TMGI, an identifier for the group call.
* @param saiArray An array of SAIs for the group call.
* @param frequencyArray An array of frequencies for the group call.
* @param callback The callback object on which the app wishes to receive updates.
* @return Any error in {@link MbmsErrors.GeneralErrors}
*/
public int startGroupCall(int subscriptionId, long tmgi, int[] saiArray, int[] frequencyArray,
GroupCallCallback callback) {
throw new UnsupportedOperationException("Not implemented");
}
/**
* Stop the group call identified by {@code tmgi}.
*
* The callback provided via {@link #startGroupCall} should no longer be
* used after this method has called by the app.
*
* May throw an {@link IllegalStateException}
*
* @param subscriptionId The subscription id to use.
* @param tmgi The TMGI for the call to stop.
*/
public void stopGroupCall(int subscriptionId, long tmgi) {
throw new UnsupportedOperationException("Not implemented");
}
/**
* Called when the app receives new SAI and frequency information for the group call identified
* by {@code tmgi}.
* @param saiArray New array of SAIs that the call is available on.
* @param frequencyArray New array of frequencies that the call is available on.
*/
public void updateGroupCall(int subscriptionId, long tmgi, int[] saiArray,
int[] frequencyArray) {
throw new UnsupportedOperationException("Not implemented");
}
/**
* Signals that the app wishes to dispose of the session identified by the
* {@code subscriptionId} argument and the caller's uid. No notification back to the
* app is required for this operation, and the corresponding callback provided via
* {@link #initialize} should no longer be used
* after this method has been called by the app.
*
* May throw an {@link IllegalStateException}
*
* @param subscriptionId The subscription id to use.
*/
public void dispose(int subscriptionId) throws RemoteException {
throw new UnsupportedOperationException("Not implemented");
}
/**
* Indicates that the app identified by the given UID and subscription ID has died.
* @param uid the UID of the app, as returned by {@link Binder#getCallingUid()}.
* @param subscriptionId The subscription ID the app is using.
*/
public void onAppCallbackDied(int uid, int subscriptionId) {
}
@Override
public IBinder onBind(Intent intent) {
return mInterface;
}
}