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.service.carrier.CarrierMessagingService;
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    private String getCallingPackageName() {
211        final String[] packages = getPackageManager().getPackagesForUid(Binder.getCallingUid());
212        if (packages != null && packages.length > 0) {
213            return packages[0];
214        }
215        return "unknown";
216    }
217
218    // Service API calls implementation, proxied to the real MmsService in "com.android.mms.service"
219    private final class BinderService extends IMms.Stub {
220        private static final String PHONE_PACKAGE_NAME = "com.android.phone";
221
222        @Override
223        public void sendMessage(int subId, String callingPkg, Uri contentUri,
224                String locationUrl, Bundle configOverrides, PendingIntent sentIntent)
225                        throws RemoteException {
226            Slog.d(TAG, "sendMessage() by " + callingPkg);
227            mContext.enforceCallingPermission(Manifest.permission.SEND_SMS, "Send MMS message");
228            if (getAppOpsManager().noteOp(AppOpsManager.OP_SEND_SMS, Binder.getCallingUid(),
229                    callingPkg) != AppOpsManager.MODE_ALLOWED) {
230                return;
231            }
232            contentUri = adjustUriForUserAndGrantPermission(contentUri,
233                    CarrierMessagingService.SERVICE_INTERFACE,
234                    Intent.FLAG_GRANT_READ_URI_PERMISSION);
235            getServiceGuarded().sendMessage(subId, callingPkg, contentUri, locationUrl,
236                    configOverrides, sentIntent);
237        }
238
239        @Override
240        public void downloadMessage(int subId, String callingPkg, String locationUrl,
241                Uri contentUri, Bundle configOverrides,
242                PendingIntent downloadedIntent) throws RemoteException {
243            Slog.d(TAG, "downloadMessage() by " + callingPkg);
244            mContext.enforceCallingPermission(Manifest.permission.RECEIVE_MMS,
245                    "Download MMS message");
246            if (getAppOpsManager().noteOp(AppOpsManager.OP_RECEIVE_MMS, Binder.getCallingUid(),
247                    callingPkg) != AppOpsManager.MODE_ALLOWED) {
248                return;
249            }
250            contentUri = adjustUriForUserAndGrantPermission(contentUri,
251                    CarrierMessagingService.SERVICE_INTERFACE,
252                    Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
253
254            getServiceGuarded().downloadMessage(subId, callingPkg, locationUrl, contentUri,
255                    configOverrides, downloadedIntent);
256        }
257
258        @Override
259        public Bundle getCarrierConfigValues(int subId) throws RemoteException {
260            Slog.d(TAG, "getCarrierConfigValues() by " + getCallingPackageName());
261            return getServiceGuarded().getCarrierConfigValues(subId);
262        }
263
264        @Override
265        public Uri importTextMessage(String callingPkg, String address, int type, String text,
266                long timestampMillis, boolean seen, boolean read) throws RemoteException {
267            mContext.enforceCallingPermission(Manifest.permission.WRITE_SMS, "Import SMS message");
268            if (getAppOpsManager().noteOp(AppOpsManager.OP_WRITE_SMS, Binder.getCallingUid(),
269                    callingPkg) != AppOpsManager.MODE_ALLOWED) {
270                // Silently fail AppOps failure due to not being the default SMS app
271                // while writing the TelephonyProvider
272                return FAKE_SMS_SENT_URI;
273            }
274            return getServiceGuarded().importTextMessage(
275                    callingPkg, address, type, text, timestampMillis, seen, read);
276        }
277
278        @Override
279        public Uri importMultimediaMessage(String callingPkg, Uri contentUri,
280                String messageId, long timestampSecs, boolean seen, boolean read)
281                        throws RemoteException {
282            mContext.enforceCallingPermission(Manifest.permission.WRITE_SMS, "Import MMS message");
283            if (getAppOpsManager().noteOp(AppOpsManager.OP_WRITE_SMS, Binder.getCallingUid(),
284                    callingPkg) != AppOpsManager.MODE_ALLOWED) {
285                // Silently fail AppOps failure due to not being the default SMS app
286                // while writing the TelephonyProvider
287                return FAKE_MMS_SENT_URI;
288            }
289            return getServiceGuarded().importMultimediaMessage(
290                    callingPkg, contentUri, messageId, timestampSecs, seen, read);
291        }
292
293        @Override
294        public boolean deleteStoredMessage(String callingPkg, Uri messageUri)
295                throws RemoteException {
296            mContext.enforceCallingPermission(Manifest.permission.WRITE_SMS,
297                    "Delete SMS/MMS message");
298            if (getAppOpsManager().noteOp(AppOpsManager.OP_WRITE_SMS, Binder.getCallingUid(),
299                    callingPkg) != AppOpsManager.MODE_ALLOWED) {
300                return false;
301            }
302            return getServiceGuarded().deleteStoredMessage(callingPkg, messageUri);
303        }
304
305        @Override
306        public boolean deleteStoredConversation(String callingPkg, long conversationId)
307                throws RemoteException {
308            mContext.enforceCallingPermission(Manifest.permission.WRITE_SMS, "Delete conversation");
309            if (getAppOpsManager().noteOp(AppOpsManager.OP_WRITE_SMS, Binder.getCallingUid(),
310                    callingPkg) != AppOpsManager.MODE_ALLOWED) {
311                return false;
312            }
313            return getServiceGuarded().deleteStoredConversation(callingPkg, conversationId);
314        }
315
316        @Override
317        public boolean updateStoredMessageStatus(String callingPkg, Uri messageUri,
318                ContentValues statusValues) throws RemoteException {
319            mContext.enforceCallingPermission(Manifest.permission.WRITE_SMS,
320                    "Update SMS/MMS message");
321            return getServiceGuarded()
322                    .updateStoredMessageStatus(callingPkg, messageUri, statusValues);
323        }
324
325        @Override
326        public boolean archiveStoredConversation(String callingPkg, long conversationId,
327                boolean archived) throws RemoteException {
328            mContext.enforceCallingPermission(Manifest.permission.WRITE_SMS,
329                    "Update SMS/MMS message");
330            return getServiceGuarded()
331                    .archiveStoredConversation(callingPkg, conversationId, archived);
332        }
333
334        @Override
335        public Uri addTextMessageDraft(String callingPkg, String address, String text)
336                throws RemoteException {
337            mContext.enforceCallingPermission(Manifest.permission.WRITE_SMS, "Add SMS draft");
338            if (getAppOpsManager().noteOp(AppOpsManager.OP_WRITE_SMS, Binder.getCallingUid(),
339                    callingPkg) != AppOpsManager.MODE_ALLOWED) {
340                // Silently fail AppOps failure due to not being the default SMS app
341                // while writing the TelephonyProvider
342                return FAKE_SMS_DRAFT_URI;
343            }
344            return getServiceGuarded().addTextMessageDraft(callingPkg, address, text);
345        }
346
347        @Override
348        public Uri addMultimediaMessageDraft(String callingPkg, Uri contentUri)
349                throws RemoteException {
350            mContext.enforceCallingPermission(Manifest.permission.WRITE_SMS, "Add MMS 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_MMS_DRAFT_URI;
356            }
357            return getServiceGuarded().addMultimediaMessageDraft(callingPkg, contentUri);
358        }
359
360        @Override
361        public void sendStoredMessage(int subId, String callingPkg, Uri messageUri,
362                Bundle configOverrides, PendingIntent sentIntent) throws RemoteException {
363            mContext.enforceCallingPermission(Manifest.permission.SEND_SMS,
364                    "Send stored MMS message");
365            if (getAppOpsManager().noteOp(AppOpsManager.OP_SEND_SMS, Binder.getCallingUid(),
366                    callingPkg) != AppOpsManager.MODE_ALLOWED) {
367                return;
368            }
369            getServiceGuarded().sendStoredMessage(subId, callingPkg, messageUri, configOverrides,
370                    sentIntent);
371        }
372
373        @Override
374        public void setAutoPersisting(String callingPkg, boolean enabled) throws RemoteException {
375            mContext.enforceCallingPermission(Manifest.permission.WRITE_SMS, "Set auto persist");
376            if (getAppOpsManager().noteOp(AppOpsManager.OP_WRITE_SMS, Binder.getCallingUid(),
377                    callingPkg) != AppOpsManager.MODE_ALLOWED) {
378                return;
379            }
380            getServiceGuarded().setAutoPersisting(callingPkg, enabled);
381        }
382
383        @Override
384        public boolean getAutoPersisting() throws RemoteException {
385            return getServiceGuarded().getAutoPersisting();
386        }
387
388        /**
389         * Modifies the Uri to contain the caller's userId, if necessary.
390         * Grants the phone package on primary user permission to access the contentUri,
391         * even if the caller is not in the primary user.
392         *
393         * @param contentUri The Uri to adjust
394         * @param action The intent action used to find the associated carrier app
395         * @param permission The permission to add
396         * @return The adjusted Uri containing the calling userId.
397         */
398        private Uri adjustUriForUserAndGrantPermission(Uri contentUri, String action,
399                int permission) {
400            final int callingUserId = UserHandle.getCallingUserId();
401            if (callingUserId != UserHandle.USER_OWNER) {
402                contentUri = ContentProvider.maybeAddUserId(contentUri, callingUserId);
403            }
404            long token = Binder.clearCallingIdentity();
405            try {
406                mContext.grantUriPermission(PHONE_PACKAGE_NAME, contentUri, permission);
407
408                // Grant permission for the carrier app.
409                Intent intent = new Intent(action);
410                TelephonyManager telephonyManager =
411                    (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
412                List<String> carrierPackages = telephonyManager.getCarrierPackageNamesForIntent(
413                        intent);
414                if (carrierPackages != null && carrierPackages.size() == 1) {
415                    mContext.grantUriPermission(carrierPackages.get(0), contentUri, permission);
416                }
417            } finally {
418                Binder.restoreCallingIdentity(token);
419            }
420            return contentUri;
421        }
422    }
423}
424