MmsServiceBroker.java revision 86201db27e7585975bd59401b4b7d28198ecf3f5
1/*
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.server;
18
19import android.Manifest;
20import android.app.AppOpsManager;
21import android.app.PendingIntent;
22import android.content.ComponentName;
23import android.content.ContentProvider;
24import android.content.ContentValues;
25import android.content.Context;
26import android.content.Intent;
27import android.content.ServiceConnection;
28import android.content.pm.PackageManager;
29import android.net.Uri;
30import android.os.Binder;
31import android.os.Bundle;
32import android.os.Handler;
33import android.os.IBinder;
34import android.os.Message;
35import android.os.RemoteException;
36import android.os.SystemClock;
37import android.os.UserHandle;
38import android.provider.Telephony;
39import android.telephony.TelephonyManager;
40import android.util.Slog;
41
42import com.android.internal.telephony.IMms;
43
44import java.util.List;
45
46/**
47 * This class is a proxy for MmsService APIs. We need this because MmsService runs
48 * in phone process and may crash anytime. This manages a connection to the actual
49 * MmsService and bridges the public SMS/MMS APIs with MmsService implementation.
50 */
51public class MmsServiceBroker extends SystemService {
52    private static final String TAG = "MmsServiceBroker";
53
54    private static final ComponentName MMS_SERVICE_COMPONENT =
55            new ComponentName("com.android.mms.service", "com.android.mms.service.MmsService");
56
57    private static final int MSG_TRY_CONNECTING = 1;
58
59    private static final Uri FAKE_SMS_SENT_URI = Uri.parse("content://sms/sent/0");
60    private static final Uri FAKE_MMS_SENT_URI = Uri.parse("content://mms/sent/0");
61    private static final Uri FAKE_SMS_DRAFT_URI = Uri.parse("content://sms/draft/0");
62    private static final Uri FAKE_MMS_DRAFT_URI = Uri.parse("content://mms/draft/0");
63
64    private static final long SERVICE_CONNECTION_WAIT_TIME_MS = 4 * 1000L; // 4 seconds
65    private static final long RETRY_DELAY_ON_DISCONNECTION_MS = 3 * 1000L; // 3 seconds
66
67    private Context mContext;
68    // The actual MMS service instance to invoke
69    private volatile IMms mService;
70
71    // Cached system service instances
72    private volatile AppOpsManager mAppOpsManager = null;
73    private volatile PackageManager mPackageManager = null;
74    private volatile TelephonyManager mTelephonyManager = null;
75
76    private final Handler mConnectionHandler = new Handler() {
77        @Override
78        public void handleMessage(Message msg) {
79            switch (msg.what) {
80                case MSG_TRY_CONNECTING:
81                    tryConnecting();
82                    break;
83                default:
84                    Slog.e(TAG, "Unknown message");
85            }
86        }
87    };
88
89    private ServiceConnection mConnection = new ServiceConnection() {
90        @Override
91        public void onServiceConnected(ComponentName name, IBinder service) {
92            Slog.i(TAG, "MmsService connected");
93            synchronized (MmsServiceBroker.this) {
94                mService = IMms.Stub.asInterface(service);
95                MmsServiceBroker.this.notifyAll();
96            }
97        }
98
99        @Override
100        public void onServiceDisconnected(ComponentName name) {
101            Slog.i(TAG, "MmsService unexpectedly disconnected");
102            synchronized (MmsServiceBroker.this) {
103                mService = null;
104                MmsServiceBroker.this.notifyAll();
105            }
106            // Retry connecting, but not too eager (with a delay)
107            // since it may come back by itself.
108            mConnectionHandler.sendMessageDelayed(
109                    mConnectionHandler.obtainMessage(MSG_TRY_CONNECTING),
110                    RETRY_DELAY_ON_DISCONNECTION_MS);
111        }
112    };
113
114    public MmsServiceBroker(Context context) {
115        super(context);
116        mContext = context;
117        mService = null;
118    }
119
120    @Override
121    public void onStart() {
122        publishBinderService("imms", new BinderService());
123    }
124
125    public void systemRunning() {
126        Slog.i(TAG, "Delay connecting to MmsService until an API is called");
127    }
128
129    private void tryConnecting() {
130        Slog.i(TAG, "Connecting to MmsService");
131        synchronized (this) {
132            if (mService != null) {
133                Slog.d(TAG, "Already connected");
134                return;
135            }
136            final Intent intent = new Intent();
137            intent.setComponent(MMS_SERVICE_COMPONENT);
138            try {
139                if (!mContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE)) {
140                    Slog.e(TAG, "Failed to bind to MmsService");
141                }
142            } catch (SecurityException e) {
143                Slog.e(TAG, "Forbidden to bind to MmsService", e);
144            }
145        }
146    }
147
148    private void ensureService() {
149        synchronized (this) {
150            if (mService == null) {
151                // Service is not connected. Try blocking connecting.
152                Slog.w(TAG, "MmsService not connected. Try connecting...");
153                mConnectionHandler.sendMessage(
154                        mConnectionHandler.obtainMessage(MSG_TRY_CONNECTING));
155                final long shouldEnd =
156                        SystemClock.elapsedRealtime() + SERVICE_CONNECTION_WAIT_TIME_MS;
157                long waitTime = SERVICE_CONNECTION_WAIT_TIME_MS;
158                while (waitTime > 0) {
159                    try {
160                        // TODO: consider using Java concurrent construct instead of raw object wait
161                        this.wait(waitTime);
162                    } catch (InterruptedException e) {
163                        Slog.w(TAG, "Connection wait interrupted", e);
164                    }
165                    if (mService != null) {
166                        // Success
167                        return;
168                    }
169                    // Calculate remaining waiting time to make sure we wait the full timeout period
170                    waitTime = shouldEnd - SystemClock.elapsedRealtime();
171                }
172                // Timed out. Something's really wrong.
173                Slog.e(TAG, "Can not connect to MmsService (timed out)");
174                throw new RuntimeException("Timed out in connecting to MmsService");
175            }
176        }
177    }
178
179    /**
180     * Making sure when we obtain the mService instance it is always valid.
181     * Throws {@link RuntimeException} when it is empty.
182     */
183    private IMms getServiceGuarded() {
184        ensureService();
185        return mService;
186    }
187
188    private AppOpsManager getAppOpsManager() {
189        if (mAppOpsManager == null) {
190            mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
191        }
192        return mAppOpsManager;
193    }
194
195    private PackageManager getPackageManager() {
196        if (mPackageManager == null) {
197            mPackageManager = mContext.getPackageManager();
198        }
199        return mPackageManager;
200    }
201
202    private TelephonyManager getTelephonyManager() {
203        if (mTelephonyManager == null) {
204            mTelephonyManager = (TelephonyManager) mContext.getSystemService(
205                    Context.TELEPHONY_SERVICE);
206        }
207        return mTelephonyManager;
208    }
209
210    /*
211     * Throws a security exception unless the caller has carrier privilege.
212     */
213    private void enforceCarrierPrivilege() {
214        final String[] packages = getPackageManager().getPackagesForUid(Binder.getCallingUid());
215        for (String pkg : packages) {
216            if (getTelephonyManager().checkCarrierPrivilegesForPackage(pkg) ==
217                    TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
218                return;
219            }
220        }
221        throw new SecurityException("No carrier privilege");
222    }
223
224    private String getCallingPackageName() {
225        final String[] packages = getPackageManager().getPackagesForUid(Binder.getCallingUid());
226        if (packages != null && packages.length > 0) {
227            return packages[0];
228        }
229        return "unknown";
230    }
231
232    // Service API calls implementation, proxied to the real MmsService in "com.android.mms.service"
233    private final class BinderService extends IMms.Stub {
234        private static final String PHONE_PACKAGE_NAME = "com.android.phone";
235
236        @Override
237        public void sendMessage(int subId, String callingPkg, Uri contentUri,
238                String locationUrl, Bundle configOverrides, PendingIntent sentIntent)
239                        throws RemoteException {
240            Slog.d(TAG, "sendMessage() by " + callingPkg);
241            mContext.enforceCallingPermission(Manifest.permission.SEND_SMS, "Send MMS message");
242            if (getAppOpsManager().noteOp(AppOpsManager.OP_SEND_SMS, Binder.getCallingUid(),
243                    callingPkg) != AppOpsManager.MODE_ALLOWED) {
244                return;
245            }
246            contentUri = adjustUriForUserAndGrantPermission(contentUri,
247                    Telephony.Mms.Intents.MMS_SEND_ACTION,
248                    Intent.FLAG_GRANT_READ_URI_PERMISSION);
249            getServiceGuarded().sendMessage(subId, callingPkg, contentUri, locationUrl,
250                    configOverrides, sentIntent);
251        }
252
253        @Override
254        public void downloadMessage(int subId, String callingPkg, String locationUrl,
255                Uri contentUri, Bundle configOverrides,
256                PendingIntent downloadedIntent) throws RemoteException {
257            Slog.d(TAG, "downloadMessage() by " + callingPkg);
258            mContext.enforceCallingPermission(Manifest.permission.RECEIVE_MMS,
259                    "Download MMS message");
260            if (getAppOpsManager().noteOp(AppOpsManager.OP_RECEIVE_MMS, Binder.getCallingUid(),
261                    callingPkg) != AppOpsManager.MODE_ALLOWED) {
262                return;
263            }
264            contentUri = adjustUriForUserAndGrantPermission(contentUri,
265                    Telephony.Mms.Intents.MMS_DOWNLOAD_ACTION,
266                    Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
267
268            getServiceGuarded().downloadMessage(subId, callingPkg, locationUrl, contentUri,
269                    configOverrides, downloadedIntent);
270        }
271
272        @Override
273        public void updateMmsSendStatus(int messageRef, byte[] pdu, int status)
274                throws RemoteException {
275            enforceCarrierPrivilege();
276            getServiceGuarded().updateMmsSendStatus(messageRef, pdu, status);
277        }
278
279        @Override
280        public void updateMmsDownloadStatus(int messageRef, int status) throws RemoteException {
281            enforceCarrierPrivilege();
282            getServiceGuarded().updateMmsDownloadStatus(messageRef, status);
283        }
284
285        @Override
286        public Bundle getCarrierConfigValues(int subId) throws RemoteException {
287            Slog.d(TAG, "getCarrierConfigValues() by " + getCallingPackageName());
288            return getServiceGuarded().getCarrierConfigValues(subId);
289        }
290
291        @Override
292        public Uri importTextMessage(String callingPkg, String address, int type, String text,
293                long timestampMillis, boolean seen, boolean read) throws RemoteException {
294            mContext.enforceCallingPermission(Manifest.permission.WRITE_SMS, "Import SMS message");
295            if (getAppOpsManager().noteOp(AppOpsManager.OP_WRITE_SMS, Binder.getCallingUid(),
296                    callingPkg) != AppOpsManager.MODE_ALLOWED) {
297                // Silently fail AppOps failure due to not being the default SMS app
298                // while writing the TelephonyProvider
299                return FAKE_SMS_SENT_URI;
300            }
301            return getServiceGuarded().importTextMessage(
302                    callingPkg, address, type, text, timestampMillis, seen, read);
303        }
304
305        @Override
306        public Uri importMultimediaMessage(String callingPkg, Uri contentUri,
307                String messageId, long timestampSecs, boolean seen, boolean read)
308                        throws RemoteException {
309            mContext.enforceCallingPermission(Manifest.permission.WRITE_SMS, "Import MMS message");
310            if (getAppOpsManager().noteOp(AppOpsManager.OP_WRITE_SMS, Binder.getCallingUid(),
311                    callingPkg) != AppOpsManager.MODE_ALLOWED) {
312                // Silently fail AppOps failure due to not being the default SMS app
313                // while writing the TelephonyProvider
314                return FAKE_MMS_SENT_URI;
315            }
316            return getServiceGuarded().importMultimediaMessage(
317                    callingPkg, contentUri, messageId, timestampSecs, seen, read);
318        }
319
320        @Override
321        public boolean deleteStoredMessage(String callingPkg, Uri messageUri)
322                throws RemoteException {
323            mContext.enforceCallingPermission(Manifest.permission.WRITE_SMS,
324                    "Delete SMS/MMS message");
325            if (getAppOpsManager().noteOp(AppOpsManager.OP_WRITE_SMS, Binder.getCallingUid(),
326                    callingPkg) != AppOpsManager.MODE_ALLOWED) {
327                return false;
328            }
329            return getServiceGuarded().deleteStoredMessage(callingPkg, messageUri);
330        }
331
332        @Override
333        public boolean deleteStoredConversation(String callingPkg, long conversationId)
334                throws RemoteException {
335            mContext.enforceCallingPermission(Manifest.permission.WRITE_SMS, "Delete conversation");
336            if (getAppOpsManager().noteOp(AppOpsManager.OP_WRITE_SMS, Binder.getCallingUid(),
337                    callingPkg) != AppOpsManager.MODE_ALLOWED) {
338                return false;
339            }
340            return getServiceGuarded().deleteStoredConversation(callingPkg, conversationId);
341        }
342
343        @Override
344        public boolean updateStoredMessageStatus(String callingPkg, Uri messageUri,
345                ContentValues statusValues) throws RemoteException {
346            mContext.enforceCallingPermission(Manifest.permission.WRITE_SMS,
347                    "Update SMS/MMS message");
348            return getServiceGuarded()
349                    .updateStoredMessageStatus(callingPkg, messageUri, statusValues);
350        }
351
352        @Override
353        public boolean archiveStoredConversation(String callingPkg, long conversationId,
354                boolean archived) throws RemoteException {
355            mContext.enforceCallingPermission(Manifest.permission.WRITE_SMS,
356                    "Update SMS/MMS message");
357            return getServiceGuarded()
358                    .archiveStoredConversation(callingPkg, conversationId, archived);
359        }
360
361        @Override
362        public Uri addTextMessageDraft(String callingPkg, String address, String text)
363                throws RemoteException {
364            mContext.enforceCallingPermission(Manifest.permission.WRITE_SMS, "Add SMS draft");
365            if (getAppOpsManager().noteOp(AppOpsManager.OP_WRITE_SMS, Binder.getCallingUid(),
366                    callingPkg) != AppOpsManager.MODE_ALLOWED) {
367                // Silently fail AppOps failure due to not being the default SMS app
368                // while writing the TelephonyProvider
369                return FAKE_SMS_DRAFT_URI;
370            }
371            return getServiceGuarded().addTextMessageDraft(callingPkg, address, text);
372        }
373
374        @Override
375        public Uri addMultimediaMessageDraft(String callingPkg, Uri contentUri)
376                throws RemoteException {
377            mContext.enforceCallingPermission(Manifest.permission.WRITE_SMS, "Add MMS draft");
378            if (getAppOpsManager().noteOp(AppOpsManager.OP_WRITE_SMS, Binder.getCallingUid(),
379                    callingPkg) != AppOpsManager.MODE_ALLOWED) {
380                // Silently fail AppOps failure due to not being the default SMS app
381                // while writing the TelephonyProvider
382                return FAKE_MMS_DRAFT_URI;
383            }
384            return getServiceGuarded().addMultimediaMessageDraft(callingPkg, contentUri);
385        }
386
387        @Override
388        public void sendStoredMessage(int subId, String callingPkg, Uri messageUri,
389                Bundle configOverrides, PendingIntent sentIntent) throws RemoteException {
390            mContext.enforceCallingPermission(Manifest.permission.SEND_SMS,
391                    "Send stored MMS message");
392            if (getAppOpsManager().noteOp(AppOpsManager.OP_SEND_SMS, Binder.getCallingUid(),
393                    callingPkg) != AppOpsManager.MODE_ALLOWED) {
394                return;
395            }
396            getServiceGuarded().sendStoredMessage(subId, callingPkg, messageUri, configOverrides,
397                    sentIntent);
398        }
399
400        @Override
401        public void setAutoPersisting(String callingPkg, boolean enabled) throws RemoteException {
402            mContext.enforceCallingPermission(Manifest.permission.WRITE_SMS, "Set auto persist");
403            if (getAppOpsManager().noteOp(AppOpsManager.OP_WRITE_SMS, Binder.getCallingUid(),
404                    callingPkg) != AppOpsManager.MODE_ALLOWED) {
405                return;
406            }
407            getServiceGuarded().setAutoPersisting(callingPkg, enabled);
408        }
409
410        @Override
411        public boolean getAutoPersisting() throws RemoteException {
412            return getServiceGuarded().getAutoPersisting();
413        }
414
415        /**
416         * Modifies the Uri to contain the caller's userId, if necessary.
417         * Grants the phone package on primary user permission to access the contentUri,
418         * even if the caller is not in the primary user.
419         *
420         * @param contentUri The Uri to adjust
421         * @param action The intent action used to find the associated carrier app
422         * @param permission The permission to add
423         * @return The adjusted Uri containing the calling userId.
424         */
425        private Uri adjustUriForUserAndGrantPermission(Uri contentUri, String action,
426                int permission) {
427            final int callingUserId = UserHandle.getCallingUserId();
428            if (callingUserId != UserHandle.USER_OWNER) {
429                contentUri = ContentProvider.maybeAddUserId(contentUri, callingUserId);
430            }
431            long token = Binder.clearCallingIdentity();
432            try {
433                mContext.grantUriPermission(PHONE_PACKAGE_NAME, contentUri, permission);
434
435                // Grant permission for the carrier app.
436                Intent intent = new Intent(action);
437                TelephonyManager telephonyManager =
438                    (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
439                List<String> carrierPackages = telephonyManager.getCarrierPackageNamesForIntent(
440                        intent);
441                if (carrierPackages != null && carrierPackages.size() == 1) {
442                    mContext.grantUriPermission(carrierPackages.get(0), contentUri, permission);
443                }
444            } finally {
445                Binder.restoreCallingIdentity(token);
446            }
447            return contentUri;
448        }
449    }
450}
451