Merge pull request #655 from chinmaygarde/master

Roll back dart dependency to work around build errors
diff --git a/examples/demo_launcher/apk/AndroidManifest.xml b/examples/demo_launcher/apk/AndroidManifest.xml
index c7259b0..10151ea 100644
--- a/examples/demo_launcher/apk/AndroidManifest.xml
+++ b/examples/demo_launcher/apk/AndroidManifest.xml
@@ -3,7 +3,7 @@
      Use of this source code is governed by a BSD-style license that can be
      found in the LICENSE file.
  -->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="org.domokit.sky.demo" android:versionCode="22" android:versionName="0.0.22">
+<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="org.domokit.sky.demo" android:versionCode="23" android:versionName="0.0.23">
 
     <uses-sdk android:minSdkVersion="14" android:targetSdkVersion="21" />
     <uses-permission android:name="android.permission.INTERNET" />
diff --git a/examples/fitness/apk/AndroidManifest.xml b/examples/fitness/apk/AndroidManifest.xml
index 9147c35..a910e58 100644
--- a/examples/fitness/apk/AndroidManifest.xml
+++ b/examples/fitness/apk/AndroidManifest.xml
@@ -3,7 +3,7 @@
      Use of this source code is governed by a BSD-style license that can be
      found in the LICENSE file.
  -->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="org.domokit.fitness" android:versionCode="2" android:versionName="0.0.2">
+<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="org.domokit.fitness" android:versionCode="3" android:versionName="0.0.3">
 
     <uses-sdk android:minSdkVersion="14" android:targetSdkVersion="21" />
     <uses-permission android:name="android.permission.INTERNET" />
diff --git a/examples/game/lib/action.dart b/examples/game/lib/action.dart
index 733281a..ceabdad 100644
--- a/examples/game/lib/action.dart
+++ b/examples/game/lib/action.dart
@@ -295,6 +295,13 @@
   }
 }
 
+/// An action that doesn't perform any other task than taking time. This action
+/// is typically used in a sequence to space out other events.
+class ActionDelay extends ActionInterval {
+  /// Creates a new action with the specified [delay]
+  ActionDelay(double delay) : super(delay);
+}
+
 /// An action that doesn't have a duration. If this class is overridden to
 /// create custom instant actions, only the [fire] method should be overriden.
 abstract class ActionInstant extends Action {
diff --git a/examples/game/lib/sound.dart b/examples/game/lib/sound.dart
index e502f6b..5e50e7f 100644
--- a/examples/game/lib/sound.dart
+++ b/examples/game/lib/sound.dart
@@ -149,6 +149,7 @@
   SoundTrackBufferingCallback onBufferingUpdate;
   bool loop;
   double time;
+  double volume;
 }
 
 SoundTrackPlayer _sharedSoundTrackPlayer;
diff --git a/examples/game/lib/sound_manager.dart b/examples/game/lib/sound_manager.dart
new file mode 100644
index 0000000..90c03a9
--- /dev/null
+++ b/examples/game/lib/sound_manager.dart
@@ -0,0 +1,238 @@
+part of sprites;
+
+enum SoundFadeMode {
+  crossFade,
+  fadeOutThenPlay,
+  fadeOutThenFadeIn,
+}
+
+enum SoundEventSimultaneousPolicy {
+  dontPlay,
+  stopOldest,
+}
+
+enum SoundEventMinimumOverlapPolicy {
+  dontPlay,
+  delay,
+}
+
+class SoundEvent {
+  SoundEvent(SoundEffect effect) {
+    effects = [effect];
+  }
+
+  SoundEvent.withList(this.effects);
+
+  List<SoundEffect> effects;
+  double pitchVariance = 0.0;
+  double volumeVariance = 0.0;
+  double panVariance = 0.0;
+
+  SoundEventSimultaneousPolicy simultaneousLimitPolicy = SoundEventSimultaneousPolicy.stopOldest;
+  int simultaneousLimit = 0;
+
+  SoundEventMinimumOverlapPolicy minimumOverlapPolicy = SoundEventMinimumOverlapPolicy.dontPlay;
+  double minimumOverlap = 0.0;
+}
+
+class _PlayingSoundEvent {
+  SoundEvent event;
+  SoundEffectStream stream;
+  int startTime;
+}
+
+SoundManager _sharedSoundManager;
+
+class SoundManager {
+
+  static SoundManager sharedInstance() {
+    if (_sharedSoundManager == null) {
+      _sharedSoundManager = new SoundManager();
+    }
+    return _sharedSoundManager;
+  }
+
+  static void purgeSharedInstance() {
+    if (_sharedSoundManager == null) return;
+    _sharedSoundManager._running = false;
+    _sharedSoundManager = null;
+  }
+
+  SoundManager() {
+    new Timer.periodic(new Duration(milliseconds:10), _update);
+  }
+
+  Map<SoundEvent, List<_PlayingSoundEvent>> _playingEvents = {};
+  SoundTrack _backgroundMusicTrack;
+
+  SoundEffectPlayer _effectPlayer = SoundEffectPlayer.sharedInstance();
+  SoundTrackPlayer _trackPlayer = SoundTrackPlayer.sharedInstance();
+  ActionController actions = new ActionController();
+
+  bool enableBackgroundMusic;
+  bool enableSoundEffects;
+
+  bool _running = true;
+  int _lastTimeStamp;
+
+  void playEvent(SoundEvent evt, [double volume = 1.0, double pitch = 1.0, double pan = 0.0]) {
+    List<_PlayingSoundEvent> playingList = _playingEvents[evt];
+    if (playingList == null) playingList = [];
+
+    // Check simultaneousLimit
+    if (evt.simultaneousLimit != 0 && evt.simultaneousLimit >= playingList.length) {
+      // We have too many sounds playing
+      if (evt.simultaneousLimitPolicy == SoundEventSimultaneousPolicy.dontPlay) {
+        // Skip this sound event
+        return;
+      } else {
+        // Stop the oldest sound
+        _effectPlayer.stop(playingList[0].stream);
+      }
+    }
+
+    // Check for overlap
+    int playTime = new DateTime.now().millisecondsSinceEpoch;
+
+    if (evt.minimumOverlap != 0.0 && playingList.length > 0) {
+      int overlap = playTime - playingList.last.startTime;
+      if (overlap.toDouble() / 1000.0 < evt.minimumOverlap) {
+        // Sounds are overlapping
+        if (evt.minimumOverlapPolicy == SoundEventMinimumOverlapPolicy.dontPlay) {
+          return;
+        } else {
+          // TODO: try to play the sound a little bit later
+          return;
+        }
+      }
+    }
+
+    // Create a new entry for the event
+    _PlayingSoundEvent newPlaying = new _PlayingSoundEvent();
+    newPlaying.startTime = playTime;
+    newPlaying.event = evt;
+
+    // Pick a sound effect to play
+    SoundEffect effect = evt.effects.elementAt(randomInt(evt.effects.length));
+
+    // Add the entry
+    playingList.add(newPlaying);
+
+    // Play the event
+    newPlaying.stream = _effectPlayer.play(
+      effect,
+      false,
+      (volume + evt.volumeVariance * randomSignedDouble()).clamp(0.0, 2.0),
+      (pitch + evt.pitchVariance * randomSignedDouble()).clamp(0.5, 2.0),
+      (pan + evt.panVariance * randomSignedDouble()).clamp(-1.0, 1.0),
+      (SoundEffectStream s) {
+        // Completion callback - remove the entry
+        playingList.remove(newPlaying);
+      }
+    );
+  }
+
+  void stopAllEvents([double fadeDuration]) {
+    for (List<_PlayingSoundEvent> playingList in _playingEvents) {
+      for (_PlayingSoundEvent playing in playingList) {
+        if (fadeDuration > 0.0) {
+          // Fade out and stop
+          ActionTween fadeOut = new ActionTween((a) => playing.stream.volume = a, playing.stream.volume, 0.0, fadeDuration);
+          ActionCallFunction stop = new ActionCallFunction(() { _effectPlayer.stop(playing.stream); });
+          ActionSequence seq = new ActionSequence([fadeOut, stop]);
+          actions.run(seq);
+        }
+        else {
+          // Stop right away
+          _effectPlayer.stop(playing.stream);
+        }
+      }
+    }
+  }
+
+  void playBackgroundMusic(SoundTrack track, [double fadeDuration = 0.0, SoundFadeMode fadeMode = SoundFadeMode.fadeOutThenPlay]) {
+    double fadeInDuration = 0.0;
+    double fadeInDelay = 0.0;
+    double fadeOutDuration = 0.0;
+
+    // Calculate durations
+    if (fadeDuration > 0.0) {
+      if (fadeMode == SoundFadeMode.crossFade) {
+        fadeOutDuration = fadeDuration;
+        fadeInDuration = fadeDuration;
+      } else if (fadeMode == SoundFadeMode.fadeOutThenPlay) {
+        fadeOutDuration = fadeDuration;
+        fadeInDelay = fadeDuration;
+      } else if (fadeMode == SoundFadeMode.fadeOutThenFadeIn) {
+        fadeOutDuration = fadeDuration / 2.0;
+        fadeInDuration = fadeDuration / 2.0;
+        fadeInDelay = fadeDuration / 2.0;
+      }
+    }
+
+    if (_backgroundMusicTrack != null) {
+      // Stop the current track
+      if (fadeOutDuration == 0.0) {
+        _trackPlayer.stop(_backgroundMusicTrack);
+      } else {
+        ActionTween fadeOut = new ActionTween((a) => _backgroundMusicTrack.volume = a, _backgroundMusicTrack.volume, 0.0, fadeOutDuration);
+        ActionCallFunction stop = new ActionCallFunction(() { _trackPlayer.stop(_backgroundMusicTrack); });
+        ActionSequence seq = new ActionSequence([fadeOut, stop]);
+        actions.run(seq);
+      }
+    } else {
+      fadeInDelay = 0.0;
+    }
+
+    // Fade in new sound
+    if (fadeInDelay == 0.0) {
+      _fadeInTrack(track, fadeInDuration);
+    } else {
+      ActionDelay delay = new ActionDelay(fadeInDelay);
+      ActionCallFunction fadeInCall = new ActionCallFunction(() {
+        _fadeInTrack(track, fadeInDuration);
+      });
+      ActionSequence seq = new ActionSequence([delay, fadeInCall]);
+      actions.run(seq);
+    }
+  }
+
+  void _fadeInTrack(SoundTrack track, double duration) {
+    _backgroundMusicTrack = track;
+
+    if (duration == 0.0) {
+      _trackPlayer.play(track);
+    } else {
+      _trackPlayer.play(track, true, 0.0);
+      actions.run(new ActionTween((a) => track.volume = a, 0.0, 1.0, duration));
+    }
+  }
+
+  void stopBackgroundMusic([double fadeDuration = 0.0]) {
+    if (fadeDuration == 0.0) {
+      _trackPlayer.stop(_backgroundMusicTrack);
+    } else {
+      ActionTween fadeOut = new ActionTween(
+        (a) => _backgroundMusicTrack.volume = a,
+        _backgroundMusicTrack.volume, 0.0, fadeDuration);
+      ActionCallFunction stopCall = new ActionCallFunction(() {
+        _trackPlayer.stop(_backgroundMusicTrack);
+      });
+      ActionSequence seq = new ActionSequence([fadeOut, stopCall]);
+      actions.run(seq);
+    }
+
+    _backgroundMusicTrack = null;
+  }
+
+  void _update(Timer timer) {
+    int delta = 0;
+    int timestamp = new DateTime.now().millisecondsSinceEpoch;
+    if (_lastTimeStamp != null) {
+      delta = timestamp - _lastTimeStamp;
+    }
+    _lastTimeStamp = timestamp;
+
+    actions.step(delta / 1000.0);
+  }
+}
diff --git a/examples/game/lib/sprites.dart b/examples/game/lib/sprites.dart
index 155dcc2..6522290 100644
--- a/examples/game/lib/sprites.dart
+++ b/examples/game/lib/sprites.dart
@@ -31,6 +31,7 @@
 part 'node_with_size.dart';
 part 'particle_system.dart';
 part 'sound.dart';
+part 'sound_manager.dart';
 part 'sprite.dart';
 part 'spritesheet.dart';
 part 'sprite_box.dart';
diff --git a/sky/packages/material_design_icons/README.md b/sky/packages/material_design_icons/README.md
index c582b93..84053f7 100644
--- a/sky/packages/material_design_icons/README.md
+++ b/sky/packages/material_design_icons/README.md
@@ -4,6 +4,21 @@
 A set of icons designed under the
 [material design guidelines](http://www.google.com/design/spec).
 
+What do they look like?
+----------------------
+
+You can view all the icons online at
+[www.google.com/design/icons](https://www.google.com/design/icons/).
+
+Website
+--------
+
+The source for the icons can be found in the
+[material-design-icons](https://github.com/google/material-design-icons) repo.
+This Pub package mirrors the icons; the source for the package can be found in the
+[sky_engine](https://github.com/domokit/sky_engine) repo in the
+`sky/packages/material_design_icons` directory.
+
 License
 -------
 
diff --git a/sky/packages/material_design_icons/pubspec.yaml b/sky/packages/material_design_icons/pubspec.yaml
index a32c6b9..89681db 100644
--- a/sky/packages/material_design_icons/pubspec.yaml
+++ b/sky/packages/material_design_icons/pubspec.yaml
@@ -2,4 +2,4 @@
 version: 0.0.2
 author: Chromium Authors <sky-dev@googlegroups.com>
 description: Material Design Icons
-homepage: https://github.com/google/material-design-icons
+homepage: https://github.com/domokit/sky_engine
diff --git a/sky/packages/sky/lib/mojo/README.md b/sky/packages/sky/lib/mojo/README.md
new file mode 100644
index 0000000..80683fa
--- /dev/null
+++ b/sky/packages/sky/lib/mojo/README.md
@@ -0,0 +1,7 @@
+This directory contains code for exposing mojo services to Sky apps.
+For example, keyboard.dart wraps the mojo keyboard service in a more
+convenient Dart class.
+
+Files in this directory (and its subdirectories) only depend on core
+Dart libraries, `dart:sky`, `dart:sky.internals`, the 'mojo' package,
+the 'mojo_services' package, and `../base/*`.
diff --git a/sky/packages/sky/lib/theme/README.md b/sky/packages/sky/lib/theme/README.md
new file mode 100644
index 0000000..ba58170
--- /dev/null
+++ b/sky/packages/sky/lib/theme/README.md
@@ -0,0 +1,5 @@
+This directory provides constants for Material Design.
+
+   http://www.google.com/design/spec/material-design/
+
+Files in this directory only depend on ../painting/* and sky:dart.
diff --git a/sky/packages/sky/lib/theme/edges.dart b/sky/packages/sky/lib/theme/edges.dart
index 4da5d9b..fad0e0f 100644
--- a/sky/packages/sky/lib/theme/edges.dart
+++ b/sky/packages/sky/lib/theme/edges.dart
@@ -4,6 +4,7 @@
 
 enum MaterialEdge { canvas, card, circle }
 
+// This map gives the border radii for each type of material.
 const Map<MaterialEdge, double> edges = const {
   MaterialEdge.canvas: null,
   MaterialEdge.card: 2.0,
diff --git a/sky/tools/roll_apk_version.py b/sky/tools/roll_apk_version.py
new file mode 100755
index 0000000..4f3c21b
--- /dev/null
+++ b/sky/tools/roll_apk_version.py
@@ -0,0 +1,71 @@
+#!/usr/bin/env python
+# Copyright 2015 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import argparse
+import os
+import subprocess
+import xml.etree.ElementTree as ET
+
+
+MANIFEST_PREFACE = '''<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2015 The Chromium Authors. All rights reserved.
+     Use of this source code is governed by a BSD-style license that can be
+     found in the LICENSE file.
+ -->
+'''
+
+
+def increment_version(version):
+    pieces = version.split('.')
+    pieces[-1] = str(int(pieces[-1]) + 1)
+    return '.'.join(pieces)
+
+
+def prepend_to_file(to_prepend, filepath):
+    with open(filepath, 'r+') as f:
+        content = f.read()
+        f.seek(0, 0)
+        f.write(to_prepend + content)
+
+
+def update_manifest(manifest):
+    VERSION_CODE = '{http://schemas.android.com/apk/res/android}versionCode'
+    VERSION_NAME = '{http://schemas.android.com/apk/res/android}versionName'
+    tree = ET.parse(manifest)
+    root = tree.getroot()
+    package_name = root.get('package')
+    old_code = root.get(VERSION_CODE)
+    old_name = root.get(VERSION_NAME)
+    root.set(VERSION_CODE, increment_version(old_code))
+    root.set(VERSION_NAME, increment_version(old_name))
+    print "%20s  %6s (%s) => %6s (%s)" % (package_name, old_name, old_code,
+        root.get(VERSION_NAME), root.get(VERSION_CODE))
+    # TODO(eseidel): This isn't smart enough to wrap/intent multi-attribute
+    # elements like <manifest> as is the typical AndroidManifiest.xml style
+    # we could write our own custom prettyprinter to do that?
+    tree.write(manifest)
+    prepend_to_file(MANIFEST_PREFACE, manifest)
+    return root.get(VERSION_NAME)
+
+
+def main():
+    # Should chdir to the root directory.
+    parser = argparse.ArgumentParser()
+    parser.add_argument('manifest')
+    args = parser.parse_args()
+
+    # TODO(eseidel): Without this ET uses 'ns0' for 'android' which is wrong.
+    ET.register_namespace('android', 'http://schemas.android.com/apk/res/android')
+
+    new_version = update_manifest(args.manifest)
+    notes_dir = os.path.join(os.path.dirname(args.manifest), 'release_notes')
+    release_notes = os.path.join(notes_dir, '%s.txt' % new_version)
+    # FIXME: We could open an editor for the release notes and prepopulate
+    # it with the changes url like how we do for pubspec CHANGELOG.md files.
+    print "Please update %s in this commit." % release_notes
+
+
+if __name__ == '__main__':
+    main()
diff --git a/sky/tools/roll_pub_versions.py b/sky/tools/roll_pub_versions.py
new file mode 100755
index 0000000..708938b
--- /dev/null
+++ b/sky/tools/roll_pub_versions.py
@@ -0,0 +1,83 @@
+#!/usr/bin/env python
+# Copyright 2015 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import os
+import subprocess
+import yaml
+
+
+PUBSPECS = [
+    'sky/packages/sky/pubspec.yaml',
+    'sky/packages/sky_engine/pubspec.yaml',
+    'sky/packages/sky_services/pubspec.yaml',
+]
+
+def increment_version(version):
+    pieces = version.split('.')
+    pieces[-1] = str(int(pieces[-1]) + 1)
+    return '.'.join(pieces)
+
+
+def sort_dict(unsorted):
+    sorted_dict = collections.OrderedDict()
+    for key in sorted(unsorted.keys()):
+        sorted_dict[key] = unsorted[key]
+    return sorted_dict
+
+
+def count_commits(start, end):
+    return subprocess.check_output([
+        'git', 'rev-list', '%s...%s' % (start, end)]).count('\n')
+
+
+def last_commit_to(file_path):
+    return subprocess.check_output(['git', 'log', '-1', '--format=%h', file_path]).strip()
+
+
+def update_pubspec(pubspec):
+    # TODO(eseidel): This does not prserve any manual sort-order of the yaml.
+    with open(pubspec, 'r') as stream:
+        spec = yaml.load(stream)
+        old_version = spec['version']
+        spec['version'] = increment_version(old_version)
+        print "%20s  %6s => %6s" % (spec['name'], old_version, spec['version'])
+
+    with open(pubspec, 'w') as stream:
+        yaml.dump(spec, stream=stream, default_flow_style=False)
+    return spec['version']
+
+
+def update_changelog(changelog, pubspec, version):
+    old = last_commit_to(pubspec)
+    new = last_commit_to('.')
+    url = "https://github.com/domokit/sky_engine/compare/%s...%s" % (old, new)
+    count = count_commits(old, new)
+    message = """## %s
+
+  - %s changes: %s
+
+""" % (version, count, url)
+    prepend_to_file(message, changelog)
+
+
+def prepend_to_file(to_prepend, filepath):
+    with open(filepath, 'r+') as f:
+        content = f.read()
+        f.seek(0, 0)
+        f.write(to_prepend + content)
+
+
+def main():
+    # Should chdir to the root directory.
+
+    print 'Pub packages:'
+    for pubspec in PUBSPECS:
+        new_version = update_pubspec(pubspec)
+        changelog = os.path.join(os.path.dirname(pubspec), 'CHANGELOG.md')
+        update_changelog(changelog, pubspec, new_version)
+
+
+if __name__ == '__main__':
+    main()
diff --git a/sky/tools/roll_versions.py b/sky/tools/roll_versions.py
deleted file mode 100755
index ccd15e5..0000000
--- a/sky/tools/roll_versions.py
+++ /dev/null
@@ -1,122 +0,0 @@
-#!/usr/bin/env python
-# Copyright 2015 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import os
-import subprocess
-import yaml
-import xml.etree.ElementTree as ET
-
-
-PUBSPECS = [
-    'sky/packages/sky/pubspec.yaml',
-    'sky/packages/sky_engine/pubspec.yaml',
-    'sky/packages/sky_services/pubspec.yaml',
-]
-
-MANIFESTS = [
-    'examples/demo_launcher/apk/AndroidManifest.xml',
-]
-
-MANIFEST_PREFACE = '''<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright 2015 The Chromium Authors. All rights reserved.
-     Use of this source code is governed by a BSD-style license that can be
-     found in the LICENSE file.
- -->
-'''
-
-
-def increment_version(version):
-    pieces = version.split('.')
-    pieces[-1] = str(int(pieces[-1]) + 1)
-    return '.'.join(pieces)
-
-
-def sort_dict(unsorted):
-    sorted_dict = collections.OrderedDict()
-    for key in sorted(unsorted.keys()):
-        sorted_dict[key] = unsorted[key]
-    return sorted_dict
-
-
-def count_commits(start, end):
-    return subprocess.check_output([
-        'git', 'rev-list', '%s...%s' % (start, end)]).count('\n')
-
-
-def last_commit_to(file_path):
-    return subprocess.check_output(['git', 'log', '-1', '--format=%h', file_path]).strip()
-
-
-def update_pubspec(pubspec):
-    # TODO(eseidel): This does not prserve any manual sort-order of the yaml.
-    with open(pubspec, 'r') as stream:
-        spec = yaml.load(stream)
-        old_version = spec['version']
-        spec['version'] = increment_version(old_version)
-        print "%20s  %6s => %6s" % (spec['name'], old_version, spec['version'])
-
-    with open(pubspec, 'w') as stream:
-        yaml.dump(spec, stream=stream, default_flow_style=False)
-    return spec['version']
-
-
-def update_changelog(changelog, pubspec, version):
-    old = last_commit_to(pubspec)
-    new = last_commit_to('.')
-    url = "https://github.com/domokit/mojo/compare/%s...%s" % (old, new)
-    count = count_commits(old, new)
-    message = """## %s
-
-  - %s changes: %s
-
-""" % (version, count, url)
-    prepend_to_file(message, changelog)
-
-
-def prepend_to_file(to_prepend, filepath):
-    with open(filepath, 'r+') as f:
-        content = f.read()
-        f.seek(0, 0)
-        f.write(to_prepend + content)
-
-
-def update_manifest(manifest):
-    VERSION_CODE = '{http://schemas.android.com/apk/res/android}versionCode'
-    VERSION_NAME = '{http://schemas.android.com/apk/res/android}versionName'
-    tree = ET.parse(manifest)
-    root = tree.getroot()
-    package_name = root.get('package')
-    old_code = root.get(VERSION_CODE)
-    old_name = root.get(VERSION_NAME)
-    root.set(VERSION_CODE, increment_version(old_code))
-    root.set(VERSION_NAME, increment_version(old_name))
-    print "%20s  %6s (%s) => %6s (%s)" % (package_name, old_name, old_code,
-        root.get(VERSION_NAME), root.get(VERSION_CODE))
-    # TODO(eseidel): This isn't smart enough to wrap/intent multi-attribute
-    # elements like <manifest> as is the typical AndroidManifiest.xml style
-    # we could write our own custom prettyprinter to do that?
-    tree.write(manifest)
-    prepend_to_file(MANIFEST_PREFACE, manifest)
-
-
-def main():
-    # Should chdir to the root directory.
-
-    print 'Pub packages:'
-    for pubspec in PUBSPECS:
-        new_version = update_pubspec(pubspec)
-        changelog = os.path.join(os.path.dirname(pubspec), 'CHANGELOG.md')
-        update_changelog(changelog, pubspec, new_version)
-
-    # TODO(eseidel): Without this ET uses 'ns0' for 'android' which is wrong.
-    ET.register_namespace('android', 'http://schemas.android.com/apk/res/android')
-
-    print 'APKs:'
-    for manifest in MANIFESTS:
-        update_manifest(manifest)
-
-
-if __name__ == '__main__':
-    main()