1 /*
2  * Copyright (C) 2015 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 androidx.appcompat.mms;
18 
19 import android.app.Service;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.os.Handler;
23 import android.os.IBinder;
24 import android.os.PowerManager;
25 import android.os.Process;
26 import android.telephony.SmsManager;
27 import android.util.Log;
28 
29 import java.util.concurrent.ExecutorService;
30 import java.util.concurrent.Executors;
31 import java.util.concurrent.RejectedExecutionException;
32 
33 /**
34  * Service to execute MMS requests using deprecated legacy APIs on older platform (prior to L)
35  */
36 public class MmsService extends Service {
37     static final String TAG = "MmsLib";
38 
39     //The default number of threads allowed to run MMS requests
40     private static final int DEFAULT_THREAD_POOL_SIZE = 4;
41     // Delay before stopping the service
42     private static final int SERVICE_STOP_DELAY_MILLIS = 2000;
43 
44     private static final String EXTRA_REQUEST = "request";
45     private static final String EXTRA_MYPID = "mypid";
46 
47     private static final String WAKELOCK_ID = "mmslib_wakelock";
48 
49     /**
50      * Thread pool size for each request queue
51      */
52     private static volatile int sThreadPoolSize = DEFAULT_THREAD_POOL_SIZE;
53 
54     /**
55      * Optional wake lock to use
56      */
57     private static volatile boolean sUseWakeLock = true;
58     private static volatile PowerManager.WakeLock sWakeLock = null;
59     private static final Object sWakeLockLock = new Object();
60 
61     /**
62      * Carrier configuration values loader
63      */
64     private static volatile CarrierConfigValuesLoader sCarrierConfigValuesLoader = null;
65 
66     /**
67      * APN loader
68      */
69     private static volatile ApnSettingsLoader sApnSettingsLoader = null;
70 
71     /**
72      * UserAgent and UA Prof URL loader
73      */
74     private static volatile UserAgentInfoLoader sUserAgentInfoLoader = null;
75 
76     /**
77      * Set the size of thread pool for request execution.
78      * Default is DEFAULT_THREAD_POOL_SIZE
79      *
80      * @param size thread pool size
81      */
setThreadPoolSize(final int size)82     static void setThreadPoolSize(final int size) {
83         sThreadPoolSize = size;
84     }
85 
86     /**
87      * Set whether to use wake lock
88      *
89      * @param useWakeLock true to use wake lock, false otherwise
90      */
setUseWakeLock(final boolean useWakeLock)91     static void setUseWakeLock(final boolean useWakeLock) {
92         sUseWakeLock = useWakeLock;
93     }
94 
95     /**
96      * Set the optional carrier config values
97      *
98      * @param loader the carrier config values loader
99      */
setCarrierConfigValuesLoader(final CarrierConfigValuesLoader loader)100     static void setCarrierConfigValuesLoader(final CarrierConfigValuesLoader loader) {
101         sCarrierConfigValuesLoader = loader;
102     }
103 
104     /**
105      * Get the current carrier config values loader
106      *
107      * @return the carrier config values loader currently set
108      */
getCarrierConfigValuesLoader()109     static CarrierConfigValuesLoader getCarrierConfigValuesLoader() {
110         return sCarrierConfigValuesLoader;
111     }
112 
113     /**
114      * Set APN settings loader
115      *
116      * @param loader the APN settings loader
117      */
setApnSettingsLoader(final ApnSettingsLoader loader)118     static void setApnSettingsLoader(final ApnSettingsLoader loader) {
119         sApnSettingsLoader = loader;
120     }
121 
122     /**
123      * Get the current APN settings loader
124      *
125      * @return the APN settings loader currently set
126      */
getApnSettingsLoader()127     static ApnSettingsLoader getApnSettingsLoader() {
128         return sApnSettingsLoader;
129     }
130 
131     /**
132      * Set user agent info loader
133      *
134      * @param loader the user agent info loader
135      */
setUserAgentInfoLoader(final UserAgentInfoLoader loader)136     static void setUserAgentInfoLoader(final UserAgentInfoLoader loader) {
137         sUserAgentInfoLoader = loader;
138     }
139 
140     /**
141      * Get the current user agent info loader
142      *
143      * @return the user agent info loader currently set
144      */
getUserAgentInfoLoader()145     static UserAgentInfoLoader getUserAgentInfoLoader() {
146         return sUserAgentInfoLoader;
147     }
148 
149     /**
150      * Make sure loaders are not null. Set to default if that's the case
151      *
152      * @param context the Context to use
153      */
ensureLoaders(final Context context)154     private static void ensureLoaders(final Context context) {
155         if (sUserAgentInfoLoader == null) {
156             sUserAgentInfoLoader = new DefaultUserAgentInfoLoader(context);
157         }
158         if (sCarrierConfigValuesLoader == null) {
159             sCarrierConfigValuesLoader = new DefaultCarrierConfigValuesLoader(context);
160         }
161         if (sApnSettingsLoader == null) {
162             sApnSettingsLoader = new DefaultApnSettingsLoader(context);
163         }
164     }
165 
166     /**
167      * Acquire the wake lock
168      *
169      * @param context the context to use
170      */
acquireWakeLock(final Context context)171     private static void acquireWakeLock(final Context context) {
172         synchronized (sWakeLockLock) {
173             if (sWakeLock == null) {
174                 final PowerManager pm =
175                         (PowerManager) context.getSystemService(Context.POWER_SERVICE);
176                 sWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_ID);
177             }
178             sWakeLock.acquire();
179         }
180     }
181 
182     /**
183      * Release the wake lock
184      */
releaseWakeLock()185     private static void releaseWakeLock() {
186         boolean releasedEmptyWakeLock = false;
187         synchronized (sWakeLockLock) {
188             if (sWakeLock != null) {
189                 sWakeLock.release();
190             } else {
191                 releasedEmptyWakeLock = true;
192             }
193         }
194         if (releasedEmptyWakeLock) {
195             Log.w(TAG, "Releasing empty wake lock");
196         }
197     }
198 
199     /**
200      * Check if wake lock is not held (e.g. when service stops)
201      */
verifyWakeLockNotHeld()202     private static void verifyWakeLockNotHeld() {
203         boolean wakeLockHeld = false;
204         synchronized (sWakeLockLock) {
205             wakeLockHeld = sWakeLock != null && sWakeLock.isHeld();
206         }
207         if (wakeLockHeld) {
208             Log.e(TAG, "Wake lock still held!");
209         }
210     }
211 
212     // Remember my PID to discard restarted intent
213     private static volatile int sMyPid = -1;
214 
215     /**
216      * Get the current PID
217      *
218      * @return the current PID
219      */
getMyPid()220     private static int getMyPid() {
221         if (sMyPid < 0) {
222             sMyPid = Process.myPid();
223         }
224         return sMyPid;
225     }
226 
227     /**
228      * Check if the intent is coming from this process
229      *
230      * @param intent the incoming intent for the service
231      * @return true if the intent is from the current process
232      */
fromThisProcess(final Intent intent)233     private static boolean fromThisProcess(final Intent intent) {
234         final int pid = intent.getIntExtra(EXTRA_MYPID, -1);
235         return pid == getMyPid();
236     }
237 
238     // Request execution thread pools. One thread pool for sending and one for downloading.
239     // The size of the thread pool controls the parallelism of request execution.
240     // See {@link setThreadPoolSize}
241     private ExecutorService[] mExecutors = new ExecutorService[2];
242 
243     // Active request count
244     private int mActiveRequestCount;
245     // The latest intent startId, used for safely stopping service
246     private int mLastStartId;
247 
248     private MmsNetworkManager mNetworkManager;
249 
250     // Handler for scheduling service stop
251     private final Handler mHandler = new Handler();
252     // Service stop task
253     private final Runnable mServiceStopRunnable = new Runnable() {
254         @Override
255         public void run() {
256             tryStopService();
257         }
258     };
259 
260     /**
261      * Start the service with a request
262      *
263      * @param context the Context to use
264      * @param request the request to start
265      */
startRequest(final Context context, final MmsRequest request)266     public static void startRequest(final Context context, final MmsRequest request) {
267         final boolean useWakeLock = sUseWakeLock;
268         request.setUseWakeLock(useWakeLock);
269         final Intent intent = new Intent(context, MmsService.class);
270         intent.putExtra(EXTRA_REQUEST, request);
271         intent.putExtra(EXTRA_MYPID, getMyPid());
272         if (useWakeLock) {
273             acquireWakeLock(context);
274         }
275         if (context.startService(intent) == null) {
276             if (useWakeLock) {
277                 releaseWakeLock();
278             }
279         }
280     }
281 
282     @Override
onCreate()283     public void onCreate() {
284         super.onCreate();
285 
286         ensureLoaders(this);
287 
288         for (int i = 0; i < mExecutors.length; i++) {
289             mExecutors[i] = Executors.newFixedThreadPool(sThreadPoolSize);
290         }
291 
292         mNetworkManager = new MmsNetworkManager(this);
293 
294         synchronized (this) {
295             mActiveRequestCount = 0;
296             mLastStartId = -1;
297         }
298     }
299 
300     @Override
onDestroy()301     public void onDestroy() {
302         super.onDestroy();
303 
304         for (ExecutorService executor : mExecutors) {
305             executor.shutdown();
306         }
307     }
308 
309     @Override
onStartCommand(Intent intent, int flags, int startId)310     public int onStartCommand(Intent intent, int flags, int startId) {
311         // Always remember the latest startId for use when we try releasing the service
312         synchronized (this) {
313             mLastStartId = startId;
314         }
315         boolean scheduled = false;
316         if (intent != null) {
317             // There is a rare situation that right after a intent is started,
318             // the service gets killed. Then the service will restart with
319             // the old intent which we don't want it to run since it will
320             // break our assumption for wake lock. Check the process ID
321             // embedded in the intent to make sure it is indeed from the
322             // the current life of this service.
323             if (fromThisProcess(intent)) {
324                 final MmsRequest request = intent.getParcelableExtra(EXTRA_REQUEST);
325                 if (request != null) {
326                     try {
327                         retainService(request, new Runnable() {
328                             @Override
329                             public void run() {
330                                 try {
331                                     request.execute(
332                                             MmsService.this,
333                                             mNetworkManager,
334                                             getApnSettingsLoader(),
335                                             getCarrierConfigValuesLoader(),
336                                             getUserAgentInfoLoader());
337                                 } catch (Exception e) {
338                                     Log.w(TAG, "Unexpected execution failure", e);
339                                 } finally {
340                                     if (request.getUseWakeLock()) {
341                                         releaseWakeLock();
342                                     }
343                                     releaseService();
344                                 }
345                             }
346                         });
347                         scheduled = true;
348                     } catch (RejectedExecutionException e) {
349                         // Rare thing happened. Send back failure using the pending intent
350                         // and also release the wake lock.
351                         Log.w(TAG, "Executing request failed " + e);
352                         request.returnResult(this, SmsManager.MMS_ERROR_UNSPECIFIED,
353                                 null/*response*/, 0/*httpStatusCode*/);
354                         if (request.getUseWakeLock()) {
355                             releaseWakeLock();
356                         }
357                     }
358                 } else {
359                     Log.w(TAG, "Empty request");
360                 }
361             } else {
362                 Log.w(TAG, "Got a restarted intent from previous incarnation");
363             }
364         } else {
365             Log.w(TAG, "Empty intent");
366         }
367         if (!scheduled) {
368             // If the request is not started successfully, we need to try shutdown the service
369             // if nobody is using it.
370             tryScheduleStop();
371         }
372         return START_NOT_STICKY;
373     }
374 
375     /**
376      * Retain the service for executing the request in service thread pool
377      *
378      * @param request The request to execute
379      * @param runnable The runnable to run the request in thread pool
380      */
retainService(final MmsRequest request, final Runnable runnable)381     private void retainService(final MmsRequest request, final Runnable runnable) {
382         final ExecutorService executor = getRequestExecutor(request);
383         synchronized (this) {
384             executor.execute(runnable);
385             mActiveRequestCount++;
386         }
387     }
388 
389     /**
390      * Release the service from the request. If nobody is using it, schedule service stop.
391      */
releaseService()392     private void releaseService() {
393         synchronized (this) {
394             mActiveRequestCount--;
395             if (mActiveRequestCount <= 0) {
396                 mActiveRequestCount = 0;
397                 rescheduleServiceStop();
398             }
399         }
400     }
401 
402     /**
403      * Schedule the service stop if there is no active request
404      */
tryScheduleStop()405     private void tryScheduleStop() {
406         synchronized (this) {
407             if (mActiveRequestCount == 0) {
408                 rescheduleServiceStop();
409             }
410         }
411     }
412 
413     /**
414      * Reschedule service stop task
415      */
rescheduleServiceStop()416     private void rescheduleServiceStop() {
417         mHandler.removeCallbacks(mServiceStopRunnable);
418         mHandler.postDelayed(mServiceStopRunnable, SERVICE_STOP_DELAY_MILLIS);
419     }
420 
421     /**
422      * Really try to stop the service if there is not active request
423      */
tryStopService()424     private void tryStopService() {
425         Boolean stopped = null;
426         synchronized (this) {
427             if (mActiveRequestCount == 0) {
428                 stopped = stopSelfResult(mLastStartId);
429             }
430         }
431         logServiceStop(stopped);
432     }
433 
434     /**
435      * Log the result of service stopping. Also check wake lock status when service stops.
436      *
437      * @param stopped Not empty if service stop is performed: true if really stopped, false
438      *                if cancelled.
439      */
logServiceStop(final Boolean stopped)440     private void logServiceStop(final Boolean stopped) {
441         if (stopped != null) {
442             if (stopped) {
443                 Log.i(TAG, "Service successfully stopped");
444                 verifyWakeLockNotHeld();
445             } else {
446                 Log.i(TAG, "Service stopping cancelled");
447             }
448         }
449     }
450 
getRequestExecutor(final MmsRequest request)451     private ExecutorService getRequestExecutor(final MmsRequest request) {
452         if (request instanceof SendRequest) {
453             // Send
454             return mExecutors[0];
455         } else {
456             // Download
457             return mExecutors[1];
458         }
459     }
460 
461     @Override
onBind(Intent intent)462     public IBinder onBind(Intent intent) {
463         return null;
464     }
465 }
466