Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / third_party / cacheinvalidation / src / java / com / google / ipc / invalidation / ticl / android2 / AndroidInternalScheduler.java
1 /*
2  * Copyright 2011 Google Inc.
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
17 package com.google.ipc.invalidation.ticl.android2;
18
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;
27
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;
33
34 import java.util.HashMap;
35 import java.util.Map;
36
37
38 /**
39  * Scheduler for controlling access to internal Ticl state in Android.
40  * <p>
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.
46  * <p>
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
50  * details.
51  *
52  */
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 {
56     /*
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"/>
61      */
62
63     @Override
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);
69     }
70   }
71
72   /**
73    * If {@code true}, {@link #isRunningOnThread} will verify that calls are being made from either
74    * the {@link TiclService} or the {@link TestableTiclService.TestableClient}.
75    */
76   public static boolean checkStackForTest = false;
77
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";
81
82   /**
83    * {@link RecurringTask}-created runnables that can be executed by this instance, by their names.
84    */
85   private final Map<String, Runnable> registeredTasks = new HashMap<String, Runnable>();
86
87   /** Android system context. */
88   private final Context context;
89
90   /** Source of time for computing scheduling delays. */
91   private final AndroidClock clock;
92
93   private Logger logger;
94
95   /** Id of the Ticl for which this scheduler will process events. */
96   private long ticlId = -1;
97
98   AndroidInternalScheduler(Context context, AndroidClock clock) {
99     this.context = Preconditions.checkNotNull(context);
100     this.clock = Preconditions.checkNotNull(clock);
101   }
102
103   @Override
104   public void setSystemResources(SystemResources resources) {
105     this.logger = Preconditions.checkNotNull(resources.getLogger());
106   }
107
108   @Override
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);
112     }
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);
120
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);
124
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);
129   }
130
131   /**
132    * Handles an event intent created in {@link #schedule} by running the corresponding recurring
133    * task.
134    * <p>
135    * REQUIRES: a recurring task with the name in the intent be present in {@link #registeredTasks}.
136    */
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());
141     }
142     if (ticlId != event.getTiclId()) {
143       logger.warning("Ignoring event with wrong ticl id (not %s): %s", ticlId, event);
144       return;
145     }
146     recurringTaskRunnable.run();
147   }
148
149   /**
150    * Registers {@code task} so that it can be subsequently run by the scheduler.
151    * <p>
152    * REQUIRES: no recurring task with the same name be already present in {@link #registeredTasks}.
153    */
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 ")
159           .append(name)
160           .append(", ")
161           .append(this)
162           .append("; tasks = ")
163           .append(registeredTasks.keySet())
164           .toString();
165       throw new IllegalStateException(message);
166     }
167   }
168
169   @Override
170   public boolean isRunningOnThread() {
171     if (!checkStackForTest) {
172       return true;
173     }
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.
179         return true;
180       }
181       if (stackElement.getClassName().equals(TESTABLE_CLIENT_CLASSNAME_FOR_TEST)) {
182         // Called from the TestableClient.
183         return true;
184       }
185     }
186     return false;
187   }
188
189   @Override
190   public long getCurrentTimeMs() {
191     return clock.nowMs();
192   }
193
194   /** Removes the registered tasks. */
195   void reset() {
196     logger.fine("Clearing registered tasks on %s", this);
197     registeredTasks.clear();
198   }
199
200   /**
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.
204    */
205   void setTiclId(long ticlId) {
206     this.ticlId = ticlId;
207   }
208 }