blob: d54071f28be962e4bd7028b50425a0d412c9d94e [file] [log] [blame]
Hall Liua7b0c1f2018-04-19 17:57:58 -07001/*
2 * Copyright (C) 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.telephony;
18
19import android.annotation.NonNull;
20import android.annotation.Nullable;
21import android.annotation.SdkConstant;
22import android.annotation.SystemApi;
23import android.annotation.TestApi;
24import android.content.ComponentName;
25import android.content.Context;
26import android.content.ServiceConnection;
27import android.os.IBinder;
28import android.os.RemoteException;
29import android.telephony.mbms.GroupCall;
30import android.telephony.mbms.GroupCallCallback;
31import android.telephony.mbms.InternalGroupCallCallback;
32import android.telephony.mbms.InternalGroupCallSessionCallback;
33import android.telephony.mbms.MbmsErrors;
34import android.telephony.mbms.MbmsGroupCallSessionCallback;
35import android.telephony.mbms.MbmsUtils;
36import android.telephony.mbms.vendor.IMbmsGroupCallService;
37import android.util.ArraySet;
38import android.util.Log;
39
Hall Liuc81cba42018-10-08 18:23:53 -070040import java.util.List;
Hall Liua7b0c1f2018-04-19 17:57:58 -070041import java.util.Set;
42import java.util.concurrent.Executor;
43import java.util.concurrent.atomic.AtomicBoolean;
44import java.util.concurrent.atomic.AtomicReference;
45
46/**
47 * This class provides functionality for accessing group call functionality over MBMS.
48 */
49public class MbmsGroupCallSession implements AutoCloseable {
50 private static final String LOG_TAG = "MbmsGroupCallSession";
51
52 /**
53 * Service action which must be handled by the middleware implementing the MBMS group call
54 * interface.
55 * @hide
56 */
57 @SystemApi
58 @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
59 public static final String MBMS_GROUP_CALL_SERVICE_ACTION =
60 "android.telephony.action.EmbmsGroupCall";
61
62 /**
63 * Metadata key that specifies the component name of the service to bind to for group calls.
64 * @hide
65 */
66 @TestApi
67 public static final String MBMS_GROUP_CALL_SERVICE_OVERRIDE_METADATA =
68 "mbms-group-call-service-override";
69
70 private static AtomicBoolean sIsInitialized = new AtomicBoolean(false);
71
72 private AtomicReference<IMbmsGroupCallService> mService = new AtomicReference<>(null);
73 private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
74 @Override
75 public void binderDied() {
76 sIsInitialized.set(false);
77 mInternalCallback.onError(MbmsErrors.ERROR_MIDDLEWARE_LOST,
78 "Received death notification");
79 }
80 };
81
82 private InternalGroupCallSessionCallback mInternalCallback;
Hall Liu718986d2019-08-29 15:42:47 -070083 private ServiceConnection mServiceConnection;
Hall Liua7b0c1f2018-04-19 17:57:58 -070084 private Set<GroupCall> mKnownActiveGroupCalls = new ArraySet<>();
85
86 private final Context mContext;
87 private int mSubscriptionId;
88
89 /** @hide */
90 private MbmsGroupCallSession(Context context, Executor executor, int subscriptionId,
91 MbmsGroupCallSessionCallback callback) {
92 mContext = context;
93 mSubscriptionId = subscriptionId;
94 mInternalCallback = new InternalGroupCallSessionCallback(callback, executor);
95 }
96
97 /**
98 * Create a new {@link MbmsGroupCallSession} using the given subscription ID.
99 *
100 * You may only have one instance of {@link MbmsGroupCallSession} per UID. If you call this
101 * method while there is an active instance of {@link MbmsGroupCallSession} in your process
102 * (in other words, one that has not had {@link #close()} called on it), this method will
103 * throw an {@link IllegalStateException}. If you call this method in a different process
104 * running under the same UID, an error will be indicated via
105 * {@link MbmsGroupCallSessionCallback#onError(int, String)}.
106 *
107 * Note that initialization may fail asynchronously. If you wish to try again after you
108 * receive such an asynchronous error, you must call {@link #close()} on the instance of
109 * {@link MbmsGroupCallSession} that you received before calling this method again.
110 *
111 * @param context The {@link Context} to use.
Hall Liua7b0c1f2018-04-19 17:57:58 -0700112 * @param subscriptionId The subscription ID to use.
Hall Liuc81cba42018-10-08 18:23:53 -0700113 * @param executor The executor on which you wish to execute callbacks.
Hall Liua7b0c1f2018-04-19 17:57:58 -0700114 * @param callback A callback object on which you wish to receive results of asynchronous
115 * operations.
116 * @return An instance of {@link MbmsGroupCallSession}, or null if an error occurred.
117 */
118 public static @Nullable MbmsGroupCallSession create(@NonNull Context context,
Hall Liuc81cba42018-10-08 18:23:53 -0700119 int subscriptionId, @NonNull Executor executor,
Hall Liua7b0c1f2018-04-19 17:57:58 -0700120 final @NonNull MbmsGroupCallSessionCallback callback) {
121 if (!sIsInitialized.compareAndSet(false, true)) {
122 throw new IllegalStateException("Cannot create two instances of MbmsGroupCallSession");
123 }
124 MbmsGroupCallSession session = new MbmsGroupCallSession(context, executor,
125 subscriptionId, callback);
126
127 final int result = session.bindAndInitialize();
128 if (result != MbmsErrors.SUCCESS) {
129 sIsInitialized.set(false);
130 executor.execute(new Runnable() {
131 @Override
132 public void run() {
133 callback.onError(result, null);
134 }
135 });
136 return null;
137 }
138 return session;
139 }
140
141 /**
142 * Create a new {@link MbmsGroupCallSession} using the system default data subscription ID.
Hall Liuc81cba42018-10-08 18:23:53 -0700143 * See {@link #create(Context, int, Executor, MbmsGroupCallSessionCallback)}.
Hall Liua7b0c1f2018-04-19 17:57:58 -0700144 */
Hall Liub22bfe42019-03-04 17:44:27 -0800145 public static @Nullable MbmsGroupCallSession create(@NonNull Context context,
Hall Liua7b0c1f2018-04-19 17:57:58 -0700146 @NonNull Executor executor, @NonNull MbmsGroupCallSessionCallback callback) {
Hall Liuc81cba42018-10-08 18:23:53 -0700147 return create(context, SubscriptionManager.getDefaultSubscriptionId(), executor, callback);
Hall Liua7b0c1f2018-04-19 17:57:58 -0700148 }
149
150 /**
151 * Terminates this instance. Also terminates
152 * any group calls spawned from this instance as if
153 * {@link GroupCall#close()} had been called on them. After this method returns,
154 * no further callbacks originating from the middleware will be enqueued on the provided
155 * instance of {@link MbmsGroupCallSessionCallback}, but callbacks that have already been
156 * enqueued will still be delivered.
157 *
Hall Liuc81cba42018-10-08 18:23:53 -0700158 * It is safe to call {@link #create(Context, int, Executor, MbmsGroupCallSessionCallback)} to
Hall Liua7b0c1f2018-04-19 17:57:58 -0700159 * obtain another instance of {@link MbmsGroupCallSession} immediately after this method
160 * returns.
161 *
162 * May throw an {@link IllegalStateException}
163 */
164 public void close() {
165 try {
166 IMbmsGroupCallService groupCallService = mService.get();
Hall Liu718986d2019-08-29 15:42:47 -0700167 if (groupCallService == null || mServiceConnection == null) {
Hall Liua7b0c1f2018-04-19 17:57:58 -0700168 // Ignore and return, assume already disposed.
169 return;
170 }
171 groupCallService.dispose(mSubscriptionId);
172 for (GroupCall s : mKnownActiveGroupCalls) {
173 s.getCallback().stop();
174 }
175 mKnownActiveGroupCalls.clear();
Hall Liu718986d2019-08-29 15:42:47 -0700176 mContext.unbindService(mServiceConnection);
Hall Liua7b0c1f2018-04-19 17:57:58 -0700177 } catch (RemoteException e) {
178 // Ignore for now
179 } finally {
180 mService.set(null);
181 sIsInitialized.set(false);
Hall Liu718986d2019-08-29 15:42:47 -0700182 mServiceConnection = null;
Hall Liua7b0c1f2018-04-19 17:57:58 -0700183 mInternalCallback.stop();
184 }
185 }
186
187 /**
188 * Starts the requested group call, reporting status to the indicated callback.
189 * Returns an object used to control that call.
190 *
191 * May throw an {@link IllegalArgumentException} or an {@link IllegalStateException}
192 *
193 * Asynchronous errors through the callback include any of the errors in
194 * {@link MbmsErrors.GeneralErrors}.
195 *
Hall Liua7b0c1f2018-04-19 17:57:58 -0700196 * @param tmgi The TMGI, an identifier for the group call you want to join.
Hall Liuc81cba42018-10-08 18:23:53 -0700197 * @param saiList A list of SAIs for the group call that should be negotiated separately with
Hall Liua7b0c1f2018-04-19 17:57:58 -0700198 * the carrier.
Hall Liuc81cba42018-10-08 18:23:53 -0700199 * @param frequencyList A lost of frequencies for the group call that should be negotiated
Hall Liua7b0c1f2018-04-19 17:57:58 -0700200 * separately with the carrier.
Hall Liuc81cba42018-10-08 18:23:53 -0700201 * @param executor The executor on which you wish to execute callbacks for this stream.
Hall Liua7b0c1f2018-04-19 17:57:58 -0700202 * @param callback The callback that you want to receive information about the call on.
203 * @return An instance of {@link GroupCall} through which the call can be controlled.
204 * May be {@code null} if an error occurred.
205 */
Hall Liuc81cba42018-10-08 18:23:53 -0700206 public @Nullable GroupCall startGroupCall(long tmgi, @NonNull List<Integer> saiList,
207 @NonNull List<Integer> frequencyList, @NonNull Executor executor,
208 @NonNull GroupCallCallback callback) {
Hall Liua7b0c1f2018-04-19 17:57:58 -0700209 IMbmsGroupCallService groupCallService = mService.get();
210 if (groupCallService == null) {
211 throw new IllegalStateException("Middleware not yet bound");
212 }
213
214 InternalGroupCallCallback serviceCallback = new InternalGroupCallCallback(
215 callback, executor);
216
217 GroupCall serviceForApp = new GroupCall(mSubscriptionId,
218 groupCallService, this, tmgi, serviceCallback);
219 mKnownActiveGroupCalls.add(serviceForApp);
220
221 try {
222 int returnCode = groupCallService.startGroupCall(
Hall Liuc81cba42018-10-08 18:23:53 -0700223 mSubscriptionId, tmgi, saiList, frequencyList, serviceCallback);
Hall Liua7b0c1f2018-04-19 17:57:58 -0700224 if (returnCode == MbmsErrors.UNKNOWN) {
225 // Unbind and throw an obvious error
226 close();
227 throw new IllegalStateException("Middleware must not return an unknown error code");
228 }
229 if (returnCode != MbmsErrors.SUCCESS) {
230 mInternalCallback.onError(returnCode, null);
231 return null;
232 }
233 } catch (RemoteException e) {
234 Log.w(LOG_TAG, "Remote process died");
235 mService.set(null);
236 sIsInitialized.set(false);
237 mInternalCallback.onError(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
238 return null;
239 }
240
241 return serviceForApp;
242 }
243
244 /** @hide */
245 public void onGroupCallStopped(GroupCall service) {
246 mKnownActiveGroupCalls.remove(service);
247 }
248
249 private int bindAndInitialize() {
Hall Liu718986d2019-08-29 15:42:47 -0700250 mServiceConnection = new ServiceConnection() {
251 @Override
252 public void onServiceConnected(ComponentName name, IBinder service) {
253 IMbmsGroupCallService groupCallService =
254 IMbmsGroupCallService.Stub.asInterface(service);
255 int result;
256 try {
257 result = groupCallService.initialize(mInternalCallback,
258 mSubscriptionId);
259 } catch (RemoteException e) {
260 Log.e(LOG_TAG, "Service died before initialization");
261 mInternalCallback.onError(
262 MbmsErrors.InitializationErrors.ERROR_UNABLE_TO_INITIALIZE,
263 e.toString());
264 sIsInitialized.set(false);
265 return;
266 } catch (RuntimeException e) {
267 Log.e(LOG_TAG, "Runtime exception during initialization");
268 mInternalCallback.onError(
269 MbmsErrors.InitializationErrors.ERROR_UNABLE_TO_INITIALIZE,
270 e.toString());
271 sIsInitialized.set(false);
272 return;
273 }
274 if (result == MbmsErrors.UNKNOWN) {
275 // Unbind and throw an obvious error
276 close();
277 throw new IllegalStateException("Middleware must not return"
278 + " an unknown error code");
279 }
280 if (result != MbmsErrors.SUCCESS) {
281 mInternalCallback.onError(result,
282 "Error returned during initialization");
283 sIsInitialized.set(false);
284 return;
285 }
286 try {
287 groupCallService.asBinder().linkToDeath(mDeathRecipient, 0);
288 } catch (RemoteException e) {
289 mInternalCallback.onError(MbmsErrors.ERROR_MIDDLEWARE_LOST,
290 "Middleware lost during initialization");
291 sIsInitialized.set(false);
292 return;
293 }
294 mService.set(groupCallService);
295 }
Hall Liua7b0c1f2018-04-19 17:57:58 -0700296
Hall Liu718986d2019-08-29 15:42:47 -0700297 @Override
298 public void onServiceDisconnected(ComponentName name) {
299 sIsInitialized.set(false);
300 mService.set(null);
301 }
302
303 @Override
304 public void onNullBinding(ComponentName name) {
305 Log.w(LOG_TAG, "bindAndInitialize: Remote service returned null");
306 mInternalCallback.onError(MbmsErrors.ERROR_MIDDLEWARE_LOST,
307 "Middleware service binding returned null");
308 sIsInitialized.set(false);
309 mService.set(null);
310 mContext.unbindService(this);
311 }
312 };
313 return MbmsUtils.startBinding(mContext, MBMS_GROUP_CALL_SERVICE_ACTION, mServiceConnection);
Hall Liua7b0c1f2018-04-19 17:57:58 -0700314 }
315}