MmsServiceBroker.java revision ee04543d58bf570fd77202d6d2cd861476c3daf6
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 com.android.internal.telephony.IMms;
20
21import android.Manifest;
22import android.app.AppOpsManager;
23import android.app.PendingIntent;
24import android.content.ComponentName;
25import android.content.ContentValues;
26import android.content.Context;
27import android.content.Intent;
28import android.content.ServiceConnection;
29import android.content.pm.PackageManager;
30import android.net.Uri;
31import android.os.Binder;
32import android.os.Bundle;
33import android.os.Handler;
34import android.os.IBinder;
35import android.os.Message;
36import android.os.RemoteException;
37import android.os.ServiceManager;
38import android.telephony.TelephonyManager;
39import android.util.Slog;
40
41/**
42 * This class is a proxy for MmsService APIs. We need this because MmsService runs
43 * in phone process and may crash anytime. This manages a connection to the actual
44 * MmsService and bridges the public SMS/MMS APIs with MmsService implementation.
45 */
46public class MmsServiceBroker extends SystemService {
47    private static final String TAG = "MmsServiceBroker";
48
49    private static final ComponentName MMS_SERVICE_COMPONENT =
50            new ComponentName("com.android.mms.service", "com.android.mms.service.MmsService");
51
52    private static final int MSG_TRY_CONNECTING = 1;
53
54    private static final Uri FAKE_SMS_SENT_URI = Uri.parse("content://sms/sent/0");
55    private static final Uri FAKE_MMS_SENT_URI = Uri.parse("content://mms/sent/0");
56    private static final Uri FAKE_SMS_DRAFT_URI = Uri.parse("content://sms/draft/0");
57    private static final Uri FAKE_MMS_DRAFT_URI = Uri.parse("content://mms/draft/0");
58
59    private Context mContext;
60    // The actual MMS service instance to invoke
61    private volatile IMms mService;
62    private boolean mIsConnecting;
63
64    // Cached system service instances
65    private volatile AppOpsManager mAppOpsManager = null;
66    private volatile PackageManager mPackageManager = null;
67    private volatile TelephonyManager mTelephonyManager = null;
68
69    private final Handler mConnectionHandler = new Handler() {
70        @Override
71        public void handleMessage(Message msg) {
72            switch (msg.what) {
73                case MSG_TRY_CONNECTING:
74                    tryConnecting();
75                    break;
76                default:
77                    Slog.e(TAG, "Unknown message");
78            }
79        }
80    };
81
82    private ServiceConnection mConnection = new ServiceConnection() {
83        @Override
84        public void onServiceConnected(ComponentName name, IBinder service) {
85            Slog.i(TAG, "MmsService connected");
86            synchronized (MmsServiceBroker.this) {
87                mService = IMms.Stub.asInterface(service);
88                mIsConnecting = false;
89            }
90        }
91
92        @Override
93        public void onServiceDisconnected(ComponentName name) {
94            Slog.i(TAG, "MmsService unexpectedly disconnected");
95            synchronized (MmsServiceBroker.this) {
96                mService = null;
97                mIsConnecting = false;
98            }
99        }
100    };
101
102    public MmsServiceBroker(Context context) {
103        super(context);
104        mContext = context;
105        mService = null;
106        mIsConnecting = false;
107    }
108
109    @Override
110    public void onStart() {
111        publishBinderService("imms", new BinderService());
112    }
113
114    public void systemRunning() {
115        tryConnecting();
116    }
117
118    private void tryConnecting() {
119        Slog.i(TAG, "Connecting to MmsService");
120        synchronized (this) {
121            if (mIsConnecting) {
122                Slog.d(TAG, "Already connecting");
123                return;
124            }
125            final Intent intent = new Intent();
126            intent.setComponent(MMS_SERVICE_COMPONENT);
127            try {
128                if (mContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE)) {
129                    mIsConnecting = true;
130                } else {
131                    Slog.e(TAG, "Failed to connect to MmsService");
132                }
133            } catch (SecurityException e) {
134                Slog.e(TAG, "Forbidden to connect to MmsService", e);
135            }
136        }
137    }
138
139    private void ensureService() {
140        if (mService == null) {
141            // Service instance lost, kicking off the connection again
142            mConnectionHandler.sendMessage(mConnectionHandler.obtainMessage(MSG_TRY_CONNECTING));
143            throw new RuntimeException("MMS service is not connected");
144        }
145    }
146
147    /**
148     * Making sure when we obtain the mService instance it is always valid.
149     * Throws {@link RuntimeException} when it is empty.
150     */
151    private IMms getServiceGuarded() {
152        ensureService();
153        return mService;
154    }
155
156    private AppOpsManager getAppOpsManager() {
157        if (mAppOpsManager == null) {
158            mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
159        }
160        return mAppOpsManager;
161    }
162
163    private PackageManager getPackageManager() {
164        if (mPackageManager == null) {
165            mPackageManager = mContext.getPackageManager();
166        }
167        return mPackageManager;
168    }
169
170    private TelephonyManager getTelephonyManager() {
171        if (mTelephonyManager == null) {
172            mTelephonyManager = (TelephonyManager) mContext.getSystemService(
173                    Context.TELEPHONY_SERVICE);
174        }
175        return mTelephonyManager;
176    }
177
178    /*
179     * Throws a security exception unless the caller has carrier privilege.
180     */
181    private void enforceCarrierPrivilege() {
182        String[] packages = getPackageManager().getPackagesForUid(Binder.getCallingUid());
183        for (String pkg : packages) {
184            if (getTelephonyManager().checkCarrierPrivilegesForPackage(pkg) ==
185                    TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
186                return;
187            }
188        }
189        throw new SecurityException("No carrier privilege");
190    }
191
192    // Service API calls implementation, proxied to the real MmsService in "com.android.mms.service"
193    private final class BinderService extends IMms.Stub {
194        @Override
195        public void sendMessage(long subId, String callingPkg, Uri contentUri,
196                String locationUrl, Bundle configOverrides, PendingIntent sentIntent)
197                        throws RemoteException {
198            mContext.enforceCallingPermission(Manifest.permission.SEND_SMS, "Send MMS message");
199            if (getAppOpsManager().noteOp(AppOpsManager.OP_SEND_SMS, Binder.getCallingUid(),
200                    callingPkg) != AppOpsManager.MODE_ALLOWED) {
201                return;
202            }
203            getServiceGuarded().sendMessage(subId, callingPkg, contentUri, locationUrl,
204                    configOverrides, sentIntent);
205        }
206
207        @Override
208        public void downloadMessage(long subId, String callingPkg, String locationUrl,
209                Uri contentUri, Bundle configOverrides,
210                PendingIntent downloadedIntent) throws RemoteException {
211            mContext.enforceCallingPermission(Manifest.permission.RECEIVE_MMS,
212                    "Download MMS message");
213            if (getAppOpsManager().noteOp(AppOpsManager.OP_RECEIVE_MMS, Binder.getCallingUid(),
214                    callingPkg) != AppOpsManager.MODE_ALLOWED) {
215                return;
216            }
217            getServiceGuarded().downloadMessage(subId, callingPkg, locationUrl, contentUri,
218                    configOverrides, downloadedIntent);
219        }
220
221        @Override
222        public void updateMmsSendStatus(int messageRef, byte[] pdu, int status)
223                throws RemoteException {
224            enforceCarrierPrivilege();
225            getServiceGuarded().updateMmsSendStatus(messageRef, pdu, status);
226        }
227
228        @Override
229        public void updateMmsDownloadStatus(int messageRef, int status) throws RemoteException {
230            enforceCarrierPrivilege();
231            getServiceGuarded().updateMmsDownloadStatus(messageRef, status);
232        }
233
234        @Override
235        public Bundle getCarrierConfigValues(long subId) throws RemoteException {
236            return getServiceGuarded().getCarrierConfigValues(subId);
237        }
238
239        @Override
240        public Uri importTextMessage(String callingPkg, String address, int type, String text,
241                long timestampMillis, boolean seen, boolean read) throws RemoteException {
242            mContext.enforceCallingPermission(Manifest.permission.WRITE_SMS, "Import SMS message");
243            if (getAppOpsManager().noteOp(AppOpsManager.OP_WRITE_SMS, Binder.getCallingUid(),
244                    callingPkg) != AppOpsManager.MODE_ALLOWED) {
245                // Silently fail AppOps failure due to not being the default SMS app
246                // while writing the TelephonyProvider
247                return FAKE_SMS_SENT_URI;
248            }
249            return getServiceGuarded().importTextMessage(
250                    callingPkg, address, type, text, timestampMillis, seen, read);
251        }
252
253        @Override
254        public Uri importMultimediaMessage(String callingPkg, Uri contentUri,
255                String messageId, long timestampSecs, boolean seen, boolean read)
256                        throws RemoteException {
257            mContext.enforceCallingPermission(Manifest.permission.WRITE_SMS, "Import MMS message");
258            if (getAppOpsManager().noteOp(AppOpsManager.OP_WRITE_SMS, Binder.getCallingUid(),
259                    callingPkg) != AppOpsManager.MODE_ALLOWED) {
260                // Silently fail AppOps failure due to not being the default SMS app
261                // while writing the TelephonyProvider
262                return FAKE_MMS_SENT_URI;
263            }
264            return getServiceGuarded().importMultimediaMessage(
265                    callingPkg, contentUri, messageId, timestampSecs, seen, read);
266        }
267
268        @Override
269        public boolean deleteStoredMessage(String callingPkg, Uri messageUri)
270                throws RemoteException {
271            mContext.enforceCallingPermission(Manifest.permission.WRITE_SMS,
272                    "Delete SMS/MMS message");
273            if (getAppOpsManager().noteOp(AppOpsManager.OP_WRITE_SMS, Binder.getCallingUid(),
274                    callingPkg) != AppOpsManager.MODE_ALLOWED) {
275                return false;
276            }
277            return getServiceGuarded().deleteStoredMessage(callingPkg, messageUri);
278        }
279
280        @Override
281        public boolean deleteStoredConversation(String callingPkg, long conversationId)
282                throws RemoteException {
283            mContext.enforceCallingPermission(Manifest.permission.WRITE_SMS, "Delete conversation");
284            if (getAppOpsManager().noteOp(AppOpsManager.OP_WRITE_SMS, Binder.getCallingUid(),
285                    callingPkg) != AppOpsManager.MODE_ALLOWED) {
286                return false;
287            }
288            return getServiceGuarded().deleteStoredConversation(callingPkg, conversationId);
289        }
290
291        @Override
292        public boolean updateStoredMessageStatus(String callingPkg, Uri messageUri,
293                ContentValues statusValues) throws RemoteException {
294            mContext.enforceCallingPermission(Manifest.permission.WRITE_SMS,
295                    "Update SMS/MMS message");
296            return getServiceGuarded()
297                    .updateStoredMessageStatus(callingPkg, messageUri, statusValues);
298        }
299
300        @Override
301        public boolean archiveStoredConversation(String callingPkg, long conversationId,
302                boolean archived) throws RemoteException {
303            mContext.enforceCallingPermission(Manifest.permission.WRITE_SMS,
304                    "Update SMS/MMS message");
305            return getServiceGuarded()
306                    .archiveStoredConversation(callingPkg, conversationId, archived);
307        }
308
309        @Override
310        public Uri addTextMessageDraft(String callingPkg, String address, String text)
311                throws RemoteException {
312            mContext.enforceCallingPermission(Manifest.permission.WRITE_SMS, "Add SMS draft");
313            if (getAppOpsManager().noteOp(AppOpsManager.OP_WRITE_SMS, Binder.getCallingUid(),
314                    callingPkg) != AppOpsManager.MODE_ALLOWED) {
315                // Silently fail AppOps failure due to not being the default SMS app
316                // while writing the TelephonyProvider
317                return FAKE_SMS_DRAFT_URI;
318            }
319            return getServiceGuarded().addTextMessageDraft(callingPkg, address, text);
320        }
321
322        @Override
323        public Uri addMultimediaMessageDraft(String callingPkg, Uri contentUri)
324                throws RemoteException {
325            mContext.enforceCallingPermission(Manifest.permission.WRITE_SMS, "Add MMS draft");
326            if (getAppOpsManager().noteOp(AppOpsManager.OP_WRITE_SMS, Binder.getCallingUid(),
327                    callingPkg) != AppOpsManager.MODE_ALLOWED) {
328                // Silently fail AppOps failure due to not being the default SMS app
329                // while writing the TelephonyProvider
330                return FAKE_MMS_DRAFT_URI;
331            }
332            return getServiceGuarded().addMultimediaMessageDraft(callingPkg, contentUri);
333        }
334
335        @Override
336        public void sendStoredMessage(long subId, String callingPkg, Uri messageUri,
337                Bundle configOverrides, PendingIntent sentIntent) throws RemoteException {
338            mContext.enforceCallingPermission(Manifest.permission.SEND_SMS,
339                    "Send stored MMS message");
340            if (getAppOpsManager().noteOp(AppOpsManager.OP_SEND_SMS, Binder.getCallingUid(),
341                    callingPkg) != AppOpsManager.MODE_ALLOWED) {
342                return;
343            }
344            getServiceGuarded().sendStoredMessage(subId, callingPkg, messageUri, configOverrides,
345                    sentIntent);
346        }
347
348        @Override
349        public void setAutoPersisting(String callingPkg, boolean enabled) throws RemoteException {
350            mContext.enforceCallingPermission(Manifest.permission.WRITE_SMS, "Set auto persist");
351            if (getAppOpsManager().noteOp(AppOpsManager.OP_WRITE_SMS, Binder.getCallingUid(),
352                    callingPkg) != AppOpsManager.MODE_ALLOWED) {
353                return;
354            }
355            getServiceGuarded().setAutoPersisting(callingPkg, enabled);
356        }
357
358        @Override
359        public boolean getAutoPersisting() throws RemoteException {
360            return getServiceGuarded().getAutoPersisting();
361        }
362    }
363}
364