2 * Copyright 2011 Google Inc.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.google.ipc.invalidation.ticl.android2;
19 import com.google.ipc.invalidation.external.client.SystemResources;
20 import com.google.ipc.invalidation.external.client.SystemResources.Logger;
21 import com.google.ipc.invalidation.external.client.SystemResources.Scheduler;
22 import com.google.ipc.invalidation.ticl.RecurringTask;
23 import com.google.ipc.invalidation.ticl.proto.AndroidService.AndroidSchedulerEvent;
24 import com.google.ipc.invalidation.util.NamedRunnable;
25 import com.google.ipc.invalidation.util.Preconditions;
26 import com.google.ipc.invalidation.util.TypedUtil;
28 import android.app.AlarmManager;
29 import android.app.PendingIntent;
30 import android.content.BroadcastReceiver;
31 import android.content.Context;
32 import android.content.Intent;
34 import java.util.HashMap;
39 * Scheduler for controlling access to internal Ticl state in Android.
41 * This class maintains a map from recurring task names to the recurring task instances in the
42 * associated Ticl. To schedule a recurring task, it uses the {@link AlarmManager} to schedule
43 * an intent to itself at the appropriate time in the future. This intent contains the name of
44 * the task to run; when it is received, this class looks up the appropriate recurring task
45 * instance and runs it.
47 * Note that this class only supports scheduling recurring tasks, not ordinary runnables. In
48 * order for it to be used, the application must declare the AlarmReceiver of the scheduler
49 * in the application's manifest file; see the implementation comment in AlarmReceiver for
53 public final class AndroidInternalScheduler implements Scheduler {
54 /** Class that receives AlarmManager broadcasts and reissues them as intents for this service. */
55 public static final class AlarmReceiver extends BroadcastReceiver {
57 * This class needs to be public so that it can be instantiated by the Android runtime.
58 * Additionally, it should be declared as a broadcast receiver in the application manifest:
59 * <receiver android:name="com.google.ipc.invalidation.ticl.android2.\
60 * AndroidInternalScheduler$AlarmReceiver" android:enabled="true"/>
64 public void onReceive(Context context, Intent intent) {
65 // Resend the intent to the service so that it's processed on the handler thread and with
66 // the automatic shutdown logic provided by IntentService.
67 intent.setClassName(context, new AndroidTiclManifest(context).getTiclServiceClass());
68 context.startService(intent);
73 * If {@code true}, {@link #isRunningOnThread} will verify that calls are being made from either
74 * the {@link TiclService} or the {@link TestableTiclService.TestableClient}.
76 public static boolean checkStackForTest = false;
78 /** Class name of the testable client class, for checking call stacks in tests. */
79 private static final String TESTABLE_CLIENT_CLASSNAME_FOR_TEST =
80 "com.google.ipc.invalidation.ticl.android2.TestableTiclService$TestableClient";
83 * {@link RecurringTask}-created runnables that can be executed by this instance, by their names.
85 private final Map<String, Runnable> registeredTasks = new HashMap<String, Runnable>();
87 /** Android system context. */
88 private final Context context;
90 /** Source of time for computing scheduling delays. */
91 private final AndroidClock clock;
93 private Logger logger;
95 /** Id of the Ticl for which this scheduler will process events. */
96 private long ticlId = -1;
98 AndroidInternalScheduler(Context context, AndroidClock clock) {
99 this.context = Preconditions.checkNotNull(context);
100 this.clock = Preconditions.checkNotNull(clock);
104 public void setSystemResources(SystemResources resources) {
105 this.logger = Preconditions.checkNotNull(resources.getLogger());
109 public void schedule(int delayMs, Runnable runnable) {
110 if (!(runnable instanceof NamedRunnable)) {
111 throw new RuntimeException("Unsupported: can only schedule named runnables, not " + runnable);
113 // Create an intent that will cause the service to run the right recurring task. We explicitly
114 // target it to our AlarmReceiver so that no other process in the system can receive it and so
115 // that our AlarmReceiver will not be able to receive events from any other broadcaster (which
116 // it would be if we used action-based targeting).
117 String taskName = ((NamedRunnable) runnable).getName();
118 Intent eventIntent = ProtocolIntents.newSchedulerIntent(taskName, ticlId);
119 eventIntent.setClass(context, AlarmReceiver.class);
121 // Create a pending intent that will cause the AlarmManager to fire the above intent.
122 PendingIntent sender = PendingIntent.getBroadcast(context,
123 (int) (Integer.MAX_VALUE * Math.random()), eventIntent, PendingIntent.FLAG_ONE_SHOT);
125 // Schedule the pending intent after the appropriate delay.
126 AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
127 long executeMs = clock.nowMs() + delayMs;
128 alarmManager.set(AlarmManager.RTC, executeMs, sender);
132 * Handles an event intent created in {@link #schedule} by running the corresponding recurring
135 * REQUIRES: a recurring task with the name in the intent be present in {@link #registeredTasks}.
137 void handleSchedulerEvent(AndroidSchedulerEvent event) {
138 Runnable recurringTaskRunnable = TypedUtil.mapGet(registeredTasks, event.getEventName());
139 if (recurringTaskRunnable == null) {
140 throw new NullPointerException("No task registered for " + event.getEventName());
142 if (ticlId != event.getTiclId()) {
143 logger.warning("Ignoring event with wrong ticl id (not %s): %s", ticlId, event);
146 recurringTaskRunnable.run();
150 * Registers {@code task} so that it can be subsequently run by the scheduler.
152 * REQUIRES: no recurring task with the same name be already present in {@link #registeredTasks}.
154 void registerTask(String name, Runnable runnable) {
155 Runnable previous = registeredTasks.put(name, runnable);
156 if (previous != null) {
157 String message = new StringBuilder()
158 .append("Cannot overwrite task registered on ")
162 .append("; tasks = ")
163 .append(registeredTasks.keySet())
165 throw new IllegalStateException(message);
170 public boolean isRunningOnThread() {
171 if (!checkStackForTest) {
174 // If requested, check that the current stack looks legitimate.
175 for (StackTraceElement stackElement : Thread.currentThread().getStackTrace()) {
176 if (stackElement.getMethodName().equals("onHandleIntent") &&
177 stackElement.getClassName().contains("TiclService")) {
178 // Called from the TiclService.
181 if (stackElement.getClassName().equals(TESTABLE_CLIENT_CLASSNAME_FOR_TEST)) {
182 // Called from the TestableClient.
190 public long getCurrentTimeMs() {
191 return clock.nowMs();
194 /** Removes the registered tasks. */
196 logger.fine("Clearing registered tasks on %s", this);
197 registeredTasks.clear();
201 * Sets the id of the ticl for which this scheduler will process events. We do not know the
202 * Ticl id until done constructing the Ticl, and we need the scheduler to construct a Ticl. This
203 * method breaks what would otherwise be a dependency cycle on getting the Ticl id.
205 void setTiclId(long ticlId) {
206 this.ticlId = ticlId;