ContentService.java revision 1b487ec44b6b5594914d52fa427bec4f29a60541
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 android.content;
18
19import android.accounts.Account;
20import android.database.IContentObserver;
21import android.database.sqlite.SQLiteException;
22import android.net.Uri;
23import android.os.Bundle;
24import android.os.IBinder;
25import android.os.Parcel;
26import android.os.RemoteException;
27import android.os.ServiceManager;
28import android.util.Config;
29import android.util.Log;
30import android.Manifest;
31
32import java.io.FileDescriptor;
33import java.io.PrintWriter;
34import java.util.ArrayList;
35import java.util.Collection;
36import java.util.List;
37
38/**
39 * {@hide}
40 */
41public final class ContentService extends IContentService.Stub {
42    private static final String TAG = "ContentService";
43    private Context mContext;
44    private boolean mFactoryTest;
45    private final ObserverNode mRootNode = new ObserverNode("");
46    private SyncManager mSyncManager = null;
47    private final Object mSyncManagerLock = new Object();
48
49    private SyncManager getSyncManager() {
50        synchronized(mSyncManagerLock) {
51            try {
52                // Try to create the SyncManager, return null if it fails (e.g. the disk is full).
53                if (mSyncManager == null) mSyncManager = new SyncManager(mContext, mFactoryTest);
54            } catch (SQLiteException e) {
55                Log.e(TAG, "Can't create SyncManager", e);
56            }
57            return mSyncManager;
58        }
59    }
60
61    @Override
62    protected synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
63        mContext.enforceCallingOrSelfPermission(Manifest.permission.DUMP,
64                "caller doesn't have the DUMP permission");
65
66        // This makes it so that future permission checks will be in the context of this
67        // process rather than the caller's process. We will restore this before returning.
68        long identityToken = clearCallingIdentity();
69        try {
70            if (mSyncManager == null) {
71                pw.println("No SyncManager created!  (Disk full?)");
72            } else {
73                mSyncManager.dump(fd, pw);
74            }
75        } finally {
76            restoreCallingIdentity(identityToken);
77        }
78    }
79
80    @Override
81    public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
82            throws RemoteException {
83        try {
84            return super.onTransact(code, data, reply, flags);
85        } catch (RuntimeException e) {
86            // The content service only throws security exceptions, so let's
87            // log all others.
88            if (!(e instanceof SecurityException)) {
89                Log.e(TAG, "Content Service Crash", e);
90            }
91            throw e;
92        }
93    }
94
95    /*package*/ ContentService(Context context, boolean factoryTest) {
96        mContext = context;
97        mFactoryTest = factoryTest;
98        getSyncManager();
99    }
100
101    public void registerContentObserver(Uri uri, boolean notifyForDescendents,
102            IContentObserver observer) {
103        if (observer == null || uri == null) {
104            throw new IllegalArgumentException("You must pass a valid uri and observer");
105        }
106        synchronized (mRootNode) {
107            mRootNode.addObserver(uri, observer, notifyForDescendents);
108            if (Config.LOGV) Log.v(TAG, "Registered observer " + observer + " at " + uri +
109                    " with notifyForDescendents " + notifyForDescendents);
110        }
111    }
112
113    public void unregisterContentObserver(IContentObserver observer) {
114        if (observer == null) {
115            throw new IllegalArgumentException("You must pass a valid observer");
116        }
117        synchronized (mRootNode) {
118            mRootNode.removeObserver(observer);
119            if (Config.LOGV) Log.v(TAG, "Unregistered observer " + observer);
120        }
121    }
122
123    public void notifyChange(Uri uri, IContentObserver observer,
124            boolean observerWantsSelfNotifications, boolean syncToNetwork) {
125        if (Log.isLoggable(TAG, Log.VERBOSE)) {
126            Log.v(TAG, "Notifying update of " + uri + " from observer " + observer
127                    + ", syncToNetwork " + syncToNetwork);
128        }
129        // This makes it so that future permission checks will be in the context of this
130        // process rather than the caller's process. We will restore this before returning.
131        long identityToken = clearCallingIdentity();
132        try {
133            ArrayList<ObserverCall> calls = new ArrayList<ObserverCall>();
134            synchronized (mRootNode) {
135                mRootNode.collectObservers(uri, 0, observer, observerWantsSelfNotifications,
136                        calls);
137            }
138            final int numCalls = calls.size();
139            for (int i=0; i<numCalls; i++) {
140                ObserverCall oc = calls.get(i);
141                try {
142                    oc.mObserver.onChange(oc.mSelfNotify);
143                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
144                        Log.v(TAG, "Notified " + oc.mObserver + " of " + "update at " + uri);
145                    }
146                } catch (RemoteException ex) {
147                    synchronized (mRootNode) {
148                        Log.w(TAG, "Found dead observer, removing");
149                        IBinder binder = oc.mObserver.asBinder();
150                        final ArrayList<ObserverNode.ObserverEntry> list
151                                = oc.mNode.mObservers;
152                        int numList = list.size();
153                        for (int j=0; j<numList; j++) {
154                            ObserverNode.ObserverEntry oe = list.get(j);
155                            if (oe.observer.asBinder() == binder) {
156                                list.remove(j);
157                                j--;
158                                numList--;
159                            }
160                        }
161                    }
162                }
163            }
164            if (syncToNetwork) {
165                SyncManager syncManager = getSyncManager();
166                if (syncManager != null) {
167                    syncManager.scheduleLocalSync(null /* all accounts */, uri.getAuthority());
168                }
169            }
170        } finally {
171            restoreCallingIdentity(identityToken);
172        }
173    }
174
175    /**
176     * Hide this class since it is not part of api,
177     * but current unittest framework requires it to be public
178     * @hide
179     *
180     */
181    public static final class ObserverCall {
182        final ObserverNode mNode;
183        final IContentObserver mObserver;
184        final boolean mSelfNotify;
185
186        ObserverCall(ObserverNode node, IContentObserver observer,
187                boolean selfNotify) {
188            mNode = node;
189            mObserver = observer;
190            mSelfNotify = selfNotify;
191        }
192    }
193
194    public void requestSync(Account account, String authority, Bundle extras) {
195        ContentResolver.validateSyncExtrasBundle(extras);
196        // This makes it so that future permission checks will be in the context of this
197        // process rather than the caller's process. We will restore this before returning.
198        long identityToken = clearCallingIdentity();
199        try {
200            SyncManager syncManager = getSyncManager();
201            if (syncManager != null) {
202                syncManager.scheduleSync(account, authority, extras, 0 /* no delay */,
203                        false /* onlyThoseWithUnkownSyncableState */);
204            }
205        } finally {
206            restoreCallingIdentity(identityToken);
207        }
208    }
209
210    /**
211     * Clear all scheduled sync operations that match the uri and cancel the active sync
212     * if they match the authority and account, if they are present.
213     * @param account filter the pending and active syncs to cancel using this account
214     * @param authority filter the pending and active syncs to cancel using this authority
215     */
216    public void cancelSync(Account account, String authority) {
217        // This makes it so that future permission checks will be in the context of this
218        // process rather than the caller's process. We will restore this before returning.
219        long identityToken = clearCallingIdentity();
220        try {
221            SyncManager syncManager = getSyncManager();
222            if (syncManager != null) {
223                syncManager.clearScheduledSyncOperations(account, authority);
224                syncManager.cancelActiveSync(account, authority);
225            }
226        } finally {
227            restoreCallingIdentity(identityToken);
228        }
229    }
230
231    /**
232     * Get information about the SyncAdapters that are known to the system.
233     * @return an array of SyncAdapters that have registered with the system
234     */
235    public SyncAdapterType[] getSyncAdapterTypes() {
236        // This makes it so that future permission checks will be in the context of this
237        // process rather than the caller's process. We will restore this before returning.
238        long identityToken = clearCallingIdentity();
239        try {
240            SyncManager syncManager = getSyncManager();
241            return syncManager.getSyncAdapterTypes();
242        } finally {
243            restoreCallingIdentity(identityToken);
244        }
245    }
246
247    public boolean getSyncAutomatically(Account account, String providerName) {
248        mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
249                "no permission to read the sync settings");
250        long identityToken = clearCallingIdentity();
251        try {
252            SyncManager syncManager = getSyncManager();
253            if (syncManager != null) {
254                return syncManager.getSyncStorageEngine().getSyncAutomatically(
255                        account, providerName);
256            }
257        } finally {
258            restoreCallingIdentity(identityToken);
259        }
260        return false;
261    }
262
263    public void setSyncAutomatically(Account account, String providerName, boolean sync) {
264        mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
265                "no permission to write the sync settings");
266        long identityToken = clearCallingIdentity();
267        try {
268            SyncManager syncManager = getSyncManager();
269            if (syncManager != null) {
270                syncManager.getSyncStorageEngine().setSyncAutomatically(
271                        account, providerName, sync);
272            }
273        } finally {
274            restoreCallingIdentity(identityToken);
275        }
276    }
277
278    public void addPeriodicSync(Account account, String authority, Bundle extras,
279            long pollFrequency) {
280        mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
281                "no permission to write the sync settings");
282        long identityToken = clearCallingIdentity();
283        try {
284            getSyncManager().getSyncStorageEngine().addPeriodicSync(
285                    account, authority, extras, pollFrequency);
286        } finally {
287            restoreCallingIdentity(identityToken);
288        }
289    }
290
291    public void removePeriodicSync(Account account, String authority, Bundle extras) {
292        mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
293                "no permission to write the sync settings");
294        long identityToken = clearCallingIdentity();
295        try {
296            getSyncManager().getSyncStorageEngine().removePeriodicSync(account, authority, extras);
297        } finally {
298            restoreCallingIdentity(identityToken);
299        }
300    }
301
302    public List<PeriodicSync> getPeriodicSyncs(Account account, String providerName) {
303        mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
304                "no permission to read the sync settings");
305        long identityToken = clearCallingIdentity();
306        try {
307            return getSyncManager().getSyncStorageEngine().getPeriodicSyncs(
308                    account, providerName);
309        } finally {
310            restoreCallingIdentity(identityToken);
311        }
312    }
313
314    public int getIsSyncable(Account account, String providerName) {
315        mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
316                "no permission to read the sync settings");
317        long identityToken = clearCallingIdentity();
318        try {
319            SyncManager syncManager = getSyncManager();
320            if (syncManager != null) {
321                return syncManager.getSyncStorageEngine().getIsSyncable(
322                        account, providerName);
323            }
324        } finally {
325            restoreCallingIdentity(identityToken);
326        }
327        return -1;
328    }
329
330    public void setIsSyncable(Account account, String providerName, int syncable) {
331        mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
332                "no permission to write the sync settings");
333        long identityToken = clearCallingIdentity();
334        try {
335            SyncManager syncManager = getSyncManager();
336            if (syncManager != null) {
337                syncManager.getSyncStorageEngine().setIsSyncable(
338                        account, providerName, syncable);
339            }
340        } finally {
341            restoreCallingIdentity(identityToken);
342        }
343    }
344
345    public boolean getMasterSyncAutomatically() {
346        mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
347                "no permission to read the sync settings");
348        long identityToken = clearCallingIdentity();
349        try {
350            SyncManager syncManager = getSyncManager();
351            if (syncManager != null) {
352                return syncManager.getSyncStorageEngine().getMasterSyncAutomatically();
353            }
354        } finally {
355            restoreCallingIdentity(identityToken);
356        }
357        return false;
358    }
359
360    public void setMasterSyncAutomatically(boolean flag) {
361        mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
362                "no permission to write the sync settings");
363        long identityToken = clearCallingIdentity();
364        try {
365            SyncManager syncManager = getSyncManager();
366            if (syncManager != null) {
367                syncManager.getSyncStorageEngine().setMasterSyncAutomatically(flag);
368            }
369        } finally {
370            restoreCallingIdentity(identityToken);
371        }
372    }
373
374    public boolean isSyncActive(Account account, String authority) {
375        mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
376                "no permission to read the sync stats");
377        long identityToken = clearCallingIdentity();
378        try {
379            SyncManager syncManager = getSyncManager();
380            if (syncManager != null) {
381                return syncManager.getSyncStorageEngine().isSyncActive(
382                        account, authority);
383            }
384        } finally {
385            restoreCallingIdentity(identityToken);
386        }
387        return false;
388    }
389
390    public ActiveSyncInfo getActiveSync() {
391        mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
392                "no permission to read the sync stats");
393        long identityToken = clearCallingIdentity();
394        try {
395            SyncManager syncManager = getSyncManager();
396            if (syncManager != null) {
397                return syncManager.getSyncStorageEngine().getActiveSync();
398            }
399        } finally {
400            restoreCallingIdentity(identityToken);
401        }
402        return null;
403    }
404
405    public SyncStatusInfo getSyncStatus(Account account, String authority) {
406        mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
407                "no permission to read the sync stats");
408        long identityToken = clearCallingIdentity();
409        try {
410            SyncManager syncManager = getSyncManager();
411            if (syncManager != null) {
412                return syncManager.getSyncStorageEngine().getStatusByAccountAndAuthority(
413                    account, authority);
414            }
415        } finally {
416            restoreCallingIdentity(identityToken);
417        }
418        return null;
419    }
420
421    public boolean isSyncPending(Account account, String authority) {
422        mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
423                "no permission to read the sync stats");
424        long identityToken = clearCallingIdentity();
425        try {
426            SyncManager syncManager = getSyncManager();
427            if (syncManager != null) {
428                return syncManager.getSyncStorageEngine().isSyncPending(account, authority);
429            }
430        } finally {
431            restoreCallingIdentity(identityToken);
432        }
433        return false;
434    }
435
436    public void addStatusChangeListener(int mask, ISyncStatusObserver callback) {
437        long identityToken = clearCallingIdentity();
438        try {
439            SyncManager syncManager = getSyncManager();
440            if (syncManager != null && callback != null) {
441                syncManager.getSyncStorageEngine().addStatusChangeListener(mask, callback);
442            }
443        } finally {
444            restoreCallingIdentity(identityToken);
445        }
446    }
447
448    public void removeStatusChangeListener(ISyncStatusObserver callback) {
449        long identityToken = clearCallingIdentity();
450        try {
451            SyncManager syncManager = getSyncManager();
452            if (syncManager != null && callback != null) {
453                syncManager.getSyncStorageEngine().removeStatusChangeListener(callback);
454            }
455        } finally {
456            restoreCallingIdentity(identityToken);
457        }
458    }
459
460    public static IContentService main(Context context, boolean factoryTest) {
461        ContentService service = new ContentService(context, factoryTest);
462        ServiceManager.addService(ContentResolver.CONTENT_SERVICE_NAME, service);
463        return service;
464    }
465
466    /**
467     * Hide this class since it is not part of api,
468     * but current unittest framework requires it to be public
469     * @hide
470     */
471    public static final class ObserverNode {
472        private class ObserverEntry implements IBinder.DeathRecipient {
473            public IContentObserver observer;
474            public boolean notifyForDescendents;
475
476            public ObserverEntry(IContentObserver o, boolean n) {
477                observer = o;
478                notifyForDescendents = n;
479                try {
480                    observer.asBinder().linkToDeath(this, 0);
481                } catch (RemoteException e) {
482                    binderDied();
483                }
484            }
485
486            public void binderDied() {
487                removeObserver(observer);
488            }
489        }
490
491        public static final int INSERT_TYPE = 0;
492        public static final int UPDATE_TYPE = 1;
493        public static final int DELETE_TYPE = 2;
494
495        private String mName;
496        private ArrayList<ObserverNode> mChildren = new ArrayList<ObserverNode>();
497        private ArrayList<ObserverEntry> mObservers = new ArrayList<ObserverEntry>();
498
499        public ObserverNode(String name) {
500            mName = name;
501        }
502
503        private String getUriSegment(Uri uri, int index) {
504            if (uri != null) {
505                if (index == 0) {
506                    return uri.getAuthority();
507                } else {
508                    return uri.getPathSegments().get(index - 1);
509                }
510            } else {
511                return null;
512            }
513        }
514
515        private int countUriSegments(Uri uri) {
516            if (uri == null) {
517                return 0;
518            }
519            return uri.getPathSegments().size() + 1;
520        }
521
522        public void addObserver(Uri uri, IContentObserver observer, boolean notifyForDescendents) {
523            addObserver(uri, 0, observer, notifyForDescendents);
524        }
525
526        private void addObserver(Uri uri, int index, IContentObserver observer,
527                boolean notifyForDescendents) {
528
529            // If this is the leaf node add the observer
530            if (index == countUriSegments(uri)) {
531                mObservers.add(new ObserverEntry(observer, notifyForDescendents));
532                return;
533            }
534
535            // Look to see if the proper child already exists
536            String segment = getUriSegment(uri, index);
537            int N = mChildren.size();
538            for (int i = 0; i < N; i++) {
539                ObserverNode node = mChildren.get(i);
540                if (node.mName.equals(segment)) {
541                    node.addObserver(uri, index + 1, observer, notifyForDescendents);
542                    return;
543                }
544            }
545
546            // No child found, create one
547            ObserverNode node = new ObserverNode(segment);
548            mChildren.add(node);
549            node.addObserver(uri, index + 1, observer, notifyForDescendents);
550        }
551
552        public boolean removeObserver(IContentObserver observer) {
553            int size = mChildren.size();
554            for (int i = 0; i < size; i++) {
555                boolean empty = mChildren.get(i).removeObserver(observer);
556                if (empty) {
557                    mChildren.remove(i);
558                    i--;
559                    size--;
560                }
561            }
562
563            IBinder observerBinder = observer.asBinder();
564            size = mObservers.size();
565            for (int i = 0; i < size; i++) {
566                ObserverEntry entry = mObservers.get(i);
567                if (entry.observer.asBinder() == observerBinder) {
568                    mObservers.remove(i);
569                    // We no longer need to listen for death notifications. Remove it.
570                    observerBinder.unlinkToDeath(entry, 0);
571                    break;
572                }
573            }
574
575            if (mChildren.size() == 0 && mObservers.size() == 0) {
576                return true;
577            }
578            return false;
579        }
580
581        private void collectMyObservers(Uri uri,
582                boolean leaf, IContentObserver observer, boolean selfNotify,
583                ArrayList<ObserverCall> calls)
584        {
585            int N = mObservers.size();
586            IBinder observerBinder = observer == null ? null : observer.asBinder();
587            for (int i = 0; i < N; i++) {
588                ObserverEntry entry = mObservers.get(i);
589
590                // Don't notify the observer if it sent the notification and isn't interesed
591                // in self notifications
592                if (entry.observer.asBinder() == observerBinder && !selfNotify) {
593                    continue;
594                }
595
596                // Make sure the observer is interested in the notification
597                if (leaf || (!leaf && entry.notifyForDescendents)) {
598                    calls.add(new ObserverCall(this, entry.observer, selfNotify));
599                }
600            }
601        }
602
603        public void collectObservers(Uri uri, int index, IContentObserver observer,
604                boolean selfNotify, ArrayList<ObserverCall> calls) {
605            String segment = null;
606            int segmentCount = countUriSegments(uri);
607            if (index >= segmentCount) {
608                // This is the leaf node, notify all observers
609                collectMyObservers(uri, true, observer, selfNotify, calls);
610            } else if (index < segmentCount){
611                segment = getUriSegment(uri, index);
612                // Notify any observers at this level who are interested in descendents
613                collectMyObservers(uri, false, observer, selfNotify, calls);
614            }
615
616            int N = mChildren.size();
617            for (int i = 0; i < N; i++) {
618                ObserverNode node = mChildren.get(i);
619                if (segment == null || node.mName.equals(segment)) {
620                    // We found the child,
621                    node.collectObservers(uri, index + 1, observer, selfNotify, calls);
622                    if (segment != null) {
623                        break;
624                    }
625                }
626            }
627        }
628    }
629}
630