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