blob: 705b994e74fd5407628e707b965903e979920884 [file] [log] [blame]
Thomas Vachuska4f1a60c2014-10-28 13:39:07 -07001/*
Ray Milkey34c95902015-04-15 09:47:53 -07002 * Copyright 2014-2015 Open Networking Laboratory
Thomas Vachuska4f1a60c2014-10-28 13:39:07 -07003 *
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 */
Brian O'Connorabafb502014-12-02 22:26:20 -080016package org.onosproject.net.intent.impl;
tom95329eb2014-10-06 08:40:06 -070017
Brian O'Connor86f6f7f2014-12-01 17:02:45 -080018import com.google.common.collect.HashMultimap;
Brian O'Connora9c18b92015-09-28 16:03:43 -070019import com.google.common.collect.ImmutableSet;
Brian O'Connor86f6f7f2014-12-01 17:02:45 -080020import com.google.common.collect.SetMultimap;
tom95329eb2014-10-06 08:40:06 -070021import org.apache.felix.scr.annotations.Activate;
22import org.apache.felix.scr.annotations.Component;
23import org.apache.felix.scr.annotations.Deactivate;
24import org.apache.felix.scr.annotations.Reference;
25import org.apache.felix.scr.annotations.ReferenceCardinality;
Brian O'Connor69d6ac72015-05-29 16:24:06 -070026import org.apache.felix.scr.annotations.ReferencePolicy;
tom95329eb2014-10-06 08:40:06 -070027import org.apache.felix.scr.annotations.Service;
Brian O'Connorabafb502014-12-02 22:26:20 -080028import org.onosproject.event.Event;
Brian O'Connorb5dcc512015-03-24 17:28:00 -070029import org.onosproject.net.DeviceId;
30import org.onosproject.net.ElementId;
31import org.onosproject.net.HostId;
Brian O'Connorabafb502014-12-02 22:26:20 -080032import org.onosproject.net.Link;
33import org.onosproject.net.LinkKey;
34import org.onosproject.net.NetworkResource;
Sho SHIMIZU44f37612015-11-25 16:23:22 -080035import org.onosproject.net.PortNumber;
Brian O'Connorb5dcc512015-03-24 17:28:00 -070036import org.onosproject.net.device.DeviceEvent;
37import org.onosproject.net.device.DeviceListener;
38import org.onosproject.net.device.DeviceService;
39import org.onosproject.net.host.HostEvent;
40import org.onosproject.net.host.HostListener;
41import org.onosproject.net.host.HostService;
Brian O'Connor69d6ac72015-05-29 16:24:06 -070042import org.onosproject.net.intent.Intent;
Thomas Vachuskac46af202015-06-03 16:43:27 -070043import org.onosproject.net.intent.IntentData;
Brian O'Connorabafb502014-12-02 22:26:20 -080044import org.onosproject.net.intent.IntentService;
Ray Milkeyf9af43c2015-02-09 16:45:48 -080045import org.onosproject.net.intent.Key;
Madan Jampani1c965102016-01-13 14:34:16 -080046import org.onosproject.net.intent.IntentPartitionEvent;
47import org.onosproject.net.intent.IntentPartitionEventListener;
48import org.onosproject.net.intent.IntentPartitionService;
Brian O'Connorabafb502014-12-02 22:26:20 -080049import org.onosproject.net.link.LinkEvent;
Sho SHIMIZUe18cb122016-02-22 21:04:56 -080050import org.onosproject.net.resource.ResourceEvent;
51import org.onosproject.net.resource.ResourceListener;
52import org.onosproject.net.resource.ResourceService;
Brian O'Connorabafb502014-12-02 22:26:20 -080053import org.onosproject.net.topology.TopologyEvent;
54import org.onosproject.net.topology.TopologyListener;
55import org.onosproject.net.topology.TopologyService;
tom95329eb2014-10-06 08:40:06 -070056import org.slf4j.Logger;
57
Brian O'Connor86f6f7f2014-12-01 17:02:45 -080058import java.util.Collection;
Brian O'Connorb5dcc512015-03-24 17:28:00 -070059import java.util.Collections;
Brian O'Connor86f6f7f2014-12-01 17:02:45 -080060import java.util.HashSet;
Thomas Vachuskac46af202015-06-03 16:43:27 -070061import java.util.List;
Brian O'Connor86f6f7f2014-12-01 17:02:45 -080062import java.util.Set;
63import java.util.concurrent.ExecutorService;
Brian O'Connor69d6ac72015-05-29 16:24:06 -070064import java.util.concurrent.Executors;
65import java.util.concurrent.ScheduledExecutorService;
66import java.util.concurrent.TimeUnit;
67import java.util.concurrent.atomic.AtomicBoolean;
tom95329eb2014-10-06 08:40:06 -070068
69import static com.google.common.base.Preconditions.checkArgument;
70import static com.google.common.base.Preconditions.checkNotNull;
71import static com.google.common.collect.Multimaps.synchronizedSetMultimap;
72import static java.util.concurrent.Executors.newSingleThreadExecutor;
Thomas Vachuska6f94ded2015-02-21 14:02:38 -080073import static org.onlab.util.Tools.groupedThreads;
Thomas Vachuskac46af202015-06-03 16:43:27 -070074import static org.onlab.util.Tools.isNullOrEmpty;
Brian O'Connorabafb502014-12-02 22:26:20 -080075import static org.onosproject.net.LinkKey.linkKey;
Thomas Vachuskac46af202015-06-03 16:43:27 -070076import static org.onosproject.net.intent.IntentState.INSTALLED;
Brian O'Connor690fd1c2015-06-04 19:50:33 -070077import static org.onosproject.net.intent.IntentState.INSTALLING;
Brian O'Connorabafb502014-12-02 22:26:20 -080078import static org.onosproject.net.link.LinkEvent.Type.LINK_REMOVED;
79import static org.onosproject.net.link.LinkEvent.Type.LINK_UPDATED;
tom95329eb2014-10-06 08:40:06 -070080import static org.slf4j.LoggerFactory.getLogger;
81
82/**
83 * Entity responsible for tracking installed flows and for monitoring topology
84 * events to determine what flows are affected by topology changes.
85 */
Ray Milkeye97ede92014-11-20 10:43:12 -080086@Component(immediate = true)
tom95329eb2014-10-06 08:40:06 -070087@Service
tom85258ee2014-10-07 00:10:02 -070088public class ObjectiveTracker implements ObjectiveTrackerService {
tom95329eb2014-10-06 08:40:06 -070089
90 private final Logger log = getLogger(getClass());
91
Ray Milkeyf9af43c2015-02-09 16:45:48 -080092 private final SetMultimap<LinkKey, Key> intentsByLink =
Brian O'Connor64a0369d2015-02-20 22:02:59 -080093 //TODO this could be slow as a point of synchronization
Ray Milkeyf9af43c2015-02-09 16:45:48 -080094 synchronizedSetMultimap(HashMultimap.<LinkKey, Key>create());
tom95329eb2014-10-06 08:40:06 -070095
Brian O'Connorb5dcc512015-03-24 17:28:00 -070096 private final SetMultimap<ElementId, Key> intentsByDevice =
97 synchronizedSetMultimap(HashMultimap.<ElementId, Key>create());
98
tom95329eb2014-10-06 08:40:06 -070099 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
100 protected TopologyService topologyService;
101
Ray Milkeye97ede92014-11-20 10:43:12 -0800102 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Sho SHIMIZU0b9c4682015-11-02 18:30:34 -0800103 protected ResourceService resourceService;
Ray Milkeye97ede92014-11-20 10:43:12 -0800104
Brian O'Connorb5dcc512015-03-24 17:28:00 -0700105 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
106 protected DeviceService deviceService;
107
108 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
109 protected HostService hostService;
110
Brian O'Connor69d6ac72015-05-29 16:24:06 -0700111 @Reference(cardinality = ReferenceCardinality.OPTIONAL_UNARY,
112 policy = ReferencePolicy.DYNAMIC)
Brian O'Connor86f6f7f2014-12-01 17:02:45 -0800113 protected IntentService intentService;
114
Brian O'Connor69d6ac72015-05-29 16:24:06 -0700115 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Madan Jampani1c965102016-01-13 14:34:16 -0800116 protected IntentPartitionService partitionService;
Brian O'Connor69d6ac72015-05-29 16:24:06 -0700117
tom95329eb2014-10-06 08:40:06 -0700118 private ExecutorService executorService =
Brian O'Connorb5dcc512015-03-24 17:28:00 -0700119 newSingleThreadExecutor(groupedThreads("onos/intent", "objectivetracker"));
Brian O'Connor69d6ac72015-05-29 16:24:06 -0700120 private ScheduledExecutorService executor = Executors
121 .newScheduledThreadPool(1);
tom95329eb2014-10-06 08:40:06 -0700122
123 private TopologyListener listener = new InternalTopologyListener();
Sho SHIMIZU0b9c4682015-11-02 18:30:34 -0800124 private ResourceListener resourceListener = new InternalResourceListener();
Brian O'Connorb5dcc512015-03-24 17:28:00 -0700125 private DeviceListener deviceListener = new InternalDeviceListener();
126 private HostListener hostListener = new InternalHostListener();
Madan Jampani1c965102016-01-13 14:34:16 -0800127 private IntentPartitionEventListener partitionListener = new InternalPartitionListener();
tom95329eb2014-10-06 08:40:06 -0700128 private TopologyChangeDelegate delegate;
129
Brian O'Connor69d6ac72015-05-29 16:24:06 -0700130 protected final AtomicBoolean updateScheduled = new AtomicBoolean(false);
131
tom95329eb2014-10-06 08:40:06 -0700132 @Activate
133 public void activate() {
134 topologyService.addListener(listener);
Sho SHIMIZU0b9c4682015-11-02 18:30:34 -0800135 resourceService.addListener(resourceListener);
Brian O'Connorb5dcc512015-03-24 17:28:00 -0700136 deviceService.addListener(deviceListener);
137 hostService.addListener(hostListener);
Brian O'Connor69d6ac72015-05-29 16:24:06 -0700138 partitionService.addListener(partitionListener);
tom95329eb2014-10-06 08:40:06 -0700139 log.info("Started");
140 }
141
142 @Deactivate
143 public void deactivate() {
144 topologyService.removeListener(listener);
Sho SHIMIZU0b9c4682015-11-02 18:30:34 -0800145 resourceService.removeListener(resourceListener);
Brian O'Connorb5dcc512015-03-24 17:28:00 -0700146 deviceService.removeListener(deviceListener);
147 hostService.removeListener(hostListener);
Brian O'Connor69d6ac72015-05-29 16:24:06 -0700148 partitionService.removeListener(partitionListener);
tom95329eb2014-10-06 08:40:06 -0700149 log.info("Stopped");
150 }
151
Brian O'Connor5d55ed42014-12-01 18:27:47 -0800152 protected void bindIntentService(IntentService service) {
153 if (intentService == null) {
154 intentService = service;
155 }
156 }
157
158 protected void unbindIntentService(IntentService service) {
159 if (intentService == service) {
160 intentService = null;
161 }
162 }
163
tom95329eb2014-10-06 08:40:06 -0700164 @Override
165 public void setDelegate(TopologyChangeDelegate delegate) {
166 checkNotNull(delegate, "Delegate cannot be null");
167 checkArgument(this.delegate == null || this.delegate == delegate,
168 "Another delegate already set");
169 this.delegate = delegate;
170 }
171
172 @Override
173 public void unsetDelegate(TopologyChangeDelegate delegate) {
174 checkArgument(this.delegate == delegate, "Not the current delegate");
175 this.delegate = null;
176 }
177
178 @Override
Ray Milkeyf9af43c2015-02-09 16:45:48 -0800179 public void addTrackedResources(Key intentKey,
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700180 Collection<NetworkResource> resources) {
181 for (NetworkResource resource : resources) {
182 if (resource instanceof Link) {
Ray Milkeyf9af43c2015-02-09 16:45:48 -0800183 intentsByLink.put(linkKey((Link) resource), intentKey);
Brian O'Connorb5dcc512015-03-24 17:28:00 -0700184 } else if (resource instanceof ElementId) {
185 intentsByDevice.put((ElementId) resource, intentKey);
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700186 }
tom95329eb2014-10-06 08:40:06 -0700187 }
188 }
189
190 @Override
Ray Milkeyf9af43c2015-02-09 16:45:48 -0800191 public void removeTrackedResources(Key intentKey,
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700192 Collection<NetworkResource> resources) {
193 for (NetworkResource resource : resources) {
194 if (resource instanceof Link) {
Ray Milkeyf9af43c2015-02-09 16:45:48 -0800195 intentsByLink.remove(linkKey((Link) resource), intentKey);
Brian O'Connorb5dcc512015-03-24 17:28:00 -0700196 } else if (resource instanceof ElementId) {
Sho SHIMIZUd82a4e62015-09-09 14:53:46 -0700197 intentsByDevice.remove(resource, intentKey);
Thomas Vachuskab97cf282014-10-20 23:31:12 -0700198 }
tom95329eb2014-10-06 08:40:06 -0700199 }
200 }
201
Thomas Vachuskac46af202015-06-03 16:43:27 -0700202 @Override
203 public void trackIntent(IntentData intentData) {
204
205 //NOTE: This will be called for intents that are being added to the store
206 // locally (i.e. every intent update)
207
208 Key key = intentData.key();
209 Intent intent = intentData.intent();
210 boolean isLocal = intentService.isLocal(key);
Brian O'Connor690fd1c2015-06-04 19:50:33 -0700211 boolean isInstalled = intentData.state() == INSTALLING ||
212 intentData.state() == INSTALLED;
Thomas Vachuskac46af202015-06-03 16:43:27 -0700213 List<Intent> installables = intentData.installables();
214
215 if (log.isTraceEnabled()) {
216 log.trace("intent {}, old: {}, new: {}, installableCount: {}, resourceCount: {}",
217 key,
218 intentsByDevice.values().contains(key),
Brian O'Connor690fd1c2015-06-04 19:50:33 -0700219 isLocal && isInstalled,
Thomas Vachuskac46af202015-06-03 16:43:27 -0700220 installables.size(),
221 intent.resources().size() +
222 installables.stream()
223 .mapToLong(i -> i.resources().size()).sum());
224 }
225
226 if (isNullOrEmpty(installables) && intentData.state() == INSTALLED) {
227 log.warn("Intent {} is INSTALLED with no installables", key);
228 }
229
Brian O'Connor690fd1c2015-06-04 19:50:33 -0700230 // FIXME Intents will be added 3 times (once directly using addTracked,
231 // then when installing and when installed)
232 if (isLocal && isInstalled) {
Thomas Vachuskac46af202015-06-03 16:43:27 -0700233 addTrackedResources(key, intent.resources());
234 for (Intent installable : installables) {
235 addTrackedResources(key, installable.resources());
236 }
237 // FIXME check all resources against current topo service(s); recompile if necessary
238 } else {
239 removeTrackedResources(key, intent.resources());
240 for (Intent installable : installables) {
241 removeTrackedResources(key, installable.resources());
242 }
243 }
244 }
245
tom95329eb2014-10-06 08:40:06 -0700246 // Internal re-actor to topology change events.
247 private class InternalTopologyListener implements TopologyListener {
248 @Override
249 public void event(TopologyEvent event) {
250 executorService.execute(new TopologyChangeHandler(event));
251 }
252 }
253
254 // Re-dispatcher of topology change events.
255 private class TopologyChangeHandler implements Runnable {
256
257 private final TopologyEvent event;
258
259 TopologyChangeHandler(TopologyEvent event) {
260 this.event = event;
261 }
262
263 @Override
264 public void run() {
Thomas Vachuska7b652ad2014-10-30 14:10:51 -0700265 // If there is no delegate, why bother? Just bail.
266 if (delegate == null) {
267 return;
268 }
269
Ray Milkey7c44c052014-12-05 10:34:54 -0800270 if (event.reasons() == null || event.reasons().isEmpty()) {
Brian O'Connorb5dcc512015-03-24 17:28:00 -0700271 delegate.triggerCompile(Collections.emptySet(), true);
tom85258ee2014-10-07 00:10:02 -0700272
tom95329eb2014-10-06 08:40:06 -0700273 } else {
Jonathan Hart96c5a4a2015-07-31 14:23:33 -0700274 Set<Key> intentsToRecompile = new HashSet<>();
275 boolean dontRecompileAllFailedIntents = true;
tom85258ee2014-10-07 00:10:02 -0700276
277 // Scan through the list of reasons and keep accruing all
278 // intents that need to be recompiled.
tom95329eb2014-10-06 08:40:06 -0700279 for (Event reason : event.reasons()) {
280 if (reason instanceof LinkEvent) {
281 LinkEvent linkEvent = (LinkEvent) reason;
Jonathan Hart96c5a4a2015-07-31 14:23:33 -0700282 final LinkKey linkKey = linkKey(linkEvent.subject());
283 synchronized (intentsByLink) {
284 Set<Key> intentKeys = intentsByLink.get(linkKey);
285 log.debug("recompile triggered by LinkEvent {} ({}) for {}",
286 linkKey, linkEvent.type(), intentKeys);
287 intentsToRecompile.addAll(intentKeys);
tom85258ee2014-10-07 00:10:02 -0700288 }
Jonathan Hart96c5a4a2015-07-31 14:23:33 -0700289 dontRecompileAllFailedIntents = dontRecompileAllFailedIntents &&
Praseed Balakrishnan00dd1f92014-11-19 17:12:36 -0800290 (linkEvent.type() == LINK_REMOVED ||
291 (linkEvent.type() == LINK_UPDATED &&
292 linkEvent.subject().isDurable()));
tom95329eb2014-10-06 08:40:06 -0700293 }
294 }
Jonathan Hart96c5a4a2015-07-31 14:23:33 -0700295 delegate.triggerCompile(intentsToRecompile, !dontRecompileAllFailedIntents);
tom95329eb2014-10-06 08:40:06 -0700296 }
297 }
298 }
299
Sho SHIMIZU0b9c4682015-11-02 18:30:34 -0800300 private class InternalResourceListener implements ResourceListener {
Ray Milkeye97ede92014-11-20 10:43:12 -0800301 @Override
Sho SHIMIZU0b9c4682015-11-02 18:30:34 -0800302 public void event(ResourceEvent event) {
Sho SHIMIZUb08d5862016-02-11 12:37:28 -0800303 if (event.subject().isSubTypeOf(PortNumber.class)) {
Sho SHIMIZU0b9c4682015-11-02 18:30:34 -0800304 executorService.execute(() -> {
305 if (delegate == null) {
306 return;
307 }
Ray Milkeye97ede92014-11-20 10:43:12 -0800308
Sho SHIMIZU0b9c4682015-11-02 18:30:34 -0800309 delegate.triggerCompile(Collections.emptySet(), true);
310 });
Ray Milkeye97ede92014-11-20 10:43:12 -0800311 }
Ray Milkeye97ede92014-11-20 10:43:12 -0800312 }
313 }
314
Brian O'Connor72a034c2014-11-26 18:24:23 -0800315 //TODO consider adding flow rule event tracking
Ray Milkeye97ede92014-11-20 10:43:12 -0800316
Brian O'Connorb5dcc512015-03-24 17:28:00 -0700317 /*
318 * Re-dispatcher of device and host events.
319 */
320 private class DeviceAvailabilityHandler implements Runnable {
321
322 private final ElementId id;
323 private final boolean available;
324
325 DeviceAvailabilityHandler(ElementId id, boolean available) {
326 this.id = checkNotNull(id);
327 this.available = available;
328 }
329
330 @Override
331 public void run() {
332 // If there is no delegate, why bother? Just bail.
333 if (delegate == null) {
334 return;
335 }
336
337 // TODO should we recompile on available==true?
HIGUCHI Yuta99b7b342015-09-29 16:54:21 -0700338
339 final ImmutableSet<Key> snapshot;
340 synchronized (intentsByDevice) {
341 snapshot = ImmutableSet.copyOf(intentsByDevice.get(id));
342 }
343 delegate.triggerCompile(snapshot, available);
Brian O'Connorb5dcc512015-03-24 17:28:00 -0700344 }
345 }
346
347
348 private class InternalDeviceListener implements DeviceListener {
349 @Override
350 public void event(DeviceEvent event) {
351 DeviceEvent.Type type = event.type();
Jonathan Hart94470fe2015-07-31 11:41:10 -0700352 switch (type) {
353 case DEVICE_ADDED:
354 case DEVICE_AVAILABILITY_CHANGED:
355 case DEVICE_REMOVED:
356 case DEVICE_SUSPENDED:
357 case DEVICE_UPDATED:
358 DeviceId id = event.subject().id();
359 // TODO we need to check whether AVAILABILITY_CHANGED means up or down
360 boolean available = (type == DeviceEvent.Type.DEVICE_AVAILABILITY_CHANGED ||
361 type == DeviceEvent.Type.DEVICE_ADDED ||
362 type == DeviceEvent.Type.DEVICE_UPDATED);
363 executorService.execute(new DeviceAvailabilityHandler(id, available));
364 break;
365 case PORT_ADDED:
366 case PORT_REMOVED:
367 case PORT_UPDATED:
368 case PORT_STATS_UPDATED:
369 default:
370 // Don't handle port events for now
371 break;
Brian O'Connorb5dcc512015-03-24 17:28:00 -0700372 }
Brian O'Connorb5dcc512015-03-24 17:28:00 -0700373 }
374 }
375
376 private class InternalHostListener implements HostListener {
377 @Override
378 public void event(HostEvent event) {
379 HostId id = event.subject().id();
Brian O'Connora9c18b92015-09-28 16:03:43 -0700380 switch (event.type()) {
381 case HOST_ADDED:
382 case HOST_MOVED:
383 case HOST_REMOVED:
384 executorService.execute(new DeviceAvailabilityHandler(id, false));
385 break;
386 case HOST_UPDATED:
387 default:
388 // DO NOTHING
389 break;
390 }
Brian O'Connorb5dcc512015-03-24 17:28:00 -0700391 }
392 }
Brian O'Connor69d6ac72015-05-29 16:24:06 -0700393
Sho SHIMIZU97a93dd2016-03-30 18:07:51 -0700394 private void doIntentUpdate() {
Brian O'Connor69d6ac72015-05-29 16:24:06 -0700395 updateScheduled.set(false);
396 if (intentService == null) {
397 log.warn("Intent service is not bound yet");
398 return;
399 }
400 try {
401 //FIXME very inefficient
Thomas Vachuskac46af202015-06-03 16:43:27 -0700402 for (IntentData intentData : intentService.getIntentData()) {
Brian O'Connor69d6ac72015-05-29 16:24:06 -0700403 try {
Thomas Vachuskac46af202015-06-03 16:43:27 -0700404 trackIntent(intentData);
Brian O'Connor69d6ac72015-05-29 16:24:06 -0700405 } catch (NullPointerException npe) {
Thomas Vachuskac46af202015-06-03 16:43:27 -0700406 log.warn("intent error {}", intentData.key(), npe);
Brian O'Connor69d6ac72015-05-29 16:24:06 -0700407 }
408 }
409 } catch (Exception e) {
410 log.warn("Exception caught during update task", e);
411 }
412 }
413
414 private void scheduleIntentUpdate(int afterDelaySec) {
415 if (updateScheduled.compareAndSet(false, true)) {
416 executor.schedule(this::doIntentUpdate, afterDelaySec, TimeUnit.SECONDS);
417 }
418 }
419
Madan Jampani1c965102016-01-13 14:34:16 -0800420 private final class InternalPartitionListener implements IntentPartitionEventListener {
Brian O'Connor69d6ac72015-05-29 16:24:06 -0700421 @Override
Madan Jampani1c965102016-01-13 14:34:16 -0800422 public void event(IntentPartitionEvent event) {
Brian O'Connor3057f212015-05-29 18:22:18 -0700423 log.debug("got message {}", event.subject());
Brian O'Connor69d6ac72015-05-29 16:24:06 -0700424 scheduleIntentUpdate(1);
425 }
426 }
tom95329eb2014-10-06 08:40:06 -0700427}