ContentService.java revision 4b9d79c30eccb61645d98a4f0d49b7769e8c7ccc
1/*
2 * Copyright (C) 2006 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.content;
18
19import android.Manifest;
20import android.accounts.Account;
21import android.app.ActivityManager;
22import android.content.ComponentName;
23import android.content.ContentResolver;
24import android.content.Context;
25import android.content.IContentService;
26import android.content.ISyncStatusObserver;
27import android.content.PeriodicSync;
28import android.content.pm.PackageManager;
29import android.content.SyncAdapterType;
30import android.content.SyncInfo;
31import android.content.SyncRequest;
32import android.content.SyncStatusInfo;
33import android.database.IContentObserver;
34import android.database.sqlite.SQLiteException;
35import android.net.Uri;
36import android.os.Binder;
37import android.os.Bundle;
38import android.os.IBinder;
39import android.os.Parcel;
40import android.os.RemoteException;
41import android.os.ServiceManager;
42import android.os.SystemProperties;
43import android.os.UserHandle;
44import android.text.TextUtils;
45import android.util.Log;
46import android.util.Slog;
47import android.util.SparseIntArray;
48
49import java.io.FileDescriptor;
50import java.io.PrintWriter;
51import java.security.InvalidParameterException;
52import java.util.ArrayList;
53import java.util.Collections;
54import java.util.Comparator;
55import java.util.List;
56
57/**
58 * {@hide}
59 */
60public final class ContentService extends IContentService.Stub {
61    private static final String TAG = "ContentService";
62    private Context mContext;
63    private boolean mFactoryTest;
64    private final ObserverNode mRootNode = new ObserverNode("");
65    private SyncManager mSyncManager = null;
66    private final Object mSyncManagerLock = new Object();
67
68    private SyncManager getSyncManager() {
69        if (SystemProperties.getBoolean("config.disable_network", false)) {
70            return null;
71        }
72
73        synchronized(mSyncManagerLock) {
74            try {
75                // Try to create the SyncManager, return null if it fails (e.g. the disk is full).
76                if (mSyncManager == null) mSyncManager = new SyncManager(mContext, mFactoryTest);
77            } catch (SQLiteException e) {
78                Log.e(TAG, "Can't create SyncManager", e);
79            }
80            return mSyncManager;
81        }
82    }
83
84    @Override
85    protected synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
86        mContext.enforceCallingOrSelfPermission(Manifest.permission.DUMP,
87                "caller doesn't have the DUMP permission");
88
89        // This makes it so that future permission checks will be in the context of this
90        // process rather than the caller's process. We will restore this before returning.
91        long identityToken = clearCallingIdentity();
92        try {
93            if (mSyncManager == null) {
94                pw.println("No SyncManager created!  (Disk full?)");
95            } else {
96                mSyncManager.dump(fd, pw);
97            }
98            pw.println();
99            pw.println("Observer tree:");
100            synchronized (mRootNode) {
101                int[] counts = new int[2];
102                final SparseIntArray pidCounts = new SparseIntArray();
103                mRootNode.dumpLocked(fd, pw, args, "", "  ", counts, pidCounts);
104                pw.println();
105                ArrayList<Integer> sorted = new ArrayList<Integer>();
106                for (int i=0; i<pidCounts.size(); i++) {
107                    sorted.add(pidCounts.keyAt(i));
108                }
109                Collections.sort(sorted, new Comparator<Integer>() {
110                    @Override
111                    public int compare(Integer lhs, Integer rhs) {
112                        int lc = pidCounts.get(lhs);
113                        int rc = pidCounts.get(rhs);
114                        if (lc < rc) {
115                            return 1;
116                        } else if (lc > rc) {
117                            return -1;
118                        }
119                        return 0;
120                    }
121
122                });
123                for (int i=0; i<sorted.size(); i++) {
124                    int pid = sorted.get(i);
125                    pw.print("  pid "); pw.print(pid); pw.print(": ");
126                            pw.print(pidCounts.get(pid)); pw.println(" observers");
127                }
128                pw.println();
129                pw.print(" Total number of nodes: "); pw.println(counts[0]);
130                pw.print(" Total number of observers: "); pw.println(counts[1]);
131            }
132        } finally {
133            restoreCallingIdentity(identityToken);
134        }
135    }
136
137    @Override
138    public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
139            throws RemoteException {
140        try {
141            return super.onTransact(code, data, reply, flags);
142        } catch (RuntimeException e) {
143            // The content service only throws security exceptions, so let's
144            // log all others.
145            if (!(e instanceof SecurityException)) {
146                Slog.wtf(TAG, "Content Service Crash", e);
147            }
148            throw e;
149        }
150    }
151
152    /*package*/ ContentService(Context context, boolean factoryTest) {
153        mContext = context;
154        mFactoryTest = factoryTest;
155    }
156
157    public void systemReady() {
158        getSyncManager();
159    }
160
161    /**
162     * Register a content observer tied to a specific user's view of the provider.
163     * @param userHandle the user whose view of the provider is to be observed.  May be
164     *     the calling user without requiring any permission, otherwise the caller needs to
165     *     hold the INTERACT_ACROSS_USERS_FULL permission.  Pseudousers USER_ALL and
166     *     USER_CURRENT are properly handled; all other pseudousers are forbidden.
167     */
168    @Override
169    public void registerContentObserver(Uri uri, boolean notifyForDescendants,
170            IContentObserver observer, int userHandle) {
171        if (observer == null || uri == null) {
172            throw new IllegalArgumentException("You must pass a valid uri and observer");
173        }
174
175        final int callingUser = UserHandle.getCallingUserId();
176        if (callingUser != userHandle) {
177            mContext.enforceCallingOrSelfPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL,
178                    "no permission to observe other users' provider view");
179        }
180
181        if (userHandle < 0) {
182            if (userHandle == UserHandle.USER_CURRENT) {
183                userHandle = ActivityManager.getCurrentUser();
184            } else if (userHandle != UserHandle.USER_ALL) {
185                throw new InvalidParameterException("Bad user handle for registerContentObserver: "
186                        + userHandle);
187            }
188        }
189
190        synchronized (mRootNode) {
191            mRootNode.addObserverLocked(uri, observer, notifyForDescendants, mRootNode,
192                    Binder.getCallingUid(), Binder.getCallingPid(), userHandle);
193            if (false) Log.v(TAG, "Registered observer " + observer + " at " + uri +
194                    " with notifyForDescendants " + notifyForDescendants);
195        }
196    }
197
198    public void registerContentObserver(Uri uri, boolean notifyForDescendants,
199            IContentObserver observer) {
200        registerContentObserver(uri, notifyForDescendants, observer,
201                UserHandle.getCallingUserId());
202    }
203
204    public void unregisterContentObserver(IContentObserver observer) {
205        if (observer == null) {
206            throw new IllegalArgumentException("You must pass a valid observer");
207        }
208        synchronized (mRootNode) {
209            mRootNode.removeObserverLocked(observer);
210            if (false) Log.v(TAG, "Unregistered observer " + observer);
211        }
212    }
213
214    /**
215     * Notify observers of a particular user's view of the provider.
216     * @param userHandle the user whose view of the provider is to be notified.  May be
217     *     the calling user without requiring any permission, otherwise the caller needs to
218     *     hold the INTERACT_ACROSS_USERS_FULL permission.  Pseudousers USER_ALL and
219     *     USER_CURRENT are properly interpreted; no other pseudousers are allowed.
220     */
221    @Override
222    public void notifyChange(Uri uri, IContentObserver observer,
223            boolean observerWantsSelfNotifications, boolean syncToNetwork,
224            int userHandle) {
225        if (Log.isLoggable(TAG, Log.VERBOSE)) {
226            Log.v(TAG, "Notifying update of " + uri + " for user " + userHandle
227                    + " from observer " + observer + ", syncToNetwork " + syncToNetwork);
228        }
229
230        // Notify for any user other than the caller's own requires permission.
231        final int callingUserHandle = UserHandle.getCallingUserId();
232        if (userHandle != callingUserHandle) {
233            mContext.enforceCallingOrSelfPermission(Manifest.permission.INTERACT_ACROSS_USERS,
234                    "no permission to notify other users");
235        }
236
237        // We passed the permission check; resolve pseudouser targets as appropriate
238        if (userHandle < 0) {
239            if (userHandle == UserHandle.USER_CURRENT) {
240                userHandle = ActivityManager.getCurrentUser();
241            } else if (userHandle != UserHandle.USER_ALL) {
242                throw new InvalidParameterException("Bad user handle for notifyChange: "
243                        + userHandle);
244            }
245        }
246
247        final int uid = Binder.getCallingUid();
248        // This makes it so that future permission checks will be in the context of this
249        // process rather than the caller's process. We will restore this before returning.
250        long identityToken = clearCallingIdentity();
251        try {
252            ArrayList<ObserverCall> calls = new ArrayList<ObserverCall>();
253            synchronized (mRootNode) {
254                mRootNode.collectObserversLocked(uri, 0, observer, observerWantsSelfNotifications,
255                        userHandle, calls);
256            }
257            final int numCalls = calls.size();
258            for (int i=0; i<numCalls; i++) {
259                ObserverCall oc = calls.get(i);
260                try {
261                    oc.mObserver.onChange(oc.mSelfChange, uri);
262                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
263                        Log.v(TAG, "Notified " + oc.mObserver + " of " + "update at " + uri);
264                    }
265                } catch (RemoteException ex) {
266                    synchronized (mRootNode) {
267                        Log.w(TAG, "Found dead observer, removing");
268                        IBinder binder = oc.mObserver.asBinder();
269                        final ArrayList<ObserverNode.ObserverEntry> list
270                                = oc.mNode.mObservers;
271                        int numList = list.size();
272                        for (int j=0; j<numList; j++) {
273                            ObserverNode.ObserverEntry oe = list.get(j);
274                            if (oe.observer.asBinder() == binder) {
275                                list.remove(j);
276                                j--;
277                                numList--;
278                            }
279                        }
280                    }
281                }
282            }
283            if (syncToNetwork) {
284                SyncManager syncManager = getSyncManager();
285                if (syncManager != null) {
286                    syncManager.scheduleLocalSync(null /* all accounts */, callingUserHandle, uid,
287                            uri.getAuthority());
288                }
289            }
290        } finally {
291            restoreCallingIdentity(identityToken);
292        }
293    }
294
295    public void notifyChange(Uri uri, IContentObserver observer,
296            boolean observerWantsSelfNotifications, boolean syncToNetwork) {
297        notifyChange(uri, observer, observerWantsSelfNotifications, syncToNetwork,
298                UserHandle.getCallingUserId());
299    }
300
301    /**
302     * Hide this class since it is not part of api,
303     * but current unittest framework requires it to be public
304     * @hide
305     *
306     */
307    public static final class ObserverCall {
308        final ObserverNode mNode;
309        final IContentObserver mObserver;
310        final boolean mSelfChange;
311
312        ObserverCall(ObserverNode node, IContentObserver observer, boolean selfChange) {
313            mNode = node;
314            mObserver = observer;
315            mSelfChange = selfChange;
316        }
317    }
318
319    public void requestSync(Account account, String authority, Bundle extras) {
320        ContentResolver.validateSyncExtrasBundle(extras);
321        int userId = UserHandle.getCallingUserId();
322        int uId = Binder.getCallingUid();
323
324        // This makes it so that future permission checks will be in the context of this
325        // process rather than the caller's process. We will restore this before returning.
326        long identityToken = clearCallingIdentity();
327        try {
328            SyncManager syncManager = getSyncManager();
329            if (syncManager != null) {
330                syncManager.scheduleSync(account, userId, uId, authority, extras,
331                        0 /* no delay */, 0 /* no delay */,
332                        false /* onlyThoseWithUnkownSyncableState */);
333            }
334        } finally {
335            restoreCallingIdentity(identityToken);
336        }
337    }
338
339    /**
340     * Request a sync with a generic {@link android.content.SyncRequest} object. This will be
341     * either:
342     *   periodic OR one-off sync.
343     * and
344     *   anonymous OR provider sync.
345     * Depending on the request, we enqueue to suit in the SyncManager.
346     * @param request The request object. Validation of this object is done by its builder.
347     */
348    public void sync(SyncRequest request) {
349        int userId = UserHandle.getCallingUserId();
350        int callerUid = Binder.getCallingUid();
351        // This makes it so that future permission checks will be in the context of this
352        // process rather than the caller's process. We will restore this before returning.
353        long identityToken = clearCallingIdentity();
354        try {
355            SyncManager syncManager = getSyncManager();
356            if (syncManager == null) {
357                return;
358            }
359
360            Bundle extras = request.getBundle();
361            long flextime = request.getSyncFlexTime();
362            long runAtTime = request.getSyncRunTime();
363            if (request.isPeriodic()) {
364                mContext.enforceCallingOrSelfPermission(
365                        Manifest.permission.WRITE_SYNC_SETTINGS,
366                        "no permission to write the sync settings");
367                SyncStorageEngine.EndPoint info;
368                if (!request.hasAuthority()) {
369                    // Extra permissions checking for sync service.
370                    verifySignatureForPackage(callerUid,
371                            request.getService().getPackageName(), "sync");
372                    info = new SyncStorageEngine.EndPoint(request.getService(), userId);
373                } else {
374                    info = new SyncStorageEngine.EndPoint(
375                            request.getAccount(), request.getProvider(), userId);
376                }
377                if (runAtTime < 60) {
378                    Slog.w(TAG, "Requested poll frequency of " + runAtTime
379                            + " seconds being rounded up to 60 seconds.");
380                    runAtTime = 60;
381                }
382                // Schedule periodic sync.
383                getSyncManager().getSyncStorageEngine()
384                    .updateOrAddPeriodicSync(info, runAtTime, flextime, extras);
385            } else {
386                long beforeRuntimeMillis = (flextime) * 1000;
387                long runtimeMillis = runAtTime * 1000;
388                if (request.hasAuthority()) {
389                syncManager.scheduleSync(
390                        request.getAccount(), userId, callerUid, request.getProvider(), extras,
391                        beforeRuntimeMillis, runtimeMillis,
392                        false /* onlyThoseWithUnknownSyncableState */);
393                } else {
394                    syncManager.scheduleSync(
395                            request.getService(), userId, callerUid, extras,
396                            beforeRuntimeMillis,
397                            runtimeMillis); // Empty function.
398                }
399            }
400        } finally {
401            restoreCallingIdentity(identityToken);
402        }
403    }
404
405    /**
406     * Clear all scheduled sync operations that match the uri and cancel the active sync
407     * if they match the authority and account, if they are present.
408     *
409     * @param account filter the pending and active syncs to cancel using this account, or null.
410     * @param authority filter the pending and active syncs to cancel using this authority, or
411     * null.
412     * @param cname cancel syncs running on this service, or null for provider/account.
413     */
414    @Override
415    public void cancelSync(Account account, String authority, ComponentName cname) {
416        if (authority != null && authority.length() == 0) {
417            throw new IllegalArgumentException("Authority must be non-empty");
418        }
419
420        int userId = UserHandle.getCallingUserId();
421        // This makes it so that future permission checks will be in the context of this
422        // process rather than the caller's process. We will restore this before returning.
423        long identityToken = clearCallingIdentity();
424        try {
425            SyncManager syncManager = getSyncManager();
426            if (syncManager != null) {
427                SyncStorageEngine.EndPoint info;
428                if (cname == null) {
429                    info = new SyncStorageEngine.EndPoint(account, authority, userId);
430                } else {
431                    info = new SyncStorageEngine.EndPoint(cname, userId);
432                }
433                syncManager.clearScheduledSyncOperations(info);
434                syncManager.cancelActiveSync(info, null /* all syncs for this adapter */);
435            }
436        } finally {
437            restoreCallingIdentity(identityToken);
438        }
439    }
440
441    public void cancelRequest(SyncRequest request) {
442        SyncManager syncManager = getSyncManager();
443        if (syncManager == null) return;
444        int userId = UserHandle.getCallingUserId();
445        int callerUid = Binder.getCallingUid();
446
447        long identityToken = clearCallingIdentity();
448        try {
449            SyncStorageEngine.EndPoint info;
450            Bundle extras = new Bundle(request.getBundle());
451            if (request.hasAuthority()) {
452                Account account = request.getAccount();
453                String provider = request.getProvider();
454                info = new SyncStorageEngine.EndPoint(account, provider, userId);
455            } else {
456                // Only allowed to manipulate syncs for a service which you own.
457                ComponentName service = request.getService();
458                verifySignatureForPackage(callerUid, service.getPackageName(), "cancel");
459                info = new SyncStorageEngine.EndPoint(service, userId);
460            }
461            if (request.isPeriodic()) {
462                // Remove periodic sync.
463                mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
464                        "no permission to write the sync settings");
465                getSyncManager().getSyncStorageEngine().removePeriodicSync(info, extras);
466            }
467            // Cancel active syncs and clear pending syncs from the queue.
468            syncManager.cancelScheduledSyncOperation(info, extras);
469            syncManager.cancelActiveSync(info, extras);
470        } finally {
471            restoreCallingIdentity(identityToken);
472        }
473    }
474
475    /**
476     * Get information about the SyncAdapters that are known to the system.
477     * @return an array of SyncAdapters that have registered with the system
478     */
479    @Override
480    public SyncAdapterType[] getSyncAdapterTypes() {
481        // This makes it so that future permission checks will be in the context of this
482        // process rather than the caller's process. We will restore this before returning.
483        final int userId = UserHandle.getCallingUserId();
484        final long identityToken = clearCallingIdentity();
485        try {
486            SyncManager syncManager = getSyncManager();
487            return syncManager.getSyncAdapterTypes(userId);
488        } finally {
489            restoreCallingIdentity(identityToken);
490        }
491    }
492
493    @Override
494    public boolean getSyncAutomatically(Account account, String providerName) {
495        mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
496                "no permission to read the sync settings");
497
498        int userId = UserHandle.getCallingUserId();
499        long identityToken = clearCallingIdentity();
500        try {
501            SyncManager syncManager = getSyncManager();
502            if (syncManager != null) {
503                return syncManager.getSyncStorageEngine()
504                        .getSyncAutomatically(account, userId, providerName);
505            }
506        } finally {
507            restoreCallingIdentity(identityToken);
508        }
509        return false;
510    }
511
512    @Override
513    public void setSyncAutomatically(Account account, String providerName, boolean sync) {
514        if (TextUtils.isEmpty(providerName)) {
515            throw new IllegalArgumentException("Authority must be non-empty");
516        }
517        mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
518                "no permission to write the sync settings");
519
520        int userId = UserHandle.getCallingUserId();
521        long identityToken = clearCallingIdentity();
522        try {
523            SyncManager syncManager = getSyncManager();
524            if (syncManager != null) {
525                syncManager.getSyncStorageEngine()
526                .setSyncAutomatically(account, userId, providerName, sync);
527            }
528        } finally {
529            restoreCallingIdentity(identityToken);
530        }
531    }
532
533    /** Old API. Schedule periodic sync with default flex time. */
534    @Override
535    public void addPeriodicSync(Account account, String authority, Bundle extras,
536            long pollFrequency) {
537        if (account == null) {
538            throw new IllegalArgumentException("Account must not be null");
539        }
540        if (TextUtils.isEmpty(authority)) {
541            throw new IllegalArgumentException("Authority must not be empty.");
542        }
543        mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
544                "no permission to write the sync settings");
545
546        int userId = UserHandle.getCallingUserId();
547        if (pollFrequency < 60) {
548            Slog.w(TAG, "Requested poll frequency of " + pollFrequency
549                    + " seconds being rounded up to 60 seconds.");
550            pollFrequency = 60;
551        }
552        long defaultFlex = SyncStorageEngine.calculateDefaultFlexTime(pollFrequency);
553
554        long identityToken = clearCallingIdentity();
555        try {
556            SyncStorageEngine.EndPoint info =
557                    new SyncStorageEngine.EndPoint(account, authority, userId);
558            getSyncManager().getSyncStorageEngine()
559                .updateOrAddPeriodicSync(info,
560                        pollFrequency,
561                        defaultFlex,
562                        extras);
563        } finally {
564            restoreCallingIdentity(identityToken);
565        }
566    }
567
568    public void removePeriodicSync(Account account, String authority, Bundle extras) {
569        if (account == null) {
570            throw new IllegalArgumentException("Account must not be null");
571        }
572        if (TextUtils.isEmpty(authority)) {
573            throw new IllegalArgumentException("Authority must not be empty");
574        }
575        mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
576                "no permission to write the sync settings");
577
578        int userId = UserHandle.getCallingUserId();
579        long identityToken = clearCallingIdentity();
580        try {
581            getSyncManager().getSyncStorageEngine()
582                .removePeriodicSync(
583                        new SyncStorageEngine.EndPoint(account, authority, userId),
584                        extras);
585        } finally {
586            restoreCallingIdentity(identityToken);
587        }
588    }
589
590
591    public List<PeriodicSync> getPeriodicSyncs(Account account, String providerName,
592            ComponentName cname) {
593        if (account == null) {
594            throw new IllegalArgumentException("Account must not be null");
595        }
596        if (TextUtils.isEmpty(providerName)) {
597            throw new IllegalArgumentException("Authority must not be empty");
598        }
599        mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
600                "no permission to read the sync settings");
601
602        int callerUid = Binder.getCallingUid();
603        int userId = UserHandle.getCallingUserId();
604        long identityToken = clearCallingIdentity();
605        try {
606            if (cname == null) {
607                return getSyncManager().getSyncStorageEngine().getPeriodicSyncs(
608                        new SyncStorageEngine.EndPoint(account, providerName, userId));
609            } else if (account == null && providerName == null) {
610                verifySignatureForPackage(callerUid, cname.getPackageName(), "getPeriodicSyncs");
611                return getSyncManager().getSyncStorageEngine().getPeriodicSyncs(
612                        new SyncStorageEngine.EndPoint(cname, userId));
613            } else {
614                throw new IllegalArgumentException("Invalid authority specified");
615            }
616        } finally {
617            restoreCallingIdentity(identityToken);
618        }
619    }
620
621    public int getIsSyncable(Account account, String providerName) {
622        mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
623                "no permission to read the sync settings");
624        int userId = UserHandle.getCallingUserId();
625
626        long identityToken = clearCallingIdentity();
627        try {
628            SyncManager syncManager = getSyncManager();
629            if (syncManager != null) {
630                return syncManager.getIsSyncable(
631                        account, userId, providerName);
632            }
633        } finally {
634            restoreCallingIdentity(identityToken);
635        }
636        return -1;
637    }
638
639    public void setIsSyncable(Account account, String providerName, int syncable) {
640        if (TextUtils.isEmpty(providerName)) {
641            throw new IllegalArgumentException("Authority must not be empty");
642        }
643        mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
644                "no permission to write the sync settings");
645
646        int userId = UserHandle.getCallingUserId();
647        long identityToken = clearCallingIdentity();
648        try {
649            SyncManager syncManager = getSyncManager();
650            if (syncManager != null) {
651                syncManager.getSyncStorageEngine().setIsSyncable(
652                        account, userId, providerName, syncable);
653            }
654        } finally {
655            restoreCallingIdentity(identityToken);
656        }
657    }
658
659    public void setServiceActive(ComponentName cname, boolean active) {
660        mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
661                "no permission to write the sync settings");
662        verifySignatureForPackage(Binder.getCallingUid(), cname.getPackageName(),
663                "setServiceActive");
664
665        int userId = UserHandle.getCallingUserId();
666        long identityToken = clearCallingIdentity();
667        try {
668            SyncManager syncManager = getSyncManager();
669            if (syncManager != null) {
670                syncManager.getSyncStorageEngine().setIsTargetServiceActive(
671                        cname, userId, active);
672            }
673        } finally {
674            restoreCallingIdentity(identityToken);
675        }
676    }
677
678    public boolean isServiceActive(ComponentName cname) {
679        mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
680                "no permission to read the sync settings");
681        verifySignatureForPackage(Binder.getCallingUid(), cname.getPackageName(),
682                "isServiceActive");
683
684        int userId = UserHandle.getCallingUserId();
685        long identityToken = clearCallingIdentity();
686        try {
687            SyncManager syncManager = getSyncManager();
688            if (syncManager != null) {
689                return syncManager.getSyncStorageEngine()
690                        .getIsTargetServiceActive(cname, userId);
691            }
692        } finally {
693            restoreCallingIdentity(identityToken);
694        }
695        return false;
696    }
697
698    @Override
699    public boolean getMasterSyncAutomatically() {
700        mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
701                "no permission to read the sync settings");
702
703        int userId = UserHandle.getCallingUserId();
704        long identityToken = clearCallingIdentity();
705        try {
706            SyncManager syncManager = getSyncManager();
707            if (syncManager != null) {
708                return syncManager.getSyncStorageEngine().getMasterSyncAutomatically(userId);
709            }
710        } finally {
711            restoreCallingIdentity(identityToken);
712        }
713        return false;
714    }
715
716    @Override
717    public void setMasterSyncAutomatically(boolean flag) {
718        mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
719                "no permission to write the sync settings");
720
721        int userId = UserHandle.getCallingUserId();
722        long identityToken = clearCallingIdentity();
723        try {
724            SyncManager syncManager = getSyncManager();
725            if (syncManager != null) {
726                syncManager.getSyncStorageEngine().setMasterSyncAutomatically(flag, userId);
727            }
728        } finally {
729            restoreCallingIdentity(identityToken);
730        }
731    }
732
733    public boolean isSyncActive(Account account, String authority, ComponentName cname) {
734        mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
735                "no permission to read the sync stats");
736        int userId = UserHandle.getCallingUserId();
737        int callingUid = Binder.getCallingUid();
738        long identityToken = clearCallingIdentity();
739        try {
740            SyncManager syncManager = getSyncManager();
741            if (syncManager == null) {
742                return false;
743            }
744            if (cname == null) {
745                return syncManager.getSyncStorageEngine().isSyncActive(
746                        new SyncStorageEngine.EndPoint(account, authority, userId));
747            } else if (account == null && authority == null) {
748                verifySignatureForPackage(callingUid, cname.getPackageName(), "isSyncActive");
749                return syncManager.getSyncStorageEngine().isSyncActive(
750                        new SyncStorageEngine.EndPoint(cname, userId));
751            }
752        } finally {
753            restoreCallingIdentity(identityToken);
754        }
755        return false;
756    }
757
758    public List<SyncInfo> getCurrentSyncs() {
759        mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
760                "no permission to read the sync stats");
761
762        int userId = UserHandle.getCallingUserId();
763        long identityToken = clearCallingIdentity();
764        try {
765            return getSyncManager().getSyncStorageEngine().getCurrentSyncsCopy(userId);
766        } finally {
767            restoreCallingIdentity(identityToken);
768        }
769    }
770
771    public SyncStatusInfo getSyncStatus(Account account, String authority, ComponentName cname) {
772        if (TextUtils.isEmpty(authority)) {
773            throw new IllegalArgumentException("Authority must not be empty");
774        }
775        mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
776                "no permission to read the sync stats");
777
778        int userId = UserHandle.getCallingUserId();
779        int callerUid = Binder.getCallingUid();
780        long identityToken = clearCallingIdentity();
781        try {
782            SyncManager syncManager = getSyncManager();
783            if (syncManager == null) {
784                return null;
785            }
786            SyncStorageEngine.EndPoint info;
787            if (cname == null) {
788                info = new SyncStorageEngine.EndPoint(account, authority, userId);
789            } else if (account == null && authority == null) {
790                verifySignatureForPackage(callerUid, cname.getPackageName(), "getSyncStatus");
791                info = new SyncStorageEngine.EndPoint(cname, userId);
792            } else {
793                throw new IllegalArgumentException("Must call sync status with valid authority");
794            }
795            return syncManager.getSyncStorageEngine().getStatusByAuthority(info);
796        } finally {
797            restoreCallingIdentity(identityToken);
798        }
799    }
800
801    public boolean isSyncPending(Account account, String authority, ComponentName cname) {
802        mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
803                "no permission to read the sync stats");
804
805        int callerUid = Binder.getCallingUid();
806        int userId = UserHandle.getCallingUserId();
807        long identityToken = clearCallingIdentity();
808        SyncManager syncManager = getSyncManager();
809        if (syncManager == null) return false;
810
811        try {
812            SyncStorageEngine.EndPoint info;
813            if (cname == null) {
814                info = new SyncStorageEngine.EndPoint(account, authority, userId);
815            } else if (account == null && authority == null) {
816                verifySignatureForPackage(callerUid, cname.getPackageName(), "isSyncPending");
817                info = new SyncStorageEngine.EndPoint(cname, userId);
818            } else {
819                throw new IllegalArgumentException("Invalid authority specified");
820            }
821            return syncManager.getSyncStorageEngine().isSyncPending(info);
822        } finally {
823            restoreCallingIdentity(identityToken);
824        }
825    }
826
827    public void addStatusChangeListener(int mask, ISyncStatusObserver callback) {
828        long identityToken = clearCallingIdentity();
829        try {
830            SyncManager syncManager = getSyncManager();
831            if (syncManager != null && callback != null) {
832                syncManager.getSyncStorageEngine().addStatusChangeListener(mask, callback);
833            }
834        } finally {
835            restoreCallingIdentity(identityToken);
836        }
837    }
838
839    public void removeStatusChangeListener(ISyncStatusObserver callback) {
840        long identityToken = clearCallingIdentity();
841        try {
842            SyncManager syncManager = getSyncManager();
843            if (syncManager != null && callback != null) {
844                syncManager.getSyncStorageEngine().removeStatusChangeListener(callback);
845            }
846        } finally {
847            restoreCallingIdentity(identityToken);
848        }
849    }
850
851    public static ContentService main(Context context, boolean factoryTest) {
852        ContentService service = new ContentService(context, factoryTest);
853        ServiceManager.addService(ContentResolver.CONTENT_SERVICE_NAME, service);
854        return service;
855    }
856
857    /**
858     * Helper to verify that the provided package name shares the same cert as the caller.
859     * @param callerUid uid of the calling process.
860     * @param packageName package to verify against package of calling application.
861     * @param tag a tag to use when throwing an exception if the signatures don't
862     * match. Cannot be null.
863     * @return true if the calling application and the provided package are signed with the same
864     * certificate.
865     */
866    private boolean verifySignatureForPackage(int callerUid, String packageName, String tag) {
867        PackageManager pm = mContext.getPackageManager();
868        try {
869            int serviceUid = pm.getApplicationInfo(packageName, 0).uid;
870            if (pm.checkSignatures(callerUid, serviceUid) == PackageManager.SIGNATURE_MATCH) {
871                return true;
872            } else {
873                throw new SecurityException(tag + ": Caller certificate does not match that for - "
874                        + packageName);
875            }
876        } catch (PackageManager.NameNotFoundException e) {
877            throw new IllegalArgumentException(tag + ": " + packageName + " package not found.");
878        }
879    }
880
881    /**
882     * Hide this class since it is not part of api,
883     * but current unittest framework requires it to be public
884     * @hide
885     */
886    public static final class ObserverNode {
887        private class ObserverEntry implements IBinder.DeathRecipient {
888            public final IContentObserver observer;
889            public final int uid;
890            public final int pid;
891            public final boolean notifyForDescendants;
892            private final int userHandle;
893            private final Object observersLock;
894
895            public ObserverEntry(IContentObserver o, boolean n, Object observersLock,
896                    int _uid, int _pid, int _userHandle) {
897                this.observersLock = observersLock;
898                observer = o;
899                uid = _uid;
900                pid = _pid;
901                userHandle = _userHandle;
902                notifyForDescendants = n;
903                try {
904                    observer.asBinder().linkToDeath(this, 0);
905                } catch (RemoteException e) {
906                    binderDied();
907                }
908            }
909
910            public void binderDied() {
911                synchronized (observersLock) {
912                    removeObserverLocked(observer);
913                }
914            }
915
916            public void dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args,
917                    String name, String prefix, SparseIntArray pidCounts) {
918                pidCounts.put(pid, pidCounts.get(pid)+1);
919                pw.print(prefix); pw.print(name); pw.print(": pid=");
920                        pw.print(pid); pw.print(" uid=");
921                        pw.print(uid); pw.print(" user=");
922                        pw.print(userHandle); pw.print(" target=");
923                        pw.println(Integer.toHexString(System.identityHashCode(
924                                observer != null ? observer.asBinder() : null)));
925            }
926        }
927
928        public static final int INSERT_TYPE = 0;
929        public static final int UPDATE_TYPE = 1;
930        public static final int DELETE_TYPE = 2;
931
932        private String mName;
933        private ArrayList<ObserverNode> mChildren = new ArrayList<ObserverNode>();
934        private ArrayList<ObserverEntry> mObservers = new ArrayList<ObserverEntry>();
935
936        public ObserverNode(String name) {
937            mName = name;
938        }
939
940        public void dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args,
941                String name, String prefix, int[] counts, SparseIntArray pidCounts) {
942            String innerName = null;
943            if (mObservers.size() > 0) {
944                if ("".equals(name)) {
945                    innerName = mName;
946                } else {
947                    innerName = name + "/" + mName;
948                }
949                for (int i=0; i<mObservers.size(); i++) {
950                    counts[1]++;
951                    mObservers.get(i).dumpLocked(fd, pw, args, innerName, prefix,
952                            pidCounts);
953                }
954            }
955            if (mChildren.size() > 0) {
956                if (innerName == null) {
957                    if ("".equals(name)) {
958                        innerName = mName;
959                    } else {
960                        innerName = name + "/" + mName;
961                    }
962                }
963                for (int i=0; i<mChildren.size(); i++) {
964                    counts[0]++;
965                    mChildren.get(i).dumpLocked(fd, pw, args, innerName, prefix,
966                            counts, pidCounts);
967                }
968            }
969        }
970
971        private String getUriSegment(Uri uri, int index) {
972            if (uri != null) {
973                if (index == 0) {
974                    return uri.getAuthority();
975                } else {
976                    return uri.getPathSegments().get(index - 1);
977                }
978            } else {
979                return null;
980            }
981        }
982
983        private int countUriSegments(Uri uri) {
984            if (uri == null) {
985                return 0;
986            }
987            return uri.getPathSegments().size() + 1;
988        }
989
990        // Invariant:  userHandle is either a hard user number or is USER_ALL
991        public void addObserverLocked(Uri uri, IContentObserver observer,
992                boolean notifyForDescendants, Object observersLock,
993                int uid, int pid, int userHandle) {
994            addObserverLocked(uri, 0, observer, notifyForDescendants, observersLock,
995                    uid, pid, userHandle);
996        }
997
998        private void addObserverLocked(Uri uri, int index, IContentObserver observer,
999                boolean notifyForDescendants, Object observersLock,
1000                int uid, int pid, int userHandle) {
1001            // If this is the leaf node add the observer
1002            if (index == countUriSegments(uri)) {
1003                mObservers.add(new ObserverEntry(observer, notifyForDescendants, observersLock,
1004                        uid, pid, userHandle));
1005                return;
1006            }
1007
1008            // Look to see if the proper child already exists
1009            String segment = getUriSegment(uri, index);
1010            if (segment == null) {
1011                throw new IllegalArgumentException("Invalid Uri (" + uri + ") used for observer");
1012            }
1013            int N = mChildren.size();
1014            for (int i = 0; i < N; i++) {
1015                ObserverNode node = mChildren.get(i);
1016                if (node.mName.equals(segment)) {
1017                    node.addObserverLocked(uri, index + 1, observer, notifyForDescendants,
1018                            observersLock, uid, pid, userHandle);
1019                    return;
1020                }
1021            }
1022
1023            // No child found, create one
1024            ObserverNode node = new ObserverNode(segment);
1025            mChildren.add(node);
1026            node.addObserverLocked(uri, index + 1, observer, notifyForDescendants,
1027                    observersLock, uid, pid, userHandle);
1028        }
1029
1030        public boolean removeObserverLocked(IContentObserver observer) {
1031            int size = mChildren.size();
1032            for (int i = 0; i < size; i++) {
1033                boolean empty = mChildren.get(i).removeObserverLocked(observer);
1034                if (empty) {
1035                    mChildren.remove(i);
1036                    i--;
1037                    size--;
1038                }
1039            }
1040
1041            IBinder observerBinder = observer.asBinder();
1042            size = mObservers.size();
1043            for (int i = 0; i < size; i++) {
1044                ObserverEntry entry = mObservers.get(i);
1045                if (entry.observer.asBinder() == observerBinder) {
1046                    mObservers.remove(i);
1047                    // We no longer need to listen for death notifications. Remove it.
1048                    observerBinder.unlinkToDeath(entry, 0);
1049                    break;
1050                }
1051            }
1052
1053            if (mChildren.size() == 0 && mObservers.size() == 0) {
1054                return true;
1055            }
1056            return false;
1057        }
1058
1059        private void collectMyObserversLocked(boolean leaf, IContentObserver observer,
1060                boolean observerWantsSelfNotifications, int targetUserHandle,
1061                ArrayList<ObserverCall> calls) {
1062            int N = mObservers.size();
1063            IBinder observerBinder = observer == null ? null : observer.asBinder();
1064            for (int i = 0; i < N; i++) {
1065                ObserverEntry entry = mObservers.get(i);
1066
1067                // Don't notify the observer if it sent the notification and isn't interested
1068                // in self notifications
1069                boolean selfChange = (entry.observer.asBinder() == observerBinder);
1070                if (selfChange && !observerWantsSelfNotifications) {
1071                    continue;
1072                }
1073
1074                // Does this observer match the target user?
1075                if (targetUserHandle == UserHandle.USER_ALL
1076                        || entry.userHandle == UserHandle.USER_ALL
1077                        || targetUserHandle == entry.userHandle) {
1078                    // Make sure the observer is interested in the notification
1079                    if (leaf || (!leaf && entry.notifyForDescendants)) {
1080                        calls.add(new ObserverCall(this, entry.observer, selfChange));
1081                    }
1082                }
1083            }
1084        }
1085
1086        /**
1087         * targetUserHandle is either a hard user handle or is USER_ALL
1088         */
1089        public void collectObserversLocked(Uri uri, int index, IContentObserver observer,
1090                boolean observerWantsSelfNotifications, int targetUserHandle,
1091                ArrayList<ObserverCall> calls) {
1092            String segment = null;
1093            int segmentCount = countUriSegments(uri);
1094            if (index >= segmentCount) {
1095                // This is the leaf node, notify all observers
1096                collectMyObserversLocked(true, observer, observerWantsSelfNotifications,
1097                        targetUserHandle, calls);
1098            } else if (index < segmentCount){
1099                segment = getUriSegment(uri, index);
1100                // Notify any observers at this level who are interested in descendants
1101                collectMyObserversLocked(false, observer, observerWantsSelfNotifications,
1102                        targetUserHandle, calls);
1103            }
1104
1105            int N = mChildren.size();
1106            for (int i = 0; i < N; i++) {
1107                ObserverNode node = mChildren.get(i);
1108                if (segment == null || node.mName.equals(segment)) {
1109                    // We found the child,
1110                    node.collectObserversLocked(uri, index + 1,
1111                            observer, observerWantsSelfNotifications, targetUserHandle, calls);
1112                    if (segment != null) {
1113                        break;
1114                    }
1115                }
1116            }
1117        }
1118    }
1119}
1120