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