summaryrefslogtreecommitdiffstats
path: root/core/java/android/content/SyncService.java
blob: 100fd40cf6946e03656b65dbfa448ce97bc9fa22 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
/*
 * Copyright (C) 2013 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.content;

import android.app.Service;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Process;
import android.os.Trace;

import com.android.internal.annotations.GuardedBy;

import java.util.HashMap;

/**
 * Simplified @link android.content.AbstractThreadedSyncAdapter. Folds that
 * behaviour into a service to which the system can bind when requesting an
 * anonymous (providerless/accountless) sync.
 * <p>
 * In order to perform an anonymous sync operation you must extend this service,
 * implementing the abstract methods. This service must then be declared in the
 * application's manifest as usual. You can use this service for other work, however you
 * <b> must not </b> override the onBind() method unless you know what you're doing,
 * which limits the usefulness of this service for other work.
 *
 * <pre>
 * &lt;service ndroid:name=".MyAnonymousSyncService" android:permission="android.permission.SYNC" /&gt;
 * </pre>
 * Like @link android.content.AbstractThreadedSyncAdapter this service supports
 * multiple syncs at the same time. Each incoming startSync() with a unique tag
 * will spawn a thread to do the work of that sync. If startSync() is called
 * with a tag that already exists, a SyncResult.ALREADY_IN_PROGRESS is returned.
 * Remember that your service will spawn multiple threads if you schedule multiple syncs
 * at once, so if you mutate local objects you must ensure synchronization.
 */
public abstract class SyncService extends Service {

    /** SyncAdapter Instantiation that any anonymous syncs call. */
    private final AnonymousSyncAdapterImpl mSyncAdapter = new AnonymousSyncAdapterImpl();

    /** Keep track of on-going syncs, keyed by tag. */
    @GuardedBy("mLock")
    private final HashMap<Bundle, AnonymousSyncThread>
            mSyncThreads = new HashMap<Bundle, AnonymousSyncThread>();
    /** Lock object for accessing the SyncThreads HashMap. */
    private final Object mSyncThreadLock = new Object();

    @Override
    public IBinder onBind(Intent intent) {
        return mSyncAdapter.asBinder();
    }

    /** {@hide} */
    private class AnonymousSyncAdapterImpl extends IAnonymousSyncAdapter.Stub {

        @Override
        public void startSync(ISyncContext syncContext, Bundle extras) {
            // Wrap the provided Sync Context because it may go away by the time
            // we call it.
            final SyncContext syncContextClient = new SyncContext(syncContext);
            boolean alreadyInProgress = false;
            synchronized (mSyncThreadLock) {
                if (mSyncThreads.containsKey(extras)) {
                    // Don't want to call back to SyncManager while still
                    // holding lock.
                    alreadyInProgress = true;
                } else {
                    AnonymousSyncThread syncThread = new AnonymousSyncThread(
                            syncContextClient, extras);
                    mSyncThreads.put(extras, syncThread);
                    syncThread.start();
                }
            }
            if (alreadyInProgress) {
                syncContextClient.onFinished(SyncResult.ALREADY_IN_PROGRESS);
            }
        }

        /**
         * Used by the SM to cancel a specific sync using the {@link
         * com.android.server.content.SyncManager.ActiveSyncContext} as a handle.
         */
        @Override
        public void cancelSync(ISyncContext syncContext) {
            AnonymousSyncThread runningSync = null;
            synchronized (mSyncThreadLock) {
                for (AnonymousSyncThread thread : mSyncThreads.values()) {
                    if (thread.mSyncContext.getSyncContextBinder() == syncContext.asBinder()) {
                        runningSync = thread;
                        break;
                    }
                }
            }
            if (runningSync != null) {
                runningSync.interrupt();
            }
        }
    }

    /**
     * {@hide}
     * Similar to {@link android.content.AbstractThreadedSyncAdapter.SyncThread}. However while
     * the ATSA considers an already in-progress sync to be if the account provided is currently
     * syncing, this anonymous sync has no notion of account and therefore considers a sync unique
     * if the provided bundle is different.
     */
    private class AnonymousSyncThread extends Thread {
        private final SyncContext mSyncContext;
        private final Bundle mExtras;

        public AnonymousSyncThread(SyncContext syncContext, Bundle extras) {
            mSyncContext = syncContext;
            mExtras = extras;
        }

        @Override
        public void run() {
            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);

            Trace.traceBegin(Trace.TRACE_TAG_SYNC_MANAGER, getApplication().getPackageName());

            SyncResult syncResult = new SyncResult();
            try {
                if (isCancelled()) {
                    return;
                }
                // Run the sync based off of the provided code.
                SyncService.this.onPerformSync(mExtras, syncResult);
            } finally {
                Trace.traceEnd(Trace.TRACE_TAG_SYNC_MANAGER);
                if (!isCancelled()) {
                    mSyncContext.onFinished(syncResult);
                }
                // Synchronize so that the assignment will be seen by other
                // threads
                // that also synchronize accesses to mSyncThreads.
                synchronized (mSyncThreadLock) {
                    mSyncThreads.remove(mExtras);
                }
            }
        }

        private boolean isCancelled() {
            return Thread.currentThread().isInterrupted();
        }
    }

    /**
     * Initiate an anonymous sync using this service. SyncAdapter-specific
     * parameters may be specified in extras, which is guaranteed to not be
     * null.
     */
    public abstract void onPerformSync(Bundle extras, SyncResult syncResult);

}