ContentService.java revision cb22807ffcf79db1b62162842d0a90251a463ea7
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);
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        mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
723                "no permission to write the sync settings");
724
725        int userId = UserHandle.getCallingUserId();
726        long identityToken = clearCallingIdentity();
727        try {
728            SyncManager syncManager = getSyncManager();
729            if (syncManager != null) {
730                syncManager.getSyncStorageEngine().setMasterSyncAutomatically(flag, userId);
731            }
732        } finally {
733            restoreCallingIdentity(identityToken);
734        }
735    }
736
737    public boolean isSyncActive(Account account, String authority, ComponentName cname) {
738        mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
739                "no permission to read the sync stats");
740        int userId = UserHandle.getCallingUserId();
741        int callingUid = Binder.getCallingUid();
742        long identityToken = clearCallingIdentity();
743        try {
744            SyncManager syncManager = getSyncManager();
745            if (syncManager == null) {
746                return false;
747            }
748            return syncManager.getSyncStorageEngine().isSyncActive(
749                    new SyncStorageEngine.EndPoint(account, authority, userId));
750        } finally {
751            restoreCallingIdentity(identityToken);
752        }
753    }
754
755    public List<SyncInfo> getCurrentSyncs() {
756        return getCurrentSyncsAsUser(UserHandle.getCallingUserId());
757    }
758
759    /**
760     * If the user id supplied is different to the calling user, the caller must hold the
761     * INTERACT_ACROSS_USERS_FULL permission.
762     */
763    public List<SyncInfo> getCurrentSyncsAsUser(int userId) {
764        enforceCrossUserPermission(userId,
765                "no permission to read the sync settings for user " + userId);
766        mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
767                "no permission to read the sync stats");
768
769        long identityToken = clearCallingIdentity();
770        try {
771            return getSyncManager().getSyncStorageEngine().getCurrentSyncsCopy(userId);
772        } finally {
773            restoreCallingIdentity(identityToken);
774        }
775    }
776
777    public SyncStatusInfo getSyncStatus(Account account, String authority, ComponentName cname) {
778        return getSyncStatusAsUser(account, authority, cname, UserHandle.getCallingUserId());
779    }
780
781    /**
782     * If the user id supplied is different to the calling user, the caller must hold the
783     * INTERACT_ACROSS_USERS_FULL permission.
784     */
785    public SyncStatusInfo getSyncStatusAsUser(Account account, String authority,
786            ComponentName cname, int userId) {
787        if (TextUtils.isEmpty(authority)) {
788            throw new IllegalArgumentException("Authority must not be empty");
789        }
790
791        enforceCrossUserPermission(userId,
792                "no permission to read the sync stats for user " + userId);
793        mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
794                "no permission to read the sync stats");
795
796        int callerUid = Binder.getCallingUid();
797        long identityToken = clearCallingIdentity();
798        try {
799            SyncManager syncManager = getSyncManager();
800            if (syncManager == null) {
801                return null;
802            }
803            SyncStorageEngine.EndPoint info;
804            if (!(account == null || authority == null)) {
805                info = new SyncStorageEngine.EndPoint(account, authority, userId);
806            } else {
807                throw new IllegalArgumentException("Must call sync status with valid authority");
808            }
809            return syncManager.getSyncStorageEngine().getStatusByAuthority(info);
810        } finally {
811            restoreCallingIdentity(identityToken);
812        }
813    }
814
815    public boolean isSyncPending(Account account, String authority, ComponentName cname) {
816        return isSyncPendingAsUser(account, authority, cname, UserHandle.getCallingUserId());
817    }
818
819    @Override
820    public boolean isSyncPendingAsUser(Account account, String authority, ComponentName cname,
821            int userId) {
822        mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
823                "no permission to read the sync stats");
824        enforceCrossUserPermission(userId,
825                "no permission to retrieve the sync settings for user " + userId);
826        int callerUid = Binder.getCallingUid();
827        long identityToken = clearCallingIdentity();
828        SyncManager syncManager = getSyncManager();
829        if (syncManager == null) return false;
830
831        try {
832            SyncStorageEngine.EndPoint info;
833            if (!(account == null || authority == null)) {
834                info = new SyncStorageEngine.EndPoint(account, authority, userId);
835            } else {
836                throw new IllegalArgumentException("Invalid authority specified");
837            }
838            return syncManager.getSyncStorageEngine().isSyncPending(info);
839        } finally {
840            restoreCallingIdentity(identityToken);
841        }
842    }
843
844    public void addStatusChangeListener(int mask, ISyncStatusObserver callback) {
845        long identityToken = clearCallingIdentity();
846        try {
847            SyncManager syncManager = getSyncManager();
848            if (syncManager != null && callback != null) {
849                syncManager.getSyncStorageEngine().addStatusChangeListener(mask, callback);
850            }
851        } finally {
852            restoreCallingIdentity(identityToken);
853        }
854    }
855
856    public void removeStatusChangeListener(ISyncStatusObserver callback) {
857        long identityToken = clearCallingIdentity();
858        try {
859            SyncManager syncManager = getSyncManager();
860            if (syncManager != null && callback != null) {
861                syncManager.getSyncStorageEngine().removeStatusChangeListener(callback);
862            }
863        } finally {
864            restoreCallingIdentity(identityToken);
865        }
866    }
867
868    public static ContentService main(Context context, boolean factoryTest) {
869        ContentService service = new ContentService(context, factoryTest);
870        ServiceManager.addService(ContentResolver.CONTENT_SERVICE_NAME, service);
871        return service;
872    }
873
874    /**
875     * Checks if the request is from the system or an app that has INTERACT_ACROSS_USERS_FULL
876     * permission, if the userHandle is not for the caller.
877     *
878     * @param userHandle the user handle of the user we want to act on behalf of.
879     * @param message the message to log on security exception.
880     */
881    private void enforceCrossUserPermission(int userHandle, String message) {
882        final int callingUser = UserHandle.getCallingUserId();
883        if (callingUser != userHandle) {
884            mContext.enforceCallingOrSelfPermission(
885                    Manifest.permission.INTERACT_ACROSS_USERS_FULL, message);
886        }
887    }
888
889    /**
890     * Hide this class since it is not part of api,
891     * but current unittest framework requires it to be public
892     * @hide
893     */
894    public static final class ObserverNode {
895        private class ObserverEntry implements IBinder.DeathRecipient {
896            public final IContentObserver observer;
897            public final int uid;
898            public final int pid;
899            public final boolean notifyForDescendants;
900            private final int userHandle;
901            private final Object observersLock;
902
903            public ObserverEntry(IContentObserver o, boolean n, Object observersLock,
904                    int _uid, int _pid, int _userHandle) {
905                this.observersLock = observersLock;
906                observer = o;
907                uid = _uid;
908                pid = _pid;
909                userHandle = _userHandle;
910                notifyForDescendants = n;
911                try {
912                    observer.asBinder().linkToDeath(this, 0);
913                } catch (RemoteException e) {
914                    binderDied();
915                }
916            }
917
918            public void binderDied() {
919                synchronized (observersLock) {
920                    removeObserverLocked(observer);
921                }
922            }
923
924            public void dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args,
925                    String name, String prefix, SparseIntArray pidCounts) {
926                pidCounts.put(pid, pidCounts.get(pid)+1);
927                pw.print(prefix); pw.print(name); pw.print(": pid=");
928                        pw.print(pid); pw.print(" uid=");
929                        pw.print(uid); pw.print(" user=");
930                        pw.print(userHandle); pw.print(" target=");
931                        pw.println(Integer.toHexString(System.identityHashCode(
932                                observer != null ? observer.asBinder() : null)));
933            }
934        }
935
936        public static final int INSERT_TYPE = 0;
937        public static final int UPDATE_TYPE = 1;
938        public static final int DELETE_TYPE = 2;
939
940        private String mName;
941        private ArrayList<ObserverNode> mChildren = new ArrayList<ObserverNode>();
942        private ArrayList<ObserverEntry> mObservers = new ArrayList<ObserverEntry>();
943
944        public ObserverNode(String name) {
945            mName = name;
946        }
947
948        public void dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args,
949                String name, String prefix, int[] counts, SparseIntArray pidCounts) {
950            String innerName = null;
951            if (mObservers.size() > 0) {
952                if ("".equals(name)) {
953                    innerName = mName;
954                } else {
955                    innerName = name + "/" + mName;
956                }
957                for (int i=0; i<mObservers.size(); i++) {
958                    counts[1]++;
959                    mObservers.get(i).dumpLocked(fd, pw, args, innerName, prefix,
960                            pidCounts);
961                }
962            }
963            if (mChildren.size() > 0) {
964                if (innerName == null) {
965                    if ("".equals(name)) {
966                        innerName = mName;
967                    } else {
968                        innerName = name + "/" + mName;
969                    }
970                }
971                for (int i=0; i<mChildren.size(); i++) {
972                    counts[0]++;
973                    mChildren.get(i).dumpLocked(fd, pw, args, innerName, prefix,
974                            counts, pidCounts);
975                }
976            }
977        }
978
979        private String getUriSegment(Uri uri, int index) {
980            if (uri != null) {
981                if (index == 0) {
982                    return uri.getAuthority();
983                } else {
984                    return uri.getPathSegments().get(index - 1);
985                }
986            } else {
987                return null;
988            }
989        }
990
991        private int countUriSegments(Uri uri) {
992            if (uri == null) {
993                return 0;
994            }
995            return uri.getPathSegments().size() + 1;
996        }
997
998        // Invariant:  userHandle is either a hard user number or is USER_ALL
999        public void addObserverLocked(Uri uri, IContentObserver observer,
1000                boolean notifyForDescendants, Object observersLock,
1001                int uid, int pid, int userHandle) {
1002            addObserverLocked(uri, 0, observer, notifyForDescendants, observersLock,
1003                    uid, pid, userHandle);
1004        }
1005
1006        private void addObserverLocked(Uri uri, int index, IContentObserver observer,
1007                boolean notifyForDescendants, Object observersLock,
1008                int uid, int pid, int userHandle) {
1009            // If this is the leaf node add the observer
1010            if (index == countUriSegments(uri)) {
1011                mObservers.add(new ObserverEntry(observer, notifyForDescendants, observersLock,
1012                        uid, pid, userHandle));
1013                return;
1014            }
1015
1016            // Look to see if the proper child already exists
1017            String segment = getUriSegment(uri, index);
1018            if (segment == null) {
1019                throw new IllegalArgumentException("Invalid Uri (" + uri + ") used for observer");
1020            }
1021            int N = mChildren.size();
1022            for (int i = 0; i < N; i++) {
1023                ObserverNode node = mChildren.get(i);
1024                if (node.mName.equals(segment)) {
1025                    node.addObserverLocked(uri, index + 1, observer, notifyForDescendants,
1026                            observersLock, uid, pid, userHandle);
1027                    return;
1028                }
1029            }
1030
1031            // No child found, create one
1032            ObserverNode node = new ObserverNode(segment);
1033            mChildren.add(node);
1034            node.addObserverLocked(uri, index + 1, observer, notifyForDescendants,
1035                    observersLock, uid, pid, userHandle);
1036        }
1037
1038        public boolean removeObserverLocked(IContentObserver observer) {
1039            int size = mChildren.size();
1040            for (int i = 0; i < size; i++) {
1041                boolean empty = mChildren.get(i).removeObserverLocked(observer);
1042                if (empty) {
1043                    mChildren.remove(i);
1044                    i--;
1045                    size--;
1046                }
1047            }
1048
1049            IBinder observerBinder = observer.asBinder();
1050            size = mObservers.size();
1051            for (int i = 0; i < size; i++) {
1052                ObserverEntry entry = mObservers.get(i);
1053                if (entry.observer.asBinder() == observerBinder) {
1054                    mObservers.remove(i);
1055                    // We no longer need to listen for death notifications. Remove it.
1056                    observerBinder.unlinkToDeath(entry, 0);
1057                    break;
1058                }
1059            }
1060
1061            if (mChildren.size() == 0 && mObservers.size() == 0) {
1062                return true;
1063            }
1064            return false;
1065        }
1066
1067        private void collectMyObserversLocked(boolean leaf, IContentObserver observer,
1068                boolean observerWantsSelfNotifications, int targetUserHandle,
1069                ArrayList<ObserverCall> calls) {
1070            int N = mObservers.size();
1071            IBinder observerBinder = observer == null ? null : observer.asBinder();
1072            for (int i = 0; i < N; i++) {
1073                ObserverEntry entry = mObservers.get(i);
1074
1075                // Don't notify the observer if it sent the notification and isn't interested
1076                // in self notifications
1077                boolean selfChange = (entry.observer.asBinder() == observerBinder);
1078                if (selfChange && !observerWantsSelfNotifications) {
1079                    continue;
1080                }
1081
1082                // Does this observer match the target user?
1083                if (targetUserHandle == UserHandle.USER_ALL
1084                        || entry.userHandle == UserHandle.USER_ALL
1085                        || targetUserHandle == entry.userHandle) {
1086                    // Make sure the observer is interested in the notification
1087                    if (leaf || (!leaf && entry.notifyForDescendants)) {
1088                        calls.add(new ObserverCall(this, entry.observer, selfChange));
1089                    }
1090                }
1091            }
1092        }
1093
1094        /**
1095         * targetUserHandle is either a hard user handle or is USER_ALL
1096         */
1097        public void collectObserversLocked(Uri uri, int index, IContentObserver observer,
1098                boolean observerWantsSelfNotifications, int targetUserHandle,
1099                ArrayList<ObserverCall> calls) {
1100            String segment = null;
1101            int segmentCount = countUriSegments(uri);
1102            if (index >= segmentCount) {
1103                // This is the leaf node, notify all observers
1104                collectMyObserversLocked(true, observer, observerWantsSelfNotifications,
1105                        targetUserHandle, calls);
1106            } else if (index < segmentCount){
1107                segment = getUriSegment(uri, index);
1108                // Notify any observers at this level who are interested in descendants
1109                collectMyObserversLocked(false, observer, observerWantsSelfNotifications,
1110                        targetUserHandle, calls);
1111            }
1112
1113            int N = mChildren.size();
1114            for (int i = 0; i < N; i++) {
1115                ObserverNode node = mChildren.get(i);
1116                if (segment == null || node.mName.equals(segment)) {
1117                    // We found the child,
1118                    node.collectObserversLocked(uri, index + 1,
1119                            observer, observerWantsSelfNotifications, targetUserHandle, calls);
1120                    if (segment != null) {
1121                        break;
1122                    }
1123                }
1124            }
1125        }
1126    }
1127}
1128