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
17package android.support.v7.mms;
18
19import android.app.Service;
20import android.content.Context;
21import android.content.Intent;
22import android.os.Handler;
23import android.os.IBinder;
24import android.os.PowerManager;
25import android.os.Process;
26import android.telephony.SmsManager;
27import android.util.Log;
28
29import java.util.concurrent.ExecutorService;
30import java.util.concurrent.Executors;
31import java.util.concurrent.RejectedExecutionException;
32
33/**
34 * Service to execute MMS requests using deprecated legacy APIs on older platform (prior to L)
35 */
36public 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     */
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     */
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     */
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     */
109    static CarrierConfigValuesLoader getCarrierConfigValuesLoader() {
110        return sCarrierConfigValuesLoader;
111    }
112
113    /**
114     * Set APN settings loader
115     *
116     * @param loader the APN settings loader
117     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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
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
301    public void onDestroy() {
302        super.onDestroy();
303
304        for (ExecutorService executor : mExecutors) {
305            executor.shutdown();
306        }
307    }
308
309    @Override
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     */
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     */
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     */
405    private void tryScheduleStop() {
406        synchronized (this) {
407            if (mActiveRequestCount == 0) {
408                rescheduleServiceStop();
409            }
410        }
411    }
412
413    /**
414     * Reschedule service stop task
415     */
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     */
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     */
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
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
462    public IBinder onBind(Intent intent) {
463        return null;
464    }
465}
466