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