blob: ff937ea85fa8c4b3319058b5abd8c8a7f8561b8c [file] [log] [blame]
Ye Wen2d860e92014-05-15 13:05:08 -07001/*
2 * Copyright (C) 2014 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.mms.service;
18
Abhijith Shastry1b2066c2014-10-31 19:02:28 -070019import android.annotation.Nullable;
Ye Wen2d860e92014-05-15 13:05:08 -070020import android.app.PendingIntent;
21import android.app.Service;
Julian Odellb83f2fa2014-08-27 17:36:56 -070022import android.content.ContentResolver;
Ye Wen18aabe22014-07-09 16:58:26 -070023import android.content.ContentUris;
24import android.content.ContentValues;
Abhijith Shastry1b2066c2014-10-31 19:02:28 -070025import android.content.Context;
Ye Wen2d860e92014-05-15 13:05:08 -070026import android.content.Intent;
Ye Wen8c027a62014-07-14 16:23:31 -070027import android.content.SharedPreferences;
Ye Wen18aabe22014-07-09 16:58:26 -070028import android.database.sqlite.SQLiteException;
29import android.net.Uri;
30import android.os.Binder;
Shri Borde8a79dc02014-09-02 09:49:09 -070031import android.os.Bundle;
Ye Wen2d860e92014-05-15 13:05:08 -070032import android.os.IBinder;
Julian Odellb83f2fa2014-08-27 17:36:56 -070033import android.os.ParcelFileDescriptor;
Ye Wen0527dc42014-07-24 17:29:57 -070034import android.os.Process;
Ye Wen2d860e92014-05-15 13:05:08 -070035import android.os.RemoteException;
Ye Wen18aabe22014-07-09 16:58:26 -070036import android.provider.Telephony;
Abhijith Shastry1b2066c2014-10-31 19:02:28 -070037import android.service.carrier.CarrierMessagingService;
Ye Wen18aabe22014-07-09 16:58:26 -070038import android.telephony.SmsManager;
Ye Wen3cf9a942014-10-15 13:35:27 -070039import android.telephony.SubscriptionManager;
Abhijith Shastry1b2066c2014-10-31 19:02:28 -070040import android.telephony.TelephonyManager;
Ye Wen18aabe22014-07-09 16:58:26 -070041import android.text.TextUtils;
Ye Wen3cf9a942014-10-15 13:35:27 -070042import android.util.SparseArray;
43
44import com.android.internal.telephony.IMms;
Ye Wen3cf9a942014-10-15 13:35:27 -070045import com.google.android.mms.MmsException;
46import com.google.android.mms.pdu.DeliveryInd;
47import com.google.android.mms.pdu.GenericPdu;
48import com.google.android.mms.pdu.NotificationInd;
Ye Wen3cf9a942014-10-15 13:35:27 -070049import com.google.android.mms.pdu.PduParser;
50import com.google.android.mms.pdu.PduPersister;
51import com.google.android.mms.pdu.ReadOrigInd;
52import com.google.android.mms.pdu.RetrieveConf;
53import com.google.android.mms.pdu.SendReq;
54import com.google.android.mms.util.SqliteWrapper;
Ye Wen2d860e92014-05-15 13:05:08 -070055
Julian Odellb83f2fa2014-08-27 17:36:56 -070056import java.io.IOException;
Ye Wen3cf9a942014-10-15 13:35:27 -070057import java.util.ArrayDeque;
Julian Odellb83f2fa2014-08-27 17:36:56 -070058import java.util.Arrays;
Abhijith Shastry1b2066c2014-10-31 19:02:28 -070059import java.util.List;
Ye Wen3cf9a942014-10-15 13:35:27 -070060import java.util.Queue;
Julian Odellb83f2fa2014-08-27 17:36:56 -070061import java.util.concurrent.Callable;
Julian Odellb83f2fa2014-08-27 17:36:56 -070062import java.util.concurrent.ExecutorService;
63import java.util.concurrent.Executors;
64import java.util.concurrent.Future;
65import java.util.concurrent.TimeUnit;
Ye Wenb786d3e2014-07-03 14:08:16 -070066
Ye Wen2d860e92014-05-15 13:05:08 -070067/**
68 * System service to process MMS API requests
69 */
Ye Wenb786d3e2014-07-03 14:08:16 -070070public class MmsService extends Service implements MmsRequest.RequestManager {
Ye Wenb786d3e2014-07-03 14:08:16 -070071 public static final int QUEUE_INDEX_SEND = 0;
72 public static final int QUEUE_INDEX_DOWNLOAD = 1;
73
Ye Wen8c027a62014-07-14 16:23:31 -070074 private static final String SHARED_PREFERENCES_NAME = "mmspref";
75 private static final String PREF_AUTO_PERSISTING = "autopersisting";
76
Julian Odellb83f2fa2014-08-27 17:36:56 -070077 // Maximum time to spend waiting to read data from a content provider before failing with error.
78 private static final int TASK_TIMEOUT_MS = 30 * 1000;
79 // Maximum size of MMS service supports - used on occassions when MMS messages are processed
80 // in a carrier independent manner (for example for imports and drafts) and the carrier
81 // specific size limit should not be used (as it could be lower on some carriers).
82 private static final int MAX_MMS_FILE_SIZE = 8 * 1024 * 1024;
83
Ye Wena167df72015-04-01 15:55:06 -070084 // The default number of threads allowed to run MMS requests in each queue
85 public static final int THREAD_POOL_SIZE = 4;
86
Ye Wen3cf9a942014-10-15 13:35:27 -070087 // Pending requests that are waiting for the SIM to be available
88 // If a different SIM is currently used by previous requests, the following
89 // requests will stay in this queue until that SIM finishes its current requests in
90 // RequestQueue.
91 // Requests are not reordered. So, e.g. if current SIM is SIM1, a request for SIM2 will be
92 // blocked in the queue. And a later request for SIM1 will be appended to the queue, ordered
93 // after the request for SIM2, instead of being put into the running queue.
94 // TODO: persist this in case MmsService crashes
95 private final Queue<MmsRequest> mPendingSimRequestQueue = new ArrayDeque<>();
Ye Wenb786d3e2014-07-03 14:08:16 -070096
Ye Wena167df72015-04-01 15:55:06 -070097 // Thread pool for transferring PDU with MMS apps
98 private final ExecutorService mPduTransferExecutor = Executors.newCachedThreadPool();
Julian Odellb83f2fa2014-08-27 17:36:56 -070099
Ye Wen3cf9a942014-10-15 13:35:27 -0700100 // A cache of MmsNetworkManager for SIMs
101 private final SparseArray<MmsNetworkManager> mNetworkManagerCache = new SparseArray<>();
102
103 // The current SIM ID for the running requests. Only one SIM can send/download MMS at a time.
104 private int mCurrentSubId;
105 // The current running MmsRequest count.
106 private int mRunningRequestCount;
107
Ye Wena167df72015-04-01 15:55:06 -0700108 // Running request queues, one thread pool per queue
109 // 0: send queue
110 // 1: download queue
111 private final ExecutorService[] mRunningRequestExecutors = new ExecutorService[2];
Ye Wenc91cc9d2014-05-19 14:41:16 -0700112
Ye Wen3cf9a942014-10-15 13:35:27 -0700113 private MmsNetworkManager getNetworkManager(int subId) {
114 synchronized (mNetworkManagerCache) {
115 MmsNetworkManager manager = mNetworkManagerCache.get(subId);
116 if (manager == null) {
117 manager = new MmsNetworkManager(this, subId);
118 mNetworkManagerCache.put(subId, manager);
119 }
120 return manager;
121 }
122 }
123
Ye Wen0527dc42014-07-24 17:29:57 -0700124 private void enforceSystemUid() {
125 if (Binder.getCallingUid() != Process.SYSTEM_UID) {
126 throw new SecurityException("Only system can call this service");
127 }
128 }
129
Ye Wen3cf9a942014-10-15 13:35:27 -0700130 private int checkSubId(int subId) {
Wink Savilled16e2412014-12-11 10:10:36 -0800131 if (!SubscriptionManager.isValidSubscriptionId(subId)) {
Ye Wen3cf9a942014-10-15 13:35:27 -0700132 throw new RuntimeException("Invalid subId " + subId);
133 }
Wink Saville79ab3ac2014-11-20 13:06:47 -0800134 if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
Shishir Agrawal033ab5c2016-01-25 14:07:09 -0800135 return SubscriptionManager.getDefaultSmsSubscriptionId();
Ye Wen3cf9a942014-10-15 13:35:27 -0700136 }
137 return subId;
138 }
139
Abhijith Shastry1b2066c2014-10-31 19:02:28 -0700140 @Nullable
Sahin Caliskand0b410f2019-04-01 11:37:19 -0700141 private String getCarrierMessagingServicePackageIfExists(int subId) {
Abhijith Shastry1b2066c2014-10-31 19:02:28 -0700142 Intent intent = new Intent(CarrierMessagingService.SERVICE_INTERFACE);
143 TelephonyManager telephonyManager =
144 (TelephonyManager) this.getSystemService(Context.TELEPHONY_SERVICE);
Sahin Caliskand0b410f2019-04-01 11:37:19 -0700145 List<String> carrierPackages = telephonyManager.getCarrierPackageNamesForIntentAndPhone(
146 intent, SubscriptionManager.getPhoneId(subId));
Abhijith Shastry1b2066c2014-10-31 19:02:28 -0700147
148 if (carrierPackages == null || carrierPackages.size() != 1) {
149 return null;
150 } else {
151 return carrierPackages.get(0);
152 }
153 }
154
Ye Wen2d860e92014-05-15 13:05:08 -0700155 private IMms.Stub mStub = new IMms.Stub() {
156 @Override
Wink Savillea6b72c42014-10-23 10:17:17 -0700157 public void sendMessage(int subId, String callingPkg, Uri contentUri,
Ye Wen3e40f4c2014-09-04 15:40:52 -0700158 String locationUrl, Bundle configOverrides, PendingIntent sentIntent)
Julian Odellb83f2fa2014-08-27 17:36:56 -0700159 throws RemoteException {
Ye Wen8153aed2015-06-16 13:36:31 -0700160 LogUtil.d("sendMessage");
Ye Wen0527dc42014-07-24 17:29:57 -0700161 enforceSystemUid();
Shishir Agrawald78cb302014-12-10 10:17:05 -0800162
Ye Wen3cf9a942014-10-15 13:35:27 -0700163 // Make sure the subId is correct
164 subId = checkSubId(subId);
Shishir Agrawald78cb302014-12-10 10:17:05 -0800165
166 // Make sure the subId is active
167 if (!isActiveSubId(subId)) {
168 sendErrorInPendingIntent(sentIntent);
169 return;
170 }
171
Julian Odellb83f2fa2014-08-27 17:36:56 -0700172 final SendRequest request = new SendRequest(MmsService.this, subId, contentUri,
Jonathan Basseri44670072015-06-09 11:46:26 -0700173 locationUrl, sentIntent, callingPkg, configOverrides, MmsService.this);
Abhijith Shastry1b2066c2014-10-31 19:02:28 -0700174
175 final String carrierMessagingServicePackage =
Sahin Caliskand0b410f2019-04-01 11:37:19 -0700176 getCarrierMessagingServicePackageIfExists(subId);
177
Abhijith Shastry1b2066c2014-10-31 19:02:28 -0700178 if (carrierMessagingServicePackage != null) {
Ye Wen8153aed2015-06-16 13:36:31 -0700179 LogUtil.d(request.toString(), "sending message by carrier app");
Abhijith Shastry1b2066c2014-10-31 19:02:28 -0700180 request.trySendingByCarrierApp(MmsService.this, carrierMessagingServicePackage);
181 } else {
182 addSimRequest(request);
183 }
Ye Wen2d860e92014-05-15 13:05:08 -0700184 }
185
186 @Override
Wink Savillea6b72c42014-10-23 10:17:17 -0700187 public void downloadMessage(int subId, String callingPkg, String locationUrl,
Ye Wen3e40f4c2014-09-04 15:40:52 -0700188 Uri contentUri, Bundle configOverrides,
Julian Odellb83f2fa2014-08-27 17:36:56 -0700189 PendingIntent downloadedIntent) throws RemoteException {
Ye Wen8153aed2015-06-16 13:36:31 -0700190 LogUtil.d("downloadMessage: " + MmsHttpClient.redactUrlForNonVerbose(locationUrl));
Ye Wen0527dc42014-07-24 17:29:57 -0700191 enforceSystemUid();
Shishir Agrawald78cb302014-12-10 10:17:05 -0800192
Ye Wen3cf9a942014-10-15 13:35:27 -0700193 // Make sure the subId is correct
194 subId = checkSubId(subId);
Shishir Agrawald78cb302014-12-10 10:17:05 -0800195
Cheuksan Wang97e68dd2015-05-12 18:36:03 -0700196 // If the subId is no longer active it could be caused by
197 // an MVNO using multiple subIds, so we should try to
198 // download anyway.
199 // TODO: Fail fast when downloading will fail (i.e. SIM swapped)
Shishir Agrawald78cb302014-12-10 10:17:05 -0800200
Jonathan Basseri44670072015-06-09 11:46:26 -0700201 final DownloadRequest request = new DownloadRequest(MmsService.this, subId, locationUrl,
202 contentUri, downloadedIntent, callingPkg, configOverrides, MmsService.this);
Abhijith Shastry1b2066c2014-10-31 19:02:28 -0700203 final String carrierMessagingServicePackage =
Sahin Caliskand0b410f2019-04-01 11:37:19 -0700204 getCarrierMessagingServicePackageIfExists(subId);
205
Abhijith Shastry1b2066c2014-10-31 19:02:28 -0700206 if (carrierMessagingServicePackage != null) {
Ye Wen8153aed2015-06-16 13:36:31 -0700207 LogUtil.d(request.toString(), "downloading message by carrier app");
Abhijith Shastry1b2066c2014-10-31 19:02:28 -0700208 request.tryDownloadingByCarrierApp(MmsService.this, carrierMessagingServicePackage);
Ye Wenb786d3e2014-07-03 14:08:16 -0700209 } else {
Abhijith Shastry1b2066c2014-10-31 19:02:28 -0700210 addSimRequest(request);
Ye Wenb786d3e2014-07-03 14:08:16 -0700211 }
212 }
213
Wink Savillea6b72c42014-10-23 10:17:17 -0700214 public Bundle getCarrierConfigValues(int subId) {
Ye Wen8153aed2015-06-16 13:36:31 -0700215 LogUtil.d("getCarrierConfigValues");
Ye Wen3cf9a942014-10-15 13:35:27 -0700216 // Make sure the subId is correct
217 subId = checkSubId(subId);
Jonathan Basseri44670072015-06-09 11:46:26 -0700218 final Bundle mmsConfig = MmsConfigManager.getInstance().getMmsConfigBySubId(subId);
Tom Taylor76b0e862014-09-02 14:21:15 -0700219 if (mmsConfig == null) {
Shri Borde8a79dc02014-09-02 09:49:09 -0700220 return new Bundle();
Tom Taylor76b0e862014-09-02 14:21:15 -0700221 }
Jonathan Basseri44670072015-06-09 11:46:26 -0700222 return mmsConfig;
Ye Wen18aabe22014-07-09 16:58:26 -0700223 }
224
225 @Override
Ye Wen18aabe22014-07-09 16:58:26 -0700226 public Uri importTextMessage(String callingPkg, String address, int type, String text,
227 long timestampMillis, boolean seen, boolean read) {
Ye Wen8153aed2015-06-16 13:36:31 -0700228 LogUtil.d("importTextMessage");
Ye Wen0527dc42014-07-24 17:29:57 -0700229 enforceSystemUid();
Ye Wen18aabe22014-07-09 16:58:26 -0700230 return importSms(address, type, text, timestampMillis, seen, read, callingPkg);
231 }
232
233 @Override
Julian Odellb83f2fa2014-08-27 17:36:56 -0700234 public Uri importMultimediaMessage(String callingPkg, Uri contentUri,
235 String messageId, long timestampSecs, boolean seen, boolean read) {
Ye Wen8153aed2015-06-16 13:36:31 -0700236 LogUtil.d("importMultimediaMessage");
Ye Wen0527dc42014-07-24 17:29:57 -0700237 enforceSystemUid();
Julian Odellb83f2fa2014-08-27 17:36:56 -0700238 return importMms(contentUri, messageId, timestampSecs, seen, read, callingPkg);
Ye Wen18aabe22014-07-09 16:58:26 -0700239 }
240
241 @Override
242 public boolean deleteStoredMessage(String callingPkg, Uri messageUri)
243 throws RemoteException {
Ye Wen8153aed2015-06-16 13:36:31 -0700244 LogUtil.d("deleteStoredMessage " + messageUri);
Ye Wen0527dc42014-07-24 17:29:57 -0700245 enforceSystemUid();
Ye Wen18aabe22014-07-09 16:58:26 -0700246 if (!isSmsMmsContentUri(messageUri)) {
Ye Wen8153aed2015-06-16 13:36:31 -0700247 LogUtil.e("deleteStoredMessage: invalid message URI: " + messageUri.toString());
Ye Wen18aabe22014-07-09 16:58:26 -0700248 return false;
249 }
Ye Wenc0c6d5e2014-07-31 17:19:06 -0700250 // Clear the calling identity and query the database using the phone user id
251 // Otherwise the AppOps check in TelephonyProvider would complain about mismatch
252 // between the calling uid and the package uid
Ye Wen18aabe22014-07-09 16:58:26 -0700253 final long identity = Binder.clearCallingIdentity();
254 try {
255 if (getContentResolver().delete(
256 messageUri, null/*where*/, null/*selectionArgs*/) != 1) {
Ye Wen8153aed2015-06-16 13:36:31 -0700257 LogUtil.e("deleteStoredMessage: failed to delete");
Ye Wen18aabe22014-07-09 16:58:26 -0700258 return false;
259 }
260 } catch (SQLiteException e) {
Ye Wen8153aed2015-06-16 13:36:31 -0700261 LogUtil.e("deleteStoredMessage: failed to delete", e);
Ye Wen18aabe22014-07-09 16:58:26 -0700262 } finally {
263 Binder.restoreCallingIdentity(identity);
264 }
265 return true;
266 }
267
268 @Override
269 public boolean deleteStoredConversation(String callingPkg, long conversationId)
270 throws RemoteException {
Ye Wen8153aed2015-06-16 13:36:31 -0700271 LogUtil.d("deleteStoredConversation " + conversationId);
Ye Wen0527dc42014-07-24 17:29:57 -0700272 enforceSystemUid();
Ye Wen18aabe22014-07-09 16:58:26 -0700273 if (conversationId == -1) {
Ye Wen8153aed2015-06-16 13:36:31 -0700274 LogUtil.e("deleteStoredConversation: invalid thread id");
Ye Wen18aabe22014-07-09 16:58:26 -0700275 return false;
276 }
277 final Uri uri = ContentUris.withAppendedId(
278 Telephony.Threads.CONTENT_URI, conversationId);
Ye Wenc0c6d5e2014-07-31 17:19:06 -0700279 // Clear the calling identity and query the database using the phone user id
280 // Otherwise the AppOps check in TelephonyProvider would complain about mismatch
281 // between the calling uid and the package uid
Ye Wen18aabe22014-07-09 16:58:26 -0700282 final long identity = Binder.clearCallingIdentity();
283 try {
284 if (getContentResolver().delete(uri, null, null) != 1) {
Ye Wen8153aed2015-06-16 13:36:31 -0700285 LogUtil.e("deleteStoredConversation: failed to delete");
Ye Wen18aabe22014-07-09 16:58:26 -0700286 return false;
287 }
288 } catch (SQLiteException e) {
Ye Wen8153aed2015-06-16 13:36:31 -0700289 LogUtil.e("deleteStoredConversation: failed to delete", e);
Ye Wen18aabe22014-07-09 16:58:26 -0700290 } finally {
291 Binder.restoreCallingIdentity(identity);
292 }
293 return true;
294 }
295
296 @Override
297 public boolean updateStoredMessageStatus(String callingPkg, Uri messageUri,
298 ContentValues statusValues) throws RemoteException {
Ye Wen8153aed2015-06-16 13:36:31 -0700299 LogUtil.d("updateStoredMessageStatus " + messageUri);
Ye Wen0527dc42014-07-24 17:29:57 -0700300 enforceSystemUid();
Ye Wen18aabe22014-07-09 16:58:26 -0700301 return updateMessageStatus(messageUri, statusValues);
302 }
303
304 @Override
Ye Wen2704c4b2014-07-29 10:43:05 -0700305 public boolean archiveStoredConversation(String callingPkg, long conversationId,
306 boolean archived) throws RemoteException {
Ye Wen8153aed2015-06-16 13:36:31 -0700307 LogUtil.d("archiveStoredConversation " + conversationId + " " + archived);
Ye Wen2704c4b2014-07-29 10:43:05 -0700308 if (conversationId == -1) {
Ye Wen8153aed2015-06-16 13:36:31 -0700309 LogUtil.e("archiveStoredConversation: invalid thread id");
Ye Wen2704c4b2014-07-29 10:43:05 -0700310 return false;
311 }
312 return archiveConversation(conversationId, archived);
313 }
314
315 @Override
Ye Wen18aabe22014-07-09 16:58:26 -0700316 public Uri addTextMessageDraft(String callingPkg, String address, String text)
317 throws RemoteException {
Ye Wen8153aed2015-06-16 13:36:31 -0700318 LogUtil.d("addTextMessageDraft");
Ye Wen0527dc42014-07-24 17:29:57 -0700319 enforceSystemUid();
Ye Wen18aabe22014-07-09 16:58:26 -0700320 return addSmsDraft(address, text, callingPkg);
321 }
322
323 @Override
Julian Odellb83f2fa2014-08-27 17:36:56 -0700324 public Uri addMultimediaMessageDraft(String callingPkg, Uri contentUri)
Ye Wen18aabe22014-07-09 16:58:26 -0700325 throws RemoteException {
Ye Wen8153aed2015-06-16 13:36:31 -0700326 LogUtil.d("addMultimediaMessageDraft");
Ye Wen0527dc42014-07-24 17:29:57 -0700327 enforceSystemUid();
Julian Odellb83f2fa2014-08-27 17:36:56 -0700328 return addMmsDraft(contentUri, callingPkg);
Ye Wen18aabe22014-07-09 16:58:26 -0700329 }
330
331 @Override
Wink Savillea6b72c42014-10-23 10:17:17 -0700332 public void sendStoredMessage(int subId, String callingPkg, Uri messageUri,
Ye Wen3e40f4c2014-09-04 15:40:52 -0700333 Bundle configOverrides, PendingIntent sentIntent) throws RemoteException {
Cheuksan Wangf6d88a72014-09-08 19:47:59 -0700334 throw new UnsupportedOperationException();
Ye Wen18aabe22014-07-09 16:58:26 -0700335 }
Shishir Agrawal6f318112014-07-14 13:53:13 -0700336
Ye Wen8c027a62014-07-14 16:23:31 -0700337 @Override
338 public void setAutoPersisting(String callingPkg, boolean enabled) throws RemoteException {
Ye Wen8153aed2015-06-16 13:36:31 -0700339 LogUtil.d("setAutoPersisting " + enabled);
Ye Wen0527dc42014-07-24 17:29:57 -0700340 enforceSystemUid();
Ye Wen8c027a62014-07-14 16:23:31 -0700341 final SharedPreferences preferences = getSharedPreferences(
342 SHARED_PREFERENCES_NAME, MODE_PRIVATE);
343 final SharedPreferences.Editor editor = preferences.edit();
344 editor.putBoolean(PREF_AUTO_PERSISTING, enabled);
345 editor.apply();
346 }
347
348 @Override
349 public boolean getAutoPersisting() throws RemoteException {
Ye Wen8153aed2015-06-16 13:36:31 -0700350 LogUtil.d("getAutoPersisting");
Ye Wen8c027a62014-07-14 16:23:31 -0700351 return getAutoPersistingPref();
352 }
Shishir Agrawald78cb302014-12-10 10:17:05 -0800353
354 /*
355 * @return true if the subId is active.
356 */
357 private boolean isActiveSubId(int subId) {
358 return SubscriptionManager.from(MmsService.this).isActiveSubId(subId);
359 }
360
361 /*
362 * Calls the pending intent with <code>MMS_ERROR_NO_DATA_NETWORK</code>.
363 */
364 private void sendErrorInPendingIntent(@Nullable PendingIntent intent) {
365 if (intent != null) {
366 try {
367 intent.send(SmsManager.MMS_ERROR_NO_DATA_NETWORK);
368 } catch (PendingIntent.CanceledException ex) {
369 }
370 }
371 }
Ye Wen2d860e92014-05-15 13:05:08 -0700372 };
373
Ye Wenb786d3e2014-07-03 14:08:16 -0700374 @Override
Ye Wen3cf9a942014-10-15 13:35:27 -0700375 public void addSimRequest(MmsRequest request) {
Ye Wenb786d3e2014-07-03 14:08:16 -0700376 if (request == null) {
Ye Wen8153aed2015-06-16 13:36:31 -0700377 LogUtil.e("Add running or pending: empty request");
Ye Wenb786d3e2014-07-03 14:08:16 -0700378 return;
379 }
Ye Wen8153aed2015-06-16 13:36:31 -0700380 LogUtil.d("Current running=" + mRunningRequestCount + ", "
Ye Wen3cf9a942014-10-15 13:35:27 -0700381 + "current subId=" + mCurrentSubId + ", "
382 + "pending=" + mPendingSimRequestQueue.size());
383 synchronized (this) {
384 if (mPendingSimRequestQueue.size() > 0 ||
385 (mRunningRequestCount > 0 && request.getSubId() != mCurrentSubId)) {
Ye Wen8153aed2015-06-16 13:36:31 -0700386 LogUtil.d("Add request to pending queue."
Ye Wen3cf9a942014-10-15 13:35:27 -0700387 + " Request subId=" + request.getSubId() + ","
388 + " current subId=" + mCurrentSubId);
389 mPendingSimRequestQueue.add(request);
390 if (mRunningRequestCount <= 0) {
Ye Wen8153aed2015-06-16 13:36:31 -0700391 LogUtil.e("Nothing's running but queue's not empty");
Ye Wen3cf9a942014-10-15 13:35:27 -0700392 // Nothing is running but we are accumulating on pending queue.
393 // This should not happen. But just in case...
394 movePendingSimRequestsToRunningSynchronized();
395 }
396 } else {
397 addToRunningRequestQueueSynchronized(request);
398 }
399 }
400 }
401
Ye Wena167df72015-04-01 15:55:06 -0700402 private void addToRunningRequestQueueSynchronized(final MmsRequest request) {
Ye Wen8153aed2015-06-16 13:36:31 -0700403 LogUtil.d("Add request to running queue for subId " + request.getSubId());
Ye Wen3cf9a942014-10-15 13:35:27 -0700404 // Update current state of running requests
Ye Wen3cf9a942014-10-15 13:35:27 -0700405 final int queue = request.getQueueType();
Ye Wena167df72015-04-01 15:55:06 -0700406 if (queue < 0 || queue >= mRunningRequestExecutors.length) {
Ye Wen8153aed2015-06-16 13:36:31 -0700407 LogUtil.e("Invalid request queue index for running request");
Ye Wena167df72015-04-01 15:55:06 -0700408 return;
409 }
410 mRunningRequestCount++;
411 mCurrentSubId = request.getSubId();
412 // Send to the corresponding request queue for execution
413 mRunningRequestExecutors[queue].execute(new Runnable() {
414 @Override
415 public void run() {
416 try {
417 request.execute(MmsService.this, getNetworkManager(request.getSubId()));
418 } finally {
419 synchronized (MmsService.this) {
420 mRunningRequestCount--;
421 if (mRunningRequestCount <= 0) {
422 movePendingSimRequestsToRunningSynchronized();
423 }
424 }
425 }
426 }
427 });
Ye Wen3cf9a942014-10-15 13:35:27 -0700428 }
429
430 private void movePendingSimRequestsToRunningSynchronized() {
Ye Wen8153aed2015-06-16 13:36:31 -0700431 LogUtil.d("Schedule requests pending on SIM");
Wink Saville79ab3ac2014-11-20 13:06:47 -0800432 mCurrentSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
Ye Wen3cf9a942014-10-15 13:35:27 -0700433 while (mPendingSimRequestQueue.size() > 0) {
434 final MmsRequest request = mPendingSimRequestQueue.peek();
435 if (request != null) {
Wink Savilled16e2412014-12-11 10:10:36 -0800436 if (!SubscriptionManager.isValidSubscriptionId(mCurrentSubId)
437 || mCurrentSubId == request.getSubId()) {
Ye Wen3cf9a942014-10-15 13:35:27 -0700438 // First or subsequent requests with same SIM ID
439 mPendingSimRequestQueue.remove();
440 addToRunningRequestQueueSynchronized(request);
441 } else {
442 // Stop if we see a different SIM ID
443 break;
444 }
445 } else {
Ye Wen8153aed2015-06-16 13:36:31 -0700446 LogUtil.e("Schedule pending: found empty request");
Ye Wen3cf9a942014-10-15 13:35:27 -0700447 mPendingSimRequestQueue.remove();
448 }
449 }
Ye Wenc91cc9d2014-05-19 14:41:16 -0700450 }
451
Ye Wen2d860e92014-05-15 13:05:08 -0700452 @Override
453 public IBinder onBind(Intent intent) {
454 return mStub;
455 }
456
457 public final IBinder asBinder() {
458 return mStub;
459 }
460
461 @Override
462 public void onCreate() {
463 super.onCreate();
Ye Wen8153aed2015-06-16 13:36:31 -0700464 LogUtil.d("onCreate");
Ye Wenc91cc9d2014-05-19 14:41:16 -0700465 // Load mms_config
Tom Taylor76b0e862014-09-02 14:21:15 -0700466 MmsConfigManager.getInstance().init(this);
Ye Wen3cf9a942014-10-15 13:35:27 -0700467 // Initialize running request state
Ye Wena167df72015-04-01 15:55:06 -0700468 for (int i = 0; i < mRunningRequestExecutors.length; i++) {
469 mRunningRequestExecutors[i] = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
470 }
Ye Wen3cf9a942014-10-15 13:35:27 -0700471 synchronized (this) {
Wink Saville79ab3ac2014-11-20 13:06:47 -0800472 mCurrentSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
Ye Wen3cf9a942014-10-15 13:35:27 -0700473 mRunningRequestCount = 0;
474 }
Ye Wenc91cc9d2014-05-19 14:41:16 -0700475 }
Ye Wen18aabe22014-07-09 16:58:26 -0700476
Ye Wena167df72015-04-01 15:55:06 -0700477 @Override
478 public void onDestroy() {
479 super.onDestroy();
Ye Wen8153aed2015-06-16 13:36:31 -0700480 LogUtil.d("onDestroy");
Ye Wena167df72015-04-01 15:55:06 -0700481 for (ExecutorService executor : mRunningRequestExecutors) {
482 executor.shutdown();
483 }
484 }
485
Ye Wen18aabe22014-07-09 16:58:26 -0700486 private Uri importSms(String address, int type, String text, long timestampMillis,
487 boolean seen, boolean read, String creator) {
488 Uri insertUri = null;
489 switch (type) {
490 case SmsManager.SMS_TYPE_INCOMING:
491 insertUri = Telephony.Sms.Inbox.CONTENT_URI;
492
493 break;
494 case SmsManager.SMS_TYPE_OUTGOING:
495 insertUri = Telephony.Sms.Sent.CONTENT_URI;
496 break;
497 }
498 if (insertUri == null) {
Ye Wen8153aed2015-06-16 13:36:31 -0700499 LogUtil.e("importTextMessage: invalid message type for importing: " + type);
Ye Wen18aabe22014-07-09 16:58:26 -0700500 return null;
501 }
502 final ContentValues values = new ContentValues(6);
503 values.put(Telephony.Sms.ADDRESS, address);
504 values.put(Telephony.Sms.DATE, timestampMillis);
505 values.put(Telephony.Sms.SEEN, seen ? 1 : 0);
506 values.put(Telephony.Sms.READ, read ? 1 : 0);
507 values.put(Telephony.Sms.BODY, text);
508 if (!TextUtils.isEmpty(creator)) {
509 values.put(Telephony.Mms.CREATOR, creator);
510 }
Ye Wenc0c6d5e2014-07-31 17:19:06 -0700511 // Clear the calling identity and query the database using the phone user id
512 // Otherwise the AppOps check in TelephonyProvider would complain about mismatch
513 // between the calling uid and the package uid
Ye Wen18aabe22014-07-09 16:58:26 -0700514 final long identity = Binder.clearCallingIdentity();
515 try {
516 return getContentResolver().insert(insertUri, values);
517 } catch (SQLiteException e) {
Ye Wen8153aed2015-06-16 13:36:31 -0700518 LogUtil.e("importTextMessage: failed to persist imported text message", e);
Ye Wen18aabe22014-07-09 16:58:26 -0700519 } finally {
520 Binder.restoreCallingIdentity(identity);
521 }
522 return null;
523 }
524
Julian Odellb83f2fa2014-08-27 17:36:56 -0700525 private Uri importMms(Uri contentUri, String messageId, long timestampSecs,
Ye Wen18aabe22014-07-09 16:58:26 -0700526 boolean seen, boolean read, String creator) {
Julian Odellb83f2fa2014-08-27 17:36:56 -0700527 byte[] pduData = readPduFromContentUri(contentUri, MAX_MMS_FILE_SIZE);
Ye Wen18aabe22014-07-09 16:58:26 -0700528 if (pduData == null || pduData.length < 1) {
Ye Wen8153aed2015-06-16 13:36:31 -0700529 LogUtil.e("importMessage: empty PDU");
Ye Wen18aabe22014-07-09 16:58:26 -0700530 return null;
531 }
Ye Wenc0c6d5e2014-07-31 17:19:06 -0700532 // Clear the calling identity and query the database using the phone user id
533 // Otherwise the AppOps check in TelephonyProvider would complain about mismatch
534 // between the calling uid and the package uid
Ye Wen18aabe22014-07-09 16:58:26 -0700535 final long identity = Binder.clearCallingIdentity();
536 try {
Ye Wena813bdf2014-11-17 17:15:07 -0800537 final GenericPdu pdu = parsePduForAnyCarrier(pduData);
Ye Wen18aabe22014-07-09 16:58:26 -0700538 if (pdu == null) {
Ye Wen8153aed2015-06-16 13:36:31 -0700539 LogUtil.e("importMessage: can't parse input PDU");
Ye Wen18aabe22014-07-09 16:58:26 -0700540 return null;
541 }
542 Uri insertUri = null;
543 if (pdu instanceof SendReq) {
544 insertUri = Telephony.Mms.Sent.CONTENT_URI;
545 } else if (pdu instanceof RetrieveConf ||
546 pdu instanceof NotificationInd ||
547 pdu instanceof DeliveryInd ||
548 pdu instanceof ReadOrigInd) {
549 insertUri = Telephony.Mms.Inbox.CONTENT_URI;
550 }
551 if (insertUri == null) {
Ye Wen8153aed2015-06-16 13:36:31 -0700552 LogUtil.e("importMessage; invalid MMS type: " + pdu.getClass().getCanonicalName());
Ye Wen18aabe22014-07-09 16:58:26 -0700553 return null;
554 }
555 final PduPersister persister = PduPersister.getPduPersister(this);
556 final Uri uri = persister.persist(
557 pdu,
558 insertUri,
559 true/*createThreadId*/,
560 true/*groupMmsEnabled*/,
561 null/*preOpenedFiles*/);
562 if (uri == null) {
Ye Wen8153aed2015-06-16 13:36:31 -0700563 LogUtil.e("importMessage: failed to persist message");
Ye Wen18aabe22014-07-09 16:58:26 -0700564 return null;
565 }
566 final ContentValues values = new ContentValues(5);
567 if (!TextUtils.isEmpty(messageId)) {
568 values.put(Telephony.Mms.MESSAGE_ID, messageId);
569 }
570 if (timestampSecs != -1) {
571 values.put(Telephony.Mms.DATE, timestampSecs);
572 }
573 values.put(Telephony.Mms.READ, seen ? 1 : 0);
574 values.put(Telephony.Mms.SEEN, read ? 1 : 0);
575 if (!TextUtils.isEmpty(creator)) {
576 values.put(Telephony.Mms.CREATOR, creator);
577 }
578 if (SqliteWrapper.update(this, getContentResolver(), uri, values,
579 null/*where*/, null/*selectionArg*/) != 1) {
Ye Wen8153aed2015-06-16 13:36:31 -0700580 LogUtil.e("importMessage: failed to update message");
Ye Wen18aabe22014-07-09 16:58:26 -0700581 }
582 return uri;
583 } catch (RuntimeException e) {
Ye Wen8153aed2015-06-16 13:36:31 -0700584 LogUtil.e("importMessage: failed to parse input PDU", e);
Ye Wen18aabe22014-07-09 16:58:26 -0700585 } catch (MmsException e) {
Ye Wen8153aed2015-06-16 13:36:31 -0700586 LogUtil.e("importMessage: failed to persist message", e);
Ye Wen18aabe22014-07-09 16:58:26 -0700587 } finally {
588 Binder.restoreCallingIdentity(identity);
589 }
590 return null;
591 }
592
593 private static boolean isSmsMmsContentUri(Uri uri) {
594 final String uriString = uri.toString();
595 if (!uriString.startsWith("content://sms/") && !uriString.startsWith("content://mms/")) {
596 return false;
597 }
598 if (ContentUris.parseId(uri) == -1) {
599 return false;
600 }
601 return true;
602 }
603
604 private boolean updateMessageStatus(Uri messageUri, ContentValues statusValues) {
605 if (!isSmsMmsContentUri(messageUri)) {
Ye Wen8153aed2015-06-16 13:36:31 -0700606 LogUtil.e("updateMessageStatus: invalid messageUri: " + messageUri.toString());
Ye Wen18aabe22014-07-09 16:58:26 -0700607 return false;
608 }
609 if (statusValues == null) {
Ye Wen8153aed2015-06-16 13:36:31 -0700610 LogUtil.w("updateMessageStatus: empty values to update");
Ye Wen18aabe22014-07-09 16:58:26 -0700611 return false;
612 }
613 final ContentValues values = new ContentValues();
614 if (statusValues.containsKey(SmsManager.MESSAGE_STATUS_READ)) {
615 final Integer val = statusValues.getAsInteger(SmsManager.MESSAGE_STATUS_READ);
616 if (val != null) {
617 // MMS uses the same column name
618 values.put(Telephony.Sms.READ, val);
619 }
620 } else if (statusValues.containsKey(SmsManager.MESSAGE_STATUS_SEEN)) {
621 final Integer val = statusValues.getAsInteger(SmsManager.MESSAGE_STATUS_SEEN);
622 if (val != null) {
623 // MMS uses the same column name
624 values.put(Telephony.Sms.SEEN, val);
625 }
Ye Wen18aabe22014-07-09 16:58:26 -0700626 }
627 if (values.size() < 1) {
Ye Wen8153aed2015-06-16 13:36:31 -0700628 LogUtil.w("updateMessageStatus: no value to update");
Ye Wen18aabe22014-07-09 16:58:26 -0700629 return false;
630 }
Ye Wenc0c6d5e2014-07-31 17:19:06 -0700631 // Clear the calling identity and query the database using the phone user id
632 // Otherwise the AppOps check in TelephonyProvider would complain about mismatch
633 // between the calling uid and the package uid
Ye Wen18aabe22014-07-09 16:58:26 -0700634 final long identity = Binder.clearCallingIdentity();
635 try {
636 if (getContentResolver().update(
637 messageUri, values, null/*where*/, null/*selectionArgs*/) != 1) {
Ye Wen8153aed2015-06-16 13:36:31 -0700638 LogUtil.e("updateMessageStatus: failed to update database");
Ye Wen18aabe22014-07-09 16:58:26 -0700639 return false;
640 }
641 return true;
642 } catch (SQLiteException e) {
Ye Wen8153aed2015-06-16 13:36:31 -0700643 LogUtil.e("updateMessageStatus: failed to update database", e);
Ye Wen18aabe22014-07-09 16:58:26 -0700644 } finally {
645 Binder.restoreCallingIdentity(identity);
646 }
647 return false;
648 }
649
Ye Wen2704c4b2014-07-29 10:43:05 -0700650 private static final String ARCHIVE_CONVERSATION_SELECTION = Telephony.Threads._ID + "=?";
651 private boolean archiveConversation(long conversationId, boolean archived) {
652 final ContentValues values = new ContentValues(1);
653 values.put(Telephony.Threads.ARCHIVED, archived ? 1 : 0);
Ye Wenc0c6d5e2014-07-31 17:19:06 -0700654 // Clear the calling identity and query the database using the phone user id
655 // Otherwise the AppOps check in TelephonyProvider would complain about mismatch
656 // between the calling uid and the package uid
Ye Wen2704c4b2014-07-29 10:43:05 -0700657 final long identity = Binder.clearCallingIdentity();
658 try {
659 if (getContentResolver().update(
660 Telephony.Threads.CONTENT_URI,
661 values,
662 ARCHIVE_CONVERSATION_SELECTION,
663 new String[] { Long.toString(conversationId)}) != 1) {
Ye Wen8153aed2015-06-16 13:36:31 -0700664 LogUtil.e("archiveConversation: failed to update database");
Ye Wen2704c4b2014-07-29 10:43:05 -0700665 return false;
666 }
667 return true;
668 } catch (SQLiteException e) {
Ye Wen8153aed2015-06-16 13:36:31 -0700669 LogUtil.e("archiveConversation: failed to update database", e);
Ye Wen2704c4b2014-07-29 10:43:05 -0700670 } finally {
671 Binder.restoreCallingIdentity(identity);
672 }
673 return false;
674 }
675
Ye Wen18aabe22014-07-09 16:58:26 -0700676 private Uri addSmsDraft(String address, String text, String creator) {
677 final ContentValues values = new ContentValues(5);
678 values.put(Telephony.Sms.ADDRESS, address);
679 values.put(Telephony.Sms.BODY, text);
680 values.put(Telephony.Sms.READ, 1);
681 values.put(Telephony.Sms.SEEN, 1);
682 if (!TextUtils.isEmpty(creator)) {
683 values.put(Telephony.Mms.CREATOR, creator);
684 }
Ye Wenc0c6d5e2014-07-31 17:19:06 -0700685 // Clear the calling identity and query the database using the phone user id
686 // Otherwise the AppOps check in TelephonyProvider would complain about mismatch
687 // between the calling uid and the package uid
Ye Wen18aabe22014-07-09 16:58:26 -0700688 final long identity = Binder.clearCallingIdentity();
689 try {
690 return getContentResolver().insert(Telephony.Sms.Draft.CONTENT_URI, values);
691 } catch (SQLiteException e) {
Ye Wen8153aed2015-06-16 13:36:31 -0700692 LogUtil.e("addSmsDraft: failed to store draft message", e);
Ye Wen18aabe22014-07-09 16:58:26 -0700693 } finally {
694 Binder.restoreCallingIdentity(identity);
695 }
696 return null;
697 }
698
Julian Odellb83f2fa2014-08-27 17:36:56 -0700699 private Uri addMmsDraft(Uri contentUri, String creator) {
700 byte[] pduData = readPduFromContentUri(contentUri, MAX_MMS_FILE_SIZE);
Ye Wen18aabe22014-07-09 16:58:26 -0700701 if (pduData == null || pduData.length < 1) {
Ye Wen8153aed2015-06-16 13:36:31 -0700702 LogUtil.e("addMmsDraft: empty PDU");
Ye Wen18aabe22014-07-09 16:58:26 -0700703 return null;
704 }
Ye Wenc0c6d5e2014-07-31 17:19:06 -0700705 // Clear the calling identity and query the database using the phone user id
706 // Otherwise the AppOps check in TelephonyProvider would complain about mismatch
707 // between the calling uid and the package uid
Ye Wen18aabe22014-07-09 16:58:26 -0700708 final long identity = Binder.clearCallingIdentity();
709 try {
Ye Wena813bdf2014-11-17 17:15:07 -0800710 final GenericPdu pdu = parsePduForAnyCarrier(pduData);
Ye Wen18aabe22014-07-09 16:58:26 -0700711 if (pdu == null) {
Ye Wen8153aed2015-06-16 13:36:31 -0700712 LogUtil.e("addMmsDraft: can't parse input PDU");
Ye Wen18aabe22014-07-09 16:58:26 -0700713 return null;
714 }
715 if (!(pdu instanceof SendReq)) {
Ye Wen8153aed2015-06-16 13:36:31 -0700716 LogUtil.e("addMmsDraft; invalid MMS type: " + pdu.getClass().getCanonicalName());
Ye Wen18aabe22014-07-09 16:58:26 -0700717 return null;
718 }
719 final PduPersister persister = PduPersister.getPduPersister(this);
720 final Uri uri = persister.persist(
721 pdu,
722 Telephony.Mms.Draft.CONTENT_URI,
723 true/*createThreadId*/,
724 true/*groupMmsEnabled*/,
725 null/*preOpenedFiles*/);
726 if (uri == null) {
Ye Wen8153aed2015-06-16 13:36:31 -0700727 LogUtil.e("addMmsDraft: failed to persist message");
Ye Wen18aabe22014-07-09 16:58:26 -0700728 return null;
729 }
730 final ContentValues values = new ContentValues(3);
731 values.put(Telephony.Mms.READ, 1);
732 values.put(Telephony.Mms.SEEN, 1);
733 if (!TextUtils.isEmpty(creator)) {
734 values.put(Telephony.Mms.CREATOR, creator);
735 }
736 if (SqliteWrapper.update(this, getContentResolver(), uri, values,
737 null/*where*/, null/*selectionArg*/) != 1) {
Ye Wen8153aed2015-06-16 13:36:31 -0700738 LogUtil.e("addMmsDraft: failed to update message");
Ye Wen18aabe22014-07-09 16:58:26 -0700739 }
740 return uri;
741 } catch (RuntimeException e) {
Ye Wen8153aed2015-06-16 13:36:31 -0700742 LogUtil.e("addMmsDraft: failed to parse input PDU", e);
Ye Wen18aabe22014-07-09 16:58:26 -0700743 } catch (MmsException e) {
Ye Wen8153aed2015-06-16 13:36:31 -0700744 LogUtil.e("addMmsDraft: failed to persist message", e);
Ye Wen18aabe22014-07-09 16:58:26 -0700745 } finally {
746 Binder.restoreCallingIdentity(identity);
747 }
748 return null;
749 }
750
Ye Wena813bdf2014-11-17 17:15:07 -0800751 /**
752 * Try parsing a PDU without knowing the carrier. This is useful for importing
753 * MMS or storing draft when carrier info is not available
754 *
755 * @param data The PDU data
756 * @return Parsed PDU, null if failed to parse
757 */
758 private static GenericPdu parsePduForAnyCarrier(final byte[] data) {
759 GenericPdu pdu = null;
Ye Wen18aabe22014-07-09 16:58:26 -0700760 try {
Ye Wena813bdf2014-11-17 17:15:07 -0800761 pdu = (new PduParser(data, true/*parseContentDisposition*/)).parse();
Ye Wen18aabe22014-07-09 16:58:26 -0700762 } catch (RuntimeException e) {
Ye Wen8153aed2015-06-16 13:36:31 -0700763 LogUtil.w("parsePduForAnyCarrier: Failed to parse PDU with content disposition", e);
Ye Wen18aabe22014-07-09 16:58:26 -0700764 }
Ye Wena813bdf2014-11-17 17:15:07 -0800765 if (pdu == null) {
Ye Wen18aabe22014-07-09 16:58:26 -0700766 try {
Ye Wena813bdf2014-11-17 17:15:07 -0800767 pdu = (new PduParser(data, false/*parseContentDisposition*/)).parse();
768 } catch (RuntimeException e) {
Ye Wen8153aed2015-06-16 13:36:31 -0700769 LogUtil.w("parsePduForAnyCarrier: Failed to parse PDU without content disposition",
Ye Wena813bdf2014-11-17 17:15:07 -0800770 e);
Ye Wen18aabe22014-07-09 16:58:26 -0700771 }
772 }
Ye Wena813bdf2014-11-17 17:15:07 -0800773 return pdu;
Ye Wen18aabe22014-07-09 16:58:26 -0700774 }
Ye Wen8c027a62014-07-14 16:23:31 -0700775
776 @Override
777 public boolean getAutoPersistingPref() {
778 final SharedPreferences preferences = getSharedPreferences(
779 SHARED_PREFERENCES_NAME, MODE_PRIVATE);
780 return preferences.getBoolean(PREF_AUTO_PERSISTING, false);
781 }
Julian Odellb83f2fa2014-08-27 17:36:56 -0700782
783 /**
784 * Read pdu from content provider uri
785 * @param contentUri content provider uri from which to read
786 * @param maxSize maximum number of bytes to read
787 * @return pdu bytes if succeeded else null
788 */
789 public byte[] readPduFromContentUri(final Uri contentUri, final int maxSize) {
Ye Wen89880092015-03-25 16:03:06 -0700790 if (contentUri == null) {
791 return null;
792 }
Julian Odellb83f2fa2014-08-27 17:36:56 -0700793 Callable<byte[]> copyPduToArray = new Callable<byte[]>() {
794 public byte[] call() {
795 ParcelFileDescriptor.AutoCloseInputStream inStream = null;
796 try {
797 ContentResolver cr = MmsService.this.getContentResolver();
798 ParcelFileDescriptor pduFd = cr.openFileDescriptor(contentUri, "r");
799 inStream = new ParcelFileDescriptor.AutoCloseInputStream(pduFd);
800 // Request one extra byte to make sure file not bigger than maxSize
801 byte[] tempBody = new byte[maxSize+1];
802 int bytesRead = inStream.read(tempBody, 0, maxSize+1);
803 if (bytesRead == 0) {
Ye Wen8153aed2015-06-16 13:36:31 -0700804 LogUtil.e("Read empty PDU");
Julian Odellb83f2fa2014-08-27 17:36:56 -0700805 return null;
806 }
807 if (bytesRead <= maxSize) {
808 return Arrays.copyOf(tempBody, bytesRead);
809 }
Ye Wen8153aed2015-06-16 13:36:31 -0700810 LogUtil.e("PDU read is too large");
Julian Odellb83f2fa2014-08-27 17:36:56 -0700811 return null;
812 } catch (IOException ex) {
Ye Wen8153aed2015-06-16 13:36:31 -0700813 LogUtil.e("IO exception reading PDU", ex);
Julian Odellb83f2fa2014-08-27 17:36:56 -0700814 return null;
815 } finally {
816 if (inStream != null) {
817 try {
818 inStream.close();
819 } catch (IOException ex) {
820 }
821 }
822 }
823 }
824 };
825
Ye Wen8153aed2015-06-16 13:36:31 -0700826 final Future<byte[]> pendingResult = mPduTransferExecutor.submit(copyPduToArray);
Julian Odellb83f2fa2014-08-27 17:36:56 -0700827 try {
Ye Wen89880092015-03-25 16:03:06 -0700828 return pendingResult.get(TASK_TIMEOUT_MS, TimeUnit.MILLISECONDS);
Julian Odellb83f2fa2014-08-27 17:36:56 -0700829 } catch (Exception e) {
830 // Typically a timeout occurred - cancel task
831 pendingResult.cancel(true);
832 }
833 return null;
834 }
835
836 /**
837 * Write pdu bytes to content provider uri
838 * @param contentUri content provider uri to which bytes should be written
839 * @param pdu Bytes to write
840 * @return true if all bytes successfully written else false
841 */
842 public boolean writePduToContentUri(final Uri contentUri, final byte[] pdu) {
Ye Wen89880092015-03-25 16:03:06 -0700843 if (contentUri == null || pdu == null) {
844 return false;
845 }
Ye Wen8153aed2015-06-16 13:36:31 -0700846 final Callable<Boolean> copyDownloadedPduToOutput = new Callable<Boolean>() {
Julian Odellb83f2fa2014-08-27 17:36:56 -0700847 public Boolean call() {
848 ParcelFileDescriptor.AutoCloseOutputStream outStream = null;
849 try {
850 ContentResolver cr = MmsService.this.getContentResolver();
851 ParcelFileDescriptor pduFd = cr.openFileDescriptor(contentUri, "w");
852 outStream = new ParcelFileDescriptor.AutoCloseOutputStream(pduFd);
853 outStream.write(pdu);
854 return Boolean.TRUE;
855 } catch (IOException ex) {
Ye Wen8153aed2015-06-16 13:36:31 -0700856 LogUtil.e("IO exception writing PDU", ex);
Julian Odellb83f2fa2014-08-27 17:36:56 -0700857 return Boolean.FALSE;
858 } finally {
859 if (outStream != null) {
860 try {
861 outStream.close();
862 } catch (IOException ex) {
863 }
864 }
865 }
866 }
867 };
868
Ye Wen8153aed2015-06-16 13:36:31 -0700869 final Future<Boolean> pendingResult =
870 mPduTransferExecutor.submit(copyDownloadedPduToOutput);
Julian Odellb83f2fa2014-08-27 17:36:56 -0700871 try {
Ye Wen89880092015-03-25 16:03:06 -0700872 return pendingResult.get(TASK_TIMEOUT_MS, TimeUnit.MILLISECONDS);
Julian Odellb83f2fa2014-08-27 17:36:56 -0700873 } catch (Exception e) {
874 // Typically a timeout occurred - cancel task
875 pendingResult.cancel(true);
876 }
877 return false;
878 }
Ye Wen2d860e92014-05-15 13:05:08 -0700879}