Use MultiFrameImageStreamProvider in the various image providers. (#12997)


diff --git a/dev/manual_tests/test/card_collection_test.dart b/dev/manual_tests/test/card_collection_test.dart
index 93cb397..428e30e 100644
--- a/dev/manual_tests/test/card_collection_test.dart
+++ b/dev/manual_tests/test/card_collection_test.dart
@@ -3,12 +3,15 @@
 // found in the LICENSE file.
 
 import 'package:flutter/material.dart';
+import 'package:flutter/services.dart' show createHttpClient;
 import 'package:flutter_test/flutter_test.dart';
 
 import '../lib/card_collection.dart' as card_collection;
+import 'mock_image_http.dart';
 
 void main() {
   testWidgets('Card Collection smoke test', (WidgetTester tester) async {
+    createHttpClient = createMockImageHttpClient;
     card_collection.main(); // builds the app and schedules a frame but doesn't trigger one
     await tester.pump(); // see https://github.com/flutter/flutter/issues/1865
     await tester.pump(); // triggers a frame
diff --git a/dev/manual_tests/test/color_testing_demo_test.dart b/dev/manual_tests/test/color_testing_demo_test.dart
index c00f64c..1d6b2d4 100644
--- a/dev/manual_tests/test/color_testing_demo_test.dart
+++ b/dev/manual_tests/test/color_testing_demo_test.dart
@@ -3,12 +3,16 @@
 // found in the LICENSE file.
 
 import 'package:flutter/material.dart';
+import 'package:flutter/services.dart' show createHttpClient;
 import 'package:flutter_test/flutter_test.dart';
 
 import '../lib/color_testing_demo.dart' as color_testing_demo;
+import 'mock_image_http.dart';
 
 void main() {
+
   testWidgets('Color testing demo smoke test', (WidgetTester tester) async {
+    createHttpClient = createMockImageHttpClient;
     color_testing_demo.main(); // builds the app and schedules a frame but doesn't trigger one
     await tester.pump(); // see https://github.com/flutter/flutter/issues/1865
     await tester.pump(); // triggers a frame
diff --git a/dev/manual_tests/test/mock_image_http.dart b/dev/manual_tests/test/mock_image_http.dart
new file mode 100644
index 0000000..b491bd9
--- /dev/null
+++ b/dev/manual_tests/test/mock_image_http.dart
@@ -0,0 +1,15 @@
+import 'dart:async';
+import 'package:flutter/foundation.dart' show ValueGetter;
+import 'package:http/http.dart' as http;
+import 'package:http/testing.dart' as http;
+
+import '../../../packages/flutter/test/services/image_data.dart';
+
+// Returns a mock HTTP client that responds with an image to all requests.
+ValueGetter<http.Client> createMockImageHttpClient = () {
+  return new http.MockClient((http.BaseRequest request) {
+    return new Future<http.Response>.value(
+      new http.Response.bytes(kTransparentImage, 200, request: request)
+    );
+  });
+};
diff --git a/packages/flutter/lib/src/services/image_provider.dart b/packages/flutter/lib/src/services/image_provider.dart
index e92719a..fa70780 100644
--- a/packages/flutter/lib/src/services/image_provider.dart
+++ b/packages/flutter/lib/src/services/image_provider.dart
@@ -5,7 +5,7 @@
 import 'dart:async';
 import 'dart:io' show File;
 import 'dart:typed_data';
-import 'dart:ui' as ui show Image;
+import 'dart:ui' as ui show instantiateImageCodec, Codec;
 import 'dart:ui' show Size, Locale, TextDirection, hashValues;
 
 import 'package:flutter/foundation.dart';
@@ -14,7 +14,6 @@
 import 'asset_bundle.dart';
 import 'http_client.dart';
 import 'image_cache.dart';
-import 'image_decoder.dart';
 import 'image_stream.dart';
 
 /// Configuration information passed to the [ImageProvider.resolve] method to
@@ -365,8 +364,9 @@
   /// image using [loadAsync].
   @override
   ImageStreamCompleter load(AssetBundleImageKey key) {
-    return new OneFrameImageStreamCompleter(
-      loadAsync(key),
+    return new MultiFrameImageStreamCompleter(
+      codec: _loadAsync(key),
+      scale: key.scale,
       informationCollector: (StringBuffer information) {
         information.writeln('Image provider: $this');
         information.write('Image key: $key');
@@ -379,23 +379,11 @@
   ///
   /// This function is used by [load].
   @protected
-  Future<ImageInfo> loadAsync(AssetBundleImageKey key) async {
+  Future<ui.Codec> _loadAsync(AssetBundleImageKey key) async {
     final ByteData data = await key.bundle.load(key.name);
     if (data == null)
       throw 'Unable to read data';
-    final ui.Image image = await decodeImage(data);
-    if (image == null)
-      throw 'Unable to decode image data';
-    return new ImageInfo(image: image, scale: key.scale);
-  }
-
-  /// Converts raw image data from a [ByteData] buffer into a decoded
-  /// [ui.Image] which can be passed to a [Canvas].
-  ///
-  /// By default, this just uses [decodeImageFromList]. This method could be
-  /// overridden in subclasses (e.g. for testing).
-  Future<ui.Image> decodeImage(ByteData data) {
-    return decodeImageFromList(data.buffer.asUint8List());
+    return await ui.instantiateImageCodec(data.buffer.asUint8List());
   }
 }
 
@@ -426,8 +414,9 @@
 
   @override
   ImageStreamCompleter load(NetworkImage key) {
-    return new OneFrameImageStreamCompleter(
-      _loadAsync(key),
+    return new MultiFrameImageStreamCompleter(
+      codec: _loadAsync(key),
+      scale: key.scale,
       informationCollector: (StringBuffer information) {
         information.writeln('Image provider: $this');
         information.write('Image key: $key');
@@ -437,26 +426,19 @@
 
   static final http.Client _httpClient = createHttpClient();
 
-  Future<ImageInfo> _loadAsync(NetworkImage key) async {
+  Future<ui.Codec> _loadAsync(NetworkImage key) async {
     assert(key == this);
 
     final Uri resolved = Uri.base.resolve(key.url);
     final http.Response response = await _httpClient.get(resolved);
     if (response == null || response.statusCode != 200)
-      return null;
+      throw new Exception('HTTP request failed, statusCode: ${response?.statusCode}, $resolved');
 
     final Uint8List bytes = response.bodyBytes;
     if (bytes.lengthInBytes == 0)
-      return null;
+      throw new Exception('NetworkImage is an empty file: $resolved');
 
-    final ui.Image image = await decodeImageFromList(bytes);
-    if (image == null)
-      return null;
-
-    return new ImageInfo(
-      image: image,
-      scale: key.scale,
-    );
+    return await ui.instantiateImageCodec(bytes);
   }
 
   @override
@@ -498,29 +480,23 @@
 
   @override
   ImageStreamCompleter load(FileImage key) {
-    return new OneFrameImageStreamCompleter(
-      _loadAsync(key),
+    return new MultiFrameImageStreamCompleter(
+      codec: _loadAsync(key),
+      scale: key.scale,
       informationCollector: (StringBuffer information) {
         information.writeln('Path: ${file?.path}');
       }
     );
   }
 
-  Future<ImageInfo> _loadAsync(FileImage key) async {
+  Future<ui.Codec> _loadAsync(FileImage key) async {
     assert(key == this);
 
     final Uint8List bytes = await file.readAsBytes();
     if (bytes.lengthInBytes == 0)
       return null;
 
-    final ui.Image image = await decodeImageFromList(bytes);
-    if (image == null)
-      return null;
-
-    return new ImageInfo(
-      image: image,
-      scale: key.scale,
-    );
+    return await ui.instantiateImageCodec(bytes);
   }
 
   @override
@@ -568,20 +544,16 @@
 
   @override
   ImageStreamCompleter load(MemoryImage key) {
-    return new OneFrameImageStreamCompleter(_loadAsync(key));
+    return new MultiFrameImageStreamCompleter(
+      codec: _loadAsync(key),
+      scale: key.scale
+    );
   }
 
-  Future<ImageInfo> _loadAsync(MemoryImage key) async {
+  Future<ui.Codec> _loadAsync(MemoryImage key) {
     assert(key == this);
 
-    final ui.Image image = await decodeImageFromList(bytes);
-    if (image == null)
-      return null;
-
-    return new ImageInfo(
-      image: image,
-      scale: key.scale,
-    );
+    return ui.instantiateImageCodec(bytes);
   }
 
   @override
diff --git a/packages/flutter/lib/src/services/image_stream.dart b/packages/flutter/lib/src/services/image_stream.dart
index a802ae0..91843ba 100644
--- a/packages/flutter/lib/src/services/image_stream.dart
+++ b/packages/flutter/lib/src/services/image_stream.dart
@@ -365,6 +365,8 @@
 
   void _handleCodecReady(ui.Codec codec) {
     _codec = codec;
+    assert(_codec != null);
+
     _decodeNextFrameAndSchedule();
   }
 
diff --git a/packages/flutter/test/widgets/image_resolution_test.dart b/packages/flutter/test/widgets/image_resolution_test.dart
index 02f9ced..301d221 100644
--- a/packages/flutter/test/widgets/image_resolution_test.dart
+++ b/packages/flutter/test/widgets/image_resolution_test.dart
@@ -88,24 +88,27 @@
   String toString() => '${describeIdentity(this)}()';
 }
 
+class FakeImageStreamCompleter extends ImageStreamCompleter {
+  FakeImageStreamCompleter(Future<ImageInfo> image) {
+    image.then<Null>(setImage);
+  }
+}
+
 class TestAssetImage extends AssetImage {
   TestAssetImage(String name) : super(name);
 
   @override
-  Future<ImageInfo> loadAsync(AssetBundleImageKey key) {
-    ImageInfo result;
+  ImageStreamCompleter load(AssetBundleImageKey key) {
+    ImageInfo imageInfo;
     key.bundle.load(key.name).then<Null>((ByteData data) {
-      decodeImage(data).then<Null>((ui.Image image) {
-        result = new ImageInfo(image: image, scale: key.scale);
-      });
+      final TestByteData testData = data;
+      final ui.Image image = new TestImage(testData.scale);
+      imageInfo = new ImageInfo(image: image, scale: key.scale);
     });
-    assert(result != null);
-    return new SynchronousFuture<ImageInfo>(result);
-  }
-
-  @override
-  Future<ui.Image> decodeImage(covariant TestByteData data) {
-    return new SynchronousFuture<ui.Image>(new TestImage(data.scale));
+    assert(imageInfo != null);
+    return new FakeImageStreamCompleter(
+      new SynchronousFuture<ImageInfo>(imageInfo)
+    );
   }
 }