ContentService.java revision 0363c3eb089afd4474bfd6ae6ee8a500d6e97614
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        if (TextUtils.isEmpty(providerName)) {
541            throw new IllegalArgumentException("Authority must be non-empty");
542        }
543        mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
544                "no permission to write the sync settings");
545
546        int userId = UserHandle.getCallingUserId();
547        long identityToken = clearCallingIdentity();
548        try {
549            SyncManager syncManager = getSyncManager();
550            if (syncManager != null) {
551                syncManager.getSyncStorageEngine()
552                .setSyncAutomatically(account, userId, providerName, sync);
553            }
554        } finally {
555            restoreCallingIdentity(identityToken);
556        }
557    }
558
559    /** Old API. Schedule periodic sync with default flex time. */
560    @Override
561    public void addPeriodicSync(Account account, String authority, Bundle extras,
562            long pollFrequency) {
563        if (account == null) {
564            throw new IllegalArgumentException("Account must not be null");
565        }
566        if (TextUtils.isEmpty(authority)) {
567            throw new IllegalArgumentException("Authority must not be empty.");
568        }
569        mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
570                "no permission to write the sync settings");
571
572        int userId = UserHandle.getCallingUserId();
573        if (pollFrequency < 60) {
574            Slog.w(TAG, "Requested poll frequency of " + pollFrequency
575                    + " seconds being rounded up to 60 seconds.");
576            pollFrequency = 60;
577        }
578        long defaultFlex = SyncStorageEngine.calculateDefaultFlexTime(pollFrequency);
579
580        long identityToken = clearCallingIdentity();
581        try {
582            SyncStorageEngine.EndPoint info =
583                    new SyncStorageEngine.EndPoint(account, authority, userId);
584            getSyncManager().getSyncStorageEngine()
585                .updateOrAddPeriodicSync(info,
586                        pollFrequency,
587                        defaultFlex,
588                        extras);
589        } finally {
590            restoreCallingIdentity(identityToken);
591        }
592    }
593
594    public void removePeriodicSync(Account account, String authority, Bundle extras) {
595        if (account == null) {
596            throw new IllegalArgumentException("Account must not be null");
597        }
598        if (TextUtils.isEmpty(authority)) {
599            throw new IllegalArgumentException("Authority must not be empty");
600        }
601        mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
602                "no permission to write the sync settings");
603
604        int userId = UserHandle.getCallingUserId();
605        long identityToken = clearCallingIdentity();
606        try {
607            getSyncManager().getSyncStorageEngine()
608                .removePeriodicSync(
609                        new SyncStorageEngine.EndPoint(account, authority, userId),
610                        extras);
611        } finally {
612            restoreCallingIdentity(identityToken);
613        }
614    }
615
616
617    public List<PeriodicSync> getPeriodicSyncs(Account account, String providerName,
618            ComponentName cname) {
619        if (account == null) {
620            throw new IllegalArgumentException("Account must not be null");
621        }
622        if (TextUtils.isEmpty(providerName)) {
623            throw new IllegalArgumentException("Authority must not be empty");
624        }
625        mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
626                "no permission to read the sync settings");
627
628        int userId = UserHandle.getCallingUserId();
629        long identityToken = clearCallingIdentity();
630        try {
631            return getSyncManager().getSyncStorageEngine().getPeriodicSyncs(
632                    new SyncStorageEngine.EndPoint(account, providerName, userId));
633        } finally {
634            restoreCallingIdentity(identityToken);
635        }
636    }
637
638    public int getIsSyncable(Account account, String providerName) {
639        return getIsSyncableAsUser(account, providerName, UserHandle.getCallingUserId());
640    }
641
642    /**
643     * If the user id supplied is different to the calling user, the caller must hold the
644     * INTERACT_ACROSS_USERS_FULL permission.
645     */
646    public int getIsSyncableAsUser(Account account, String providerName, int userId) {
647        enforceCrossUserPermission(userId,
648                "no permission to read the sync settings for user " + userId);
649        mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
650                "no permission to read the sync settings");
651
652        long identityToken = clearCallingIdentity();
653        try {
654            SyncManager syncManager = getSyncManager();
655            if (syncManager != null) {
656                return syncManager.getIsSyncable(
657                        account, userId, providerName);
658            }
659        } finally {
660            restoreCallingIdentity(identityToken);
661        }
662        return -1;
663    }
664
665    public void setIsSyncable(Account account, String providerName, int syncable) {
666        if (TextUtils.isEmpty(providerName)) {
667            throw new IllegalArgumentException("Authority must not be empty");
668        }
669        mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
670                "no permission to write the sync settings");
671
672        int userId = UserHandle.getCallingUserId();
673        long identityToken = clearCallingIdentity();
674        try {
675            SyncManager syncManager = getSyncManager();
676            if (syncManager != null) {
677                syncManager.getSyncStorageEngine().setIsSyncable(
678                        account, userId, providerName, syncable);
679            }
680        } finally {
681            restoreCallingIdentity(identityToken);
682        }
683    }
684
685    @Override
686    public boolean getMasterSyncAutomatically() {
687        return getMasterSyncAutomaticallyAsUser(UserHandle.getCallingUserId());
688    }
689
690    /**
691     * If the user id supplied is different to the calling user, the caller must hold the
692     * INTERACT_ACROSS_USERS_FULL permission.
693     */
694    @Override
695    public boolean getMasterSyncAutomaticallyAsUser(int userId) {
696        enforceCrossUserPermission(userId,
697                "no permission to read the sync settings for user " + userId);
698        mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
699                "no permission to read the sync settings");
700
701        long identityToken = clearCallingIdentity();
702        try {
703            SyncManager syncManager = getSyncManager();
704            if (syncManager != null) {
705                return syncManager.getSyncStorageEngine().getMasterSyncAutomatically(userId);
706            }
707        } finally {
708            restoreCallingIdentity(identityToken);
709        }
710        return false;
711    }
712
713    @Override
714    public void setMasterSyncAutomatically(boolean flag) {
715        mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
716                "no permission to write the sync settings");
717
718        int userId = UserHandle.getCallingUserId();
719        long identityToken = clearCallingIdentity();
720        try {
721            SyncManager syncManager = getSyncManager();
722            if (syncManager != null) {
723                syncManager.getSyncStorageEngine().setMasterSyncAutomatically(flag, userId);
724            }
725        } finally {
726            restoreCallingIdentity(identityToken);
727        }
728    }
729
730    public boolean isSyncActive(Account account, String authority, ComponentName cname) {
731        mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
732                "no permission to read the sync stats");
733        int userId = UserHandle.getCallingUserId();
734        int callingUid = Binder.getCallingUid();
735        long identityToken = clearCallingIdentity();
736        try {
737            SyncManager syncManager = getSyncManager();
738            if (syncManager == null) {
739                return false;
740            }
741            return syncManager.getSyncStorageEngine().isSyncActive(
742                    new SyncStorageEngine.EndPoint(account, authority, userId));
743        } finally {
744            restoreCallingIdentity(identityToken);
745        }
746    }
747
748    public List<SyncInfo> getCurrentSyncs() {
749        return getCurrentSyncsAsUser(UserHandle.getCallingUserId());
750    }
751
752    /**
753     * If the user id supplied is different to the calling user, the caller must hold the
754     * INTERACT_ACROSS_USERS_FULL permission.
755     */
756    public List<SyncInfo> getCurrentSyncsAsUser(int userId) {
757        enforceCrossUserPermission(userId,
758                "no permission to read the sync settings for user " + userId);
759        mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
760                "no permission to read the sync stats");
761
762        long identityToken = clearCallingIdentity();
763        try {
764            return getSyncManager().getSyncStorageEngine().getCurrentSyncsCopy(userId);
765        } finally {
766            restoreCallingIdentity(identityToken);
767        }
768    }
769
770    public SyncStatusInfo getSyncStatus(Account account, String authority, ComponentName cname) {
771        return getSyncStatusAsUser(account, authority, cname, UserHandle.getCallingUserId());
772    }
773
774    /**
775     * If the user id supplied is different to the calling user, the caller must hold the
776     * INTERACT_ACROSS_USERS_FULL permission.
777     */
778    public SyncStatusInfo getSyncStatusAsUser(Account account, String authority,
779            ComponentName cname, int userId) {
780        if (TextUtils.isEmpty(authority)) {
781            throw new IllegalArgumentException("Authority must not be empty");
782        }
783
784        enforceCrossUserPermission(userId,
785                "no permission to read the sync stats for user " + userId);
786        mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
787                "no permission to read the sync stats");
788
789        int callerUid = Binder.getCallingUid();
790        long identityToken = clearCallingIdentity();
791        try {
792            SyncManager syncManager = getSyncManager();
793            if (syncManager == null) {
794                return null;
795            }
796            SyncStorageEngine.EndPoint info;
797            if (!(account == null || authority == null)) {
798                info = new SyncStorageEngine.EndPoint(account, authority, userId);
799            } else {
800                throw new IllegalArgumentException("Must call sync status with valid authority");
801            }
802            return syncManager.getSyncStorageEngine().getStatusByAuthority(info);
803        } finally {
804            restoreCallingIdentity(identityToken);
805        }
806    }
807
808    public boolean isSyncPending(Account account, String authority, ComponentName cname) {
809        mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
810                "no permission to read the sync stats");
811
812        int callerUid = Binder.getCallingUid();
813        int userId = UserHandle.getCallingUserId();
814        long identityToken = clearCallingIdentity();
815        SyncManager syncManager = getSyncManager();
816        if (syncManager == null) return false;
817
818        try {
819            SyncStorageEngine.EndPoint info;
820            if (!(account == null || authority == null)) {
821                info = new SyncStorageEngine.EndPoint(account, authority, userId);
822            } else {
823                throw new IllegalArgumentException("Invalid authority specified");
824            }
825            return syncManager.getSyncStorageEngine().isSyncPending(info);
826        } finally {
827            restoreCallingIdentity(identityToken);
828        }
829    }
830
831    public void addStatusChangeListener(int mask, ISyncStatusObserver callback) {
832        long identityToken = clearCallingIdentity();
833        try {
834            SyncManager syncManager = getSyncManager();
835            if (syncManager != null && callback != null) {
836                syncManager.getSyncStorageEngine().addStatusChangeListener(mask, callback);
837            }
838        } finally {
839            restoreCallingIdentity(identityToken);
840        }
841    }
842
843    public void removeStatusChangeListener(ISyncStatusObserver callback) {
844        long identityToken = clearCallingIdentity();
845        try {
846            SyncManager syncManager = getSyncManager();
847            if (syncManager != null && callback != null) {
848                syncManager.getSyncStorageEngine().removeStatusChangeListener(callback);
849            }
850        } finally {
851            restoreCallingIdentity(identityToken);
852        }
853    }
854
855    public static ContentService main(Context context, boolean factoryTest) {
856        ContentService service = new ContentService(context, factoryTest);
857        ServiceManager.addService(ContentResolver.CONTENT_SERVICE_NAME, service);
858        return service;
859    }
860
861    /**
862     * Checks if the request is from the system or an app that has INTERACT_ACROSS_USERS_FULL
863     * permission, if the userHandle is not for the caller.
864     *
865     * @param userHandle the user handle of the user we want to act on behalf of.
866     * @param message the message to log on security exception.
867     */
868    private void enforceCrossUserPermission(int userHandle, String message) {
869        final int callingUser = UserHandle.getCallingUserId();
870        if (callingUser != userHandle) {
871            mContext.enforceCallingOrSelfPermission(
872                    Manifest.permission.INTERACT_ACROSS_USERS_FULL, message);
873        }
874    }
875
876    /**
877     * Hide this class since it is not part of api,
878     * but current unittest framework requires it to be public
879     * @hide
880     */
881    public static final class ObserverNode {
882        private class ObserverEntry implements IBinder.DeathRecipient {
883            public final IContentObserver observer;
884            public final int uid;
885            public final int pid;
886            public final boolean notifyForDescendants;
887            private final int userHandle;
888            private final Object observersLock;
889
890            public ObserverEntry(IContentObserver o, boolean n, Object observersLock,
891                    int _uid, int _pid, int _userHandle) {
892                this.observersLock = observersLock;
893                observer = o;
894                uid = _uid;
895                pid = _pid;
896                userHandle = _userHandle;
897                notifyForDescendants = n;
898                try {
899                    observer.asBinder().linkToDeath(this, 0);
900                } catch (RemoteException e) {
901                    binderDied();
902                }
903            }
904
905            public void binderDied() {
906                synchronized (observersLock) {
907                    removeObserverLocked(observer);
908                }
909            }
910
911            public void dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args,
912                    String name, String prefix, SparseIntArray pidCounts) {
913                pidCounts.put(pid, pidCounts.get(pid)+1);
914                pw.print(prefix); pw.print(name); pw.print(": pid=");
915                        pw.print(pid); pw.print(" uid=");
916                        pw.print(uid); pw.print(" user=");
917                        pw.print(userHandle); pw.print(" target=");
918                        pw.println(Integer.toHexString(System.identityHashCode(
919                                observer != null ? observer.asBinder() : null)));
920            }
921        }
922
923        public static final int INSERT_TYPE = 0;
924        public static final int UPDATE_TYPE = 1;
925        public static final int DELETE_TYPE = 2;
926
927        private String mName;
928        private ArrayList<ObserverNode> mChildren = new ArrayList<ObserverNode>();
929        private ArrayList<ObserverEntry> mObservers = new ArrayList<ObserverEntry>();
930
931        public ObserverNode(String name) {
932            mName = name;
933        }
934
935        public void dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args,
936                String name, String prefix, int[] counts, SparseIntArray pidCounts) {
937            String innerName = null;
938            if (mObservers.size() > 0) {
939                if ("".equals(name)) {
940                    innerName = mName;
941                } else {
942                    innerName = name + "/" + mName;
943                }
944                for (int i=0; i<mObservers.size(); i++) {
945                    counts[1]++;
946                    mObservers.get(i).dumpLocked(fd, pw, args, innerName, prefix,
947                            pidCounts);
948                }
949            }
950            if (mChildren.size() > 0) {
951                if (innerName == null) {
952                    if ("".equals(name)) {
953                        innerName = mName;
954                    } else {
955                        innerName = name + "/" + mName;
956                    }
957                }
958                for (int i=0; i<mChildren.size(); i++) {
959                    counts[0]++;
960                    mChildren.get(i).dumpLocked(fd, pw, args, innerName, prefix,
961                            counts, pidCounts);
962                }
963            }
964        }
965
966        private String getUriSegment(Uri uri, int index) {
967            if (uri != null) {
968                if (index == 0) {
969                    return uri.getAuthority();
970                } else {
971                    return uri.getPathSegments().get(index - 1);
972                }
973            } else {
974                return null;
975            }
976        }
977
978        private int countUriSegments(Uri uri) {
979            if (uri == null) {
980                return 0;
981            }
982            return uri.getPathSegments().size() + 1;
983        }
984
985        // Invariant:  userHandle is either a hard user number or is USER_ALL
986        public void addObserverLocked(Uri uri, IContentObserver observer,
987                boolean notifyForDescendants, Object observersLock,
988                int uid, int pid, int userHandle) {
989            addObserverLocked(uri, 0, observer, notifyForDescendants, observersLock,
990                    uid, pid, userHandle);
991        }
992
993        private void addObserverLocked(Uri uri, int index, IContentObserver observer,
994                boolean notifyForDescendants, Object observersLock,
995                int uid, int pid, int userHandle) {
996            // If this is the leaf node add the observer
997            if (index == countUriSegments(uri)) {
998                mObservers.add(new ObserverEntry(observer, notifyForDescendants, observersLock,
999                        uid, pid, userHandle));
1000                return;
1001            }
1002
1003            // Look to see if the proper child already exists
1004            String segment = getUriSegment(uri, index);
1005            if (segment == null) {
1006                throw new IllegalArgumentException("Invalid Uri (" + uri + ") used for observer");
1007            }
1008            int N = mChildren.size();
1009            for (int i = 0; i < N; i++) {
1010                ObserverNode node = mChildren.get(i);
1011                if (node.mName.equals(segment)) {
1012                    node.addObserverLocked(uri, index + 1, observer, notifyForDescendants,
1013                            observersLock, uid, pid, userHandle);
1014                    return;
1015                }
1016            }
1017
1018            // No child found, create one
1019            ObserverNode node = new ObserverNode(segment);
1020            mChildren.add(node);
1021            node.addObserverLocked(uri, index + 1, observer, notifyForDescendants,
1022                    observersLock, uid, pid, userHandle);
1023        }
1024
1025        public boolean removeObserverLocked(IContentObserver observer) {
1026            int size = mChildren.size();
1027            for (int i = 0; i < size; i++) {
1028                boolean empty = mChildren.get(i).removeObserverLocked(observer);
1029                if (empty) {
1030                    mChildren.remove(i);
1031                    i--;
1032                    size--;
1033                }
1034            }
1035
1036            IBinder observerBinder = observer.asBinder();
1037            size = mObservers.size();
1038            for (int i = 0; i < size; i++) {
1039                ObserverEntry entry = mObservers.get(i);
1040                if (entry.observer.asBinder() == observerBinder) {
1041                    mObservers.remove(i);
1042                    // We no longer need to listen for death notifications. Remove it.
1043                    observerBinder.unlinkToDeath(entry, 0);
1044                    break;
1045                }
1046            }
1047
1048            if (mChildren.size() == 0 && mObservers.size() == 0) {
1049                return true;
1050            }
1051            return false;
1052        }
1053
1054        private void collectMyObserversLocked(boolean leaf, IContentObserver observer,
1055                boolean observerWantsSelfNotifications, int targetUserHandle,
1056                ArrayList<ObserverCall> calls) {
1057            int N = mObservers.size();
1058            IBinder observerBinder = observer == null ? null : observer.asBinder();
1059            for (int i = 0; i < N; i++) {
1060                ObserverEntry entry = mObservers.get(i);
1061
1062                // Don't notify the observer if it sent the notification and isn't interested
1063                // in self notifications
1064                boolean selfChange = (entry.observer.asBinder() == observerBinder);
1065                if (selfChange && !observerWantsSelfNotifications) {
1066                    continue;
1067                }
1068
1069                // Does this observer match the target user?
1070                if (targetUserHandle == UserHandle.USER_ALL
1071                        || entry.userHandle == UserHandle.USER_ALL
1072                        || targetUserHandle == entry.userHandle) {
1073                    // Make sure the observer is interested in the notification
1074                    if (leaf || (!leaf && entry.notifyForDescendants)) {
1075                        calls.add(new ObserverCall(this, entry.observer, selfChange));
1076                    }
1077                }
1078            }
1079        }
1080
1081        /**
1082         * targetUserHandle is either a hard user handle or is USER_ALL
1083         */
1084        public void collectObserversLocked(Uri uri, int index, IContentObserver observer,
1085                boolean observerWantsSelfNotifications, int targetUserHandle,
1086                ArrayList<ObserverCall> calls) {
1087            String segment = null;
1088            int segmentCount = countUriSegments(uri);
1089            if (index >= segmentCount) {
1090                // This is the leaf node, notify all observers
1091                collectMyObserversLocked(true, observer, observerWantsSelfNotifications,
1092                        targetUserHandle, calls);
1093            } else if (index < segmentCount){
1094                segment = getUriSegment(uri, index);
1095                // Notify any observers at this level who are interested in descendants
1096                collectMyObserversLocked(false, observer, observerWantsSelfNotifications,
1097                        targetUserHandle, calls);
1098            }
1099
1100            int N = mChildren.size();
1101            for (int i = 0; i < N; i++) {
1102                ObserverNode node = mChildren.get(i);
1103                if (segment == null || node.mName.equals(segment)) {
1104                    // We found the child,
1105                    node.collectObserversLocked(uri, index + 1,
1106                            observer, observerWantsSelfNotifications, targetUserHandle, calls);
1107                    if (segment != null) {
1108                        break;
1109                    }
1110                }
1111            }
1112        }
1113    }
1114}
1115