1/*
2 * Copyright (C) 2017 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 com.android.phone.testapps.embmsmw;
18
19import android.app.Service;
20import android.content.Intent;
21import android.net.Uri;
22import android.os.Binder;
23import android.os.Handler;
24import android.os.HandlerThread;
25import android.os.IBinder;
26import android.telephony.mbms.MbmsErrors;
27import android.telephony.mbms.MbmsStreamingSessionCallback;
28import android.telephony.mbms.StreamingService;
29import android.telephony.mbms.StreamingServiceCallback;
30import android.telephony.mbms.StreamingServiceInfo;
31import android.telephony.mbms.vendor.MbmsStreamingServiceBase;
32import android.util.Log;
33
34import com.android.internal.os.SomeArgs;
35
36import java.util.Arrays;
37import java.util.HashMap;
38import java.util.HashSet;
39import java.util.List;
40import java.util.Map;
41import java.util.Set;
42
43public class EmbmsTestStreamingService extends Service {
44    private static final Set<String> ALLOWED_PACKAGES = new HashSet<String>() {{
45        add("com.android.phone.testapps.embmsfrontend");
46    }};
47
48    private static final String TAG = "EmbmsTestStreaming";
49
50    private static final long INITIALIZATION_DELAY = 200;
51    private static final long SEND_SERVICE_LIST_DELAY = 300;
52    private static final long START_STREAMING_DELAY = 500;
53
54    private static final int SEND_STREAMING_SERVICES_LIST = 1;
55
56    private final Map<FrontendAppIdentifier, MbmsStreamingSessionCallback> mAppCallbacks =
57            new HashMap<>();
58
59    private HandlerThread mHandlerThread;
60    private Handler mHandler;
61    private Handler.Callback mWorkerCallback = (msg) -> {
62        switch (msg.what) {
63            case SEND_STREAMING_SERVICES_LIST:
64                SomeArgs args = (SomeArgs) msg.obj;
65                FrontendAppIdentifier appKey = (FrontendAppIdentifier) args.arg1;
66                List<StreamingServiceInfo> services = (List) args.arg2;
67                MbmsStreamingSessionCallback appCallback = mAppCallbacks.get(appKey);
68                if (appCallback != null) {
69                    appCallback.onStreamingServicesUpdated(services);
70                }
71                break;
72        }
73        return true;
74    };
75
76    private final MbmsStreamingServiceBase mBinder = new MbmsStreamingServiceBase() {
77        @Override
78        public int initialize(MbmsStreamingSessionCallback callback, int subId) {
79            int packageUid = Binder.getCallingUid();
80            String[] packageNames = getPackageManager().getPackagesForUid(packageUid);
81            if (packageNames == null) {
82                return MbmsErrors.InitializationErrors.ERROR_APP_PERMISSIONS_NOT_GRANTED;
83            }
84            boolean isUidAllowed = Arrays.stream(packageNames).anyMatch(ALLOWED_PACKAGES::contains);
85            if (!isUidAllowed) {
86                return MbmsErrors.InitializationErrors.ERROR_APP_PERMISSIONS_NOT_GRANTED;
87            }
88
89            mHandler.postDelayed(() -> {
90                FrontendAppIdentifier appKey = new FrontendAppIdentifier(packageUid, subId);
91                if (!mAppCallbacks.containsKey(appKey)) {
92                    mAppCallbacks.put(appKey, callback);
93                } else {
94                    callback.onError(
95                            MbmsErrors.InitializationErrors.ERROR_DUPLICATE_INITIALIZE, "");
96                    return;
97                }
98                callback.onMiddlewareReady();
99            }, INITIALIZATION_DELAY);
100            return MbmsErrors.SUCCESS;
101        }
102
103        @Override
104        public int requestUpdateStreamingServices(int subscriptionId, List<String> serviceClasses) {
105            FrontendAppIdentifier appKey =
106                    new FrontendAppIdentifier(Binder.getCallingUid(), subscriptionId);
107            checkInitialized(appKey);
108
109            List<StreamingServiceInfo> serviceInfos =
110                    StreamingServiceRepository.getStreamingServicesForClasses(serviceClasses);
111
112            SomeArgs args = SomeArgs.obtain();
113            args.arg1 = appKey;
114            args.arg2 = serviceInfos;
115
116            mHandler.removeMessages(SEND_STREAMING_SERVICES_LIST);
117            mHandler.sendMessageDelayed(
118                    mHandler.obtainMessage(SEND_STREAMING_SERVICES_LIST, args),
119                    SEND_SERVICE_LIST_DELAY);
120            return MbmsErrors.SUCCESS;
121        }
122
123        @Override
124        public int startStreaming(int subscriptionId, String serviceId,
125                StreamingServiceCallback callback) {
126            FrontendAppIdentifier appKey =
127                    new FrontendAppIdentifier(Binder.getCallingUid(), subscriptionId);
128            checkInitialized(appKey);
129            checkServiceExists(serviceId);
130
131            if (StreamStateTracker.getStreamingState(appKey, serviceId) ==
132                    StreamingService.STATE_STARTED) {
133                return MbmsErrors.StreamingErrors.ERROR_DUPLICATE_START_STREAM;
134            }
135
136            mHandler.postDelayed(
137                    () -> StreamStateTracker.startStreaming(appKey, serviceId, callback,
138                            StreamingService.REASON_BY_USER_REQUEST),
139                    START_STREAMING_DELAY);
140            return MbmsErrors.SUCCESS;
141        }
142
143        @Override
144        public Uri getPlaybackUri(int subscriptionId, String serviceId) {
145            FrontendAppIdentifier appKey =
146                    new FrontendAppIdentifier(Binder.getCallingUid(), subscriptionId);
147            checkInitialized(appKey);
148            checkServiceExists(serviceId);
149
150            Uri streamingUri = StreamingServiceRepository.getUriForService(serviceId);
151            if (streamingUri == null) {
152                throw new IllegalArgumentException("Invalid service ID");
153            }
154            return streamingUri;
155        }
156
157        @Override
158        public void stopStreaming(int subscriptionId, String serviceId) {
159            FrontendAppIdentifier appKey =
160                    new FrontendAppIdentifier(Binder.getCallingUid(), subscriptionId);
161            checkInitialized(appKey);
162            checkServiceExists(serviceId);
163
164            mHandler.post(() -> StreamStateTracker.stopStreaming(appKey, serviceId,
165                    StreamingService.REASON_BY_USER_REQUEST));
166            StreamStateTracker.dispose(appKey, serviceId);
167        }
168
169        @Override
170        public void dispose(int subscriptionId) {
171            FrontendAppIdentifier appKey =
172                    new FrontendAppIdentifier(Binder.getCallingUid(), subscriptionId);
173            checkInitialized(appKey);
174
175            Log.i(TAG, "Disposing app with uid " + Binder.getCallingUid());
176            StreamStateTracker.disposeAll(appKey);
177            mAppCallbacks.remove(appKey);
178        }
179
180        @Override
181        public void onAppCallbackDied(int uid, int subscriptionId) {
182            FrontendAppIdentifier appKey = new FrontendAppIdentifier(uid, subscriptionId);
183
184            Log.i(TAG, "Disposing app " + appKey + " due to binder death");
185            StreamStateTracker.disposeAll(appKey);
186            mAppCallbacks.remove(appKey);
187        }
188    };
189
190    @Override
191    public void onDestroy() {
192        super.onCreate();
193        mHandlerThread.quitSafely();
194        logd("EmbmsTestStreamingService onDestroy");
195    }
196
197    @Override
198    public IBinder onBind(Intent intent) {
199        logd("EmbmsTestStreamingService onBind");
200        mHandlerThread = new HandlerThread("EmbmsTestStreamingServiceWorker");
201        mHandlerThread.start();
202        mHandler = new Handler(mHandlerThread.getLooper(), mWorkerCallback);
203        return mBinder;
204    }
205
206    private static void logd(String s) {
207        Log.d(TAG, s);
208    }
209
210    private void checkInitialized(FrontendAppIdentifier appKey) {
211        if (!mAppCallbacks.containsKey(appKey)) {
212            throw new IllegalStateException("Not yet initialized");
213        }
214    }
215
216    private void checkServiceExists(String serviceId) {
217        if (StreamingServiceRepository.getStreamingServiceInfoForId(serviceId) == null) {
218            throw new IllegalArgumentException("Invalid service ID");
219        }
220    }
221}
222