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