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