1 /**
2  * Copyright (C) 2022 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 
17 package android.telephony.imsmedia;
18 
19 import android.content.ComponentName;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.ServiceConnection;
23 import android.os.IBinder;
24 import android.os.ParcelFileDescriptor;
25 import android.os.RemoteException;
26 import android.util.Log;
27 
28 import androidx.annotation.NonNull;
29 import androidx.annotation.Nullable;
30 import androidx.annotation.VisibleForTesting;
31 
32 import java.net.DatagramSocket;
33 import java.util.Objects;
34 import java.util.concurrent.Executor;
35 
36 
37 /**
38  * IMS media manager APIs to manage the IMS media sessions
39  *
40  * @hide
41  */
42 public class ImsMediaManager {
43     private static final String TAG = "ImsMediaManager";
44     @VisibleForTesting
45     protected static final String MEDIA_SERVICE_PACKAGE = "com.android.telephony.imsmedia";
46     @VisibleForTesting
47     protected static final String MEDIA_SERVICE_CLASS =
48             MEDIA_SERVICE_PACKAGE + ".ImsMediaController";
49     private final Context mContext;
50     private final OnConnectedCallback mOnConnectedCallback;
51     private final Executor mExecutor;
52     private final ServiceConnection mConnection;
53     private volatile IImsMedia mImsMedia;
54 
55     /**
56      * Opens a RTP session based on the local sockets with the associated initial
57      * remote configuration if there is a valid {@link RtpConfig} passed. It starts
58      * the media flow if the media direction in the {@link RtpConfig} is set to any
59      * value other than {@link RtpConfig#NO_MEDIA_FLOW}. If the open session is
60      * successful then a new {@link ImsMediaSession} object will be returned using
61      * the {@link SessionCallback#onOpenSessionSuccess(ImsMediaSession)} API. If the
62      * open session is failed then an error code {@link SessionOperationResult} will
63      * be returned using {@link SessionCallback#onOpenSessionFailure(int)} API.
64      *
65      * @param rtpSocket  local UDP socket to send and receive incoming RTP packets
66      * @param rtcpSocket local UDP socket to send and receive incoming RTCP packets
67      * @param rtpConfig  provides remote endpoint info and codec details.
68      *                   This could be null initially and the application may update
69      *                   this later using {@link ImsMediaSession#modifySession()}
70      *                   API.
71      * @param callback   callbacks to receive session specific notifications.
72      */
openSession(@onNull final DatagramSocket rtpSocket, @NonNull final DatagramSocket rtcpSocket, @NonNull final @ImsMediaSession.SessionType int sessionType, @Nullable final RtpConfig rtpConfig, @NonNull final Executor executor, @NonNull final SessionCallback callback)73     public void openSession(@NonNull final DatagramSocket rtpSocket,
74             @NonNull final DatagramSocket rtcpSocket,
75             @NonNull final @ImsMediaSession.SessionType int sessionType,
76             @Nullable final RtpConfig rtpConfig,
77             @NonNull final Executor executor,
78             @NonNull final SessionCallback callback) {
79         if (isConnected()) {
80             try {
81                 callback.setExecutor(executor);
82                 mImsMedia.openSession(ParcelFileDescriptor.fromDatagramSocket(rtpSocket),
83                         ParcelFileDescriptor.fromDatagramSocket(rtcpSocket), sessionType,
84                         rtpConfig, callback.getBinder());
85             } catch (RemoteException e) {
86                 Log.e(TAG, "Failed to openSession: " + e);
87                 // TODO throw exception or callback.onOpenSessionFailure()
88             }
89         }
90     }
91 
92     /**
93      * Closes the RTP session including cleanup of all the resources
94      * associated with the session. This will also close the session object
95      * and associated callback.
96      *
97      * @param session RTP session to be closed.
98      */
closeSession(@onNull ImsMediaSession session)99     public void closeSession(@NonNull ImsMediaSession session) {
100         if (isConnected()) {
101             try {
102                 mImsMedia.closeSession(session.getBinder());
103             } catch (RemoteException e) {
104                 Log.e(TAG, "Failed to closeSession: " + e);
105             }
106         }
107     }
108 
109     /**
110      * Generates the array of SPROP strings for the given array of video
111      * configurations and returns via IImsMediaCallback.
112      *
113      * @param videoConfigList array of video configuration for which sprop should be generated.
114      * @param callback Binder interface implemented by caller and called with array of generated
115      * sprop values.
116      **/
generateVideoSprop(@onNull VideoConfig[] videoConfigList, IBinder callback)117     public void generateVideoSprop(@NonNull VideoConfig[] videoConfigList, IBinder callback) {
118         if (isConnected()) {
119             try {
120                 mImsMedia.generateVideoSprop(videoConfigList, callback);
121             } catch (RemoteException e) {
122                 Log.e(TAG, "Failed to closeSession: " + e);
123             }
124         }
125     }
126 
127     /**
128      * Unbinds the service connection with ImsMediaService
129      */
release()130     public void release() {
131         // TODO: close all the open sessions
132         if (isConnected()) {
133             try {
134                 mContext.unbindService(mConnection);
135             } catch (IllegalArgumentException e) {
136                 Log.e(TAG, "IllegalArgumentException: " + e.toString());
137             }
138             mImsMedia = null;
139         }
140     }
141 
142     /**
143      * Interface to send call-backs to the application when the service is
144      * connected.
145      */
146     public interface OnConnectedCallback {
147         /**
148          * Called by the ImsMedia framework when the service is connected.
149          */
onConnected()150         void onConnected();
151 
152         /**
153          * Called by the ImsMedia framework when the service is disconnected.
154          */
onDisconnected()155         void onDisconnected();
156     }
157 
ImsMediaManager(@onNull Context context, @NonNull Executor executor, @NonNull OnConnectedCallback callback)158     public ImsMediaManager(@NonNull Context context, @NonNull Executor executor,
159             @NonNull OnConnectedCallback callback) {
160 
161         mContext = Objects.requireNonNull(context, "context cannot be null");
162         mExecutor = Objects.requireNonNull(executor, "executor cannot be null");
163         mOnConnectedCallback = Objects.requireNonNull(callback, "callback cannot be null");
164 
165         mConnection = new ServiceConnection() {
166 
167             public synchronized void onServiceConnected(
168                     ComponentName className, IBinder service) {
169 
170                 mImsMedia = IImsMedia.Stub.asInterface(service);
171                 Log.d(TAG, "onServiceConnected");
172                 mExecutor.execute(new Runnable() {
173                     @Override
174                     public void run() {
175                         mOnConnectedCallback.onConnected();
176                     }
177                 });
178             }
179 
180             public void onServiceDisconnected(ComponentName className) {
181                 Log.d(TAG, "onServiceDisconnected");
182                 mImsMedia = null;
183                 mExecutor.execute(new Runnable() {
184                     @Override
185                     public void run() {
186                         mOnConnectedCallback.onDisconnected();
187                     }
188                 });
189             }
190         };
191 
192         Intent intent = new Intent(IImsMedia.class.getName());
193         intent.setClassName(MEDIA_SERVICE_PACKAGE, MEDIA_SERVICE_CLASS);
194         boolean bindingSuccessful = mContext.bindService(intent, mConnection,
195                 Context.BIND_AUTO_CREATE);
196 
197         Log.d(TAG, "binding: " + bindingSuccessful);
198         if (bindingSuccessful) {
199             Log.d(TAG, "bindService successful");
200         }
201     }
202 
isConnected()203     private boolean isConnected() {
204         return mImsMedia != null;
205     }
206 
207     public static abstract class SessionCallback {
208         /** @hide */
getBinder()209         public IBinder getBinder() {
210             // Base Implementation
211             return null;
212         }
213 
214         /** @hide */
setExecutor(Executor executor)215         public void setExecutor(Executor executor) {
216             // Base Implementation
217         }
218 
219         /**
220          * Called when the session is opened successfully
221          *
222          * @param session session object
223          */
onOpenSessionSuccess(ImsMediaSession session)224         public void onOpenSessionSuccess(ImsMediaSession session) {
225             // Base Implementation
226         }
227 
228         /**
229          * Called when the open session fails
230          *
231          * @param error Error code
232          */
onOpenSessionFailure(int error)233         public void onOpenSessionFailure(int error) {
234             // Base Implementation
235         }
236 
237         /**
238          * Called when the session is closed.
239          */
onSessionClosed()240         public void onSessionClosed() {
241             // Base Implementation
242         }
243 
244     }
245 }
246