Skip to content

Commit a6a8caa

Browse files
authored
Read AndroidManifest.xml and emit manifest-impeller-(enabled|disabled) analytics (flutter#150791)
Work towards flutter#132712. After this PR, after a completed `flutter build apk` command, we: - Emit a `manifest-impeller-disabled` command if `io.flutter.embedding.android.EnableImpeller` is `'false'`. - Emit a `manifest-impeller-disabled` command if `io.flutter.embedding.android.EnableImpeller` is _missing_. - Emit a `manifest-impeller-enabled` command if `io.flutter.embedding.android.EnableImpeller` is `'true'`. We will need to change the default (see `_impellerEnabledByDefault` in `project.dart`) before releasing, otherwise we will misreport `manifest-impeller-disabled` at a much higher rate than actual. If there is a way to instead compute the default instead of hard-coding, that would have been good. See <https://docs.flutter.dev/perf/impeller#android> for details on the key-value pair. --- I also did a tad of TLC, by removing the (now-defunct) `Usage` events for `flutter build ios`, so they are consistent. /cc @zanderso, @chinmaygarde, @jonahwilliams
1 parent 9afd397 commit a6a8caa

File tree

5 files changed

+166
-24
lines changed

5 files changed

+166
-24
lines changed

packages/flutter_tools/lib/src/commands/build_apk.dart

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,12 +137,26 @@ class BuildApkCommand extends BuildSubCommand {
137137
validateBuild(androidBuildInfo);
138138
displayNullSafetyMode(androidBuildInfo.buildInfo);
139139
globals.terminal.usesTerminalUi = true;
140+
final FlutterProject project = FlutterProject.current();
140141
await androidBuilder?.buildApk(
141-
project: FlutterProject.current(),
142+
project: project,
142143
target: targetFile,
143144
androidBuildInfo: androidBuildInfo,
144145
configOnly: configOnly,
145146
);
147+
148+
// When an app is successfully built, record to analytics whether Impeller
149+
// is enabled or disabled. Note that 'computeImpellerEnabled' will default
150+
// to false if not enabled explicitly in the manifest.
151+
final bool impellerEnabled = project.android.computeImpellerEnabled();
152+
final String buildLabel = impellerEnabled
153+
? 'manifest-impeller-enabled'
154+
: 'manifest-impeller-disabled';
155+
globals.analytics.send(Event.flutterBuildInfo(
156+
label: buildLabel,
157+
buildType: 'android',
158+
));
159+
146160
return FlutterCommandResult.success();
147161
}
148162
}

packages/flutter_tools/lib/src/commands/build_ios.dart

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import '../ios/application_package.dart';
2424
import '../ios/mac.dart';
2525
import '../ios/plist_parser.dart';
2626
import '../project.dart';
27-
import '../reporting/reporting.dart';
2827
import '../runner/flutter_command.dart';
2928
import 'build.dart';
3029

@@ -769,7 +768,8 @@ abstract class _BuildIOSSubCommand extends BuildSubCommand {
769768
);
770769

771770
// When an app is successfully built, record to analytics whether Impeller
772-
// is enabled or disabled.
771+
// is enabled or disabled. Note that we report the _lack_ of an explicit
772+
// flag set as "enabled" because the default is to enable Impeller on iOS.
773773
final BuildableIOSApp app = await buildableIOSApp;
774774
final String plistPath = app.project.infoPlist.path;
775775
final bool? impellerEnabled = globals.plistParser.getValueFromFile<bool>(
@@ -779,11 +779,6 @@ abstract class _BuildIOSSubCommand extends BuildSubCommand {
779779
final String buildLabel = impellerEnabled == false
780780
? 'plist-impeller-disabled'
781781
: 'plist-impeller-enabled';
782-
BuildEvent(
783-
buildLabel,
784-
type: 'ios',
785-
flutterUsage: globals.flutterUsage,
786-
).send();
787782
globals.analytics.send(Event.flutterBuildInfo(
788783
label: buildLabel,
789784
buildType: 'ios',

packages/flutter_tools/lib/src/project.dart

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -881,6 +881,41 @@ $javaGradleCompatUrl
881881
}
882882
return AndroidEmbeddingVersionResult(AndroidEmbeddingVersion.v1, 'No `<meta-data android:name="flutterEmbedding" android:value="2"/>` in ${appManifestFile.absolute.path}');
883883
}
884+
885+
// TODO(matanlurey): Flip to true when on by default, https://github.com/flutter/flutter/issues/132712.
886+
static const bool _impellerEnabledByDefault = false;
887+
888+
/// Returns the `io.flutter.embedding.android.EnableImpeller` manifest value.
889+
///
890+
/// If there is no manifest file, or the key is not present, returns `false`.
891+
bool computeImpellerEnabled() {
892+
if (!appManifestFile.existsSync()) {
893+
return _impellerEnabledByDefault;
894+
}
895+
final XmlDocument document;
896+
try {
897+
document = XmlDocument.parse(appManifestFile.readAsStringSync());
898+
} on XmlException {
899+
throwToolExit('Error parsing $appManifestFile '
900+
'Please ensure that the android manifest is a valid XML document and try again.');
901+
} on FileSystemException {
902+
throwToolExit('Error reading $appManifestFile even though it exists. '
903+
'Please ensure that you have read permission to this file and try again.');
904+
}
905+
for (final XmlElement metaData in document.findAllElements('meta-data')) {
906+
final String? name = metaData.getAttribute('android:name');
907+
if (name == 'io.flutter.embedding.android.EnableImpeller') {
908+
final String? value = metaData.getAttribute('android:value');
909+
if (value == 'true') {
910+
return true;
911+
}
912+
if (value == 'false') {
913+
return false;
914+
}
915+
}
916+
}
917+
return _impellerEnabledByDefault;
918+
}
884919
}
885920

886921
/// Iteration of the embedding Java API in the engine used by the Android project.

packages/flutter_tools/test/commands.shard/hermetic/build_ios_test.dart

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -621,14 +621,6 @@ void main() {
621621
const <String>['build', 'ios', '--no-pub']
622622
);
623623

624-
expect(usage.events, contains(
625-
const TestUsageEvent(
626-
'build', 'ios',
627-
label:'plist-impeller-enabled',
628-
parameters:CustomDimensions(),
629-
),
630-
));
631-
632624
expect(fakeAnalytics.sentEvents, contains(
633625
Event.flutterBuildInfo(
634626
label: 'plist-impeller-enabled',
@@ -684,14 +676,6 @@ void main() {
684676
const <String>['build', 'ios', '--no-pub']
685677
);
686678

687-
expect(usage.events, contains(
688-
const TestUsageEvent(
689-
'build', 'ios',
690-
label:'plist-impeller-disabled',
691-
parameters:CustomDimensions(),
692-
),
693-
));
694-
695679
expect(fakeAnalytics.sentEvents, contains(
696680
Event.flutterBuildInfo(
697681
label: 'plist-impeller-disabled',

packages/flutter_tools/test/commands.shard/permeable/build_apk_test.dart

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,120 @@ void main() {
127127
AndroidBuilder: () => FakeAndroidBuilder(),
128128
Usage: () => testUsage,
129129
});
130+
131+
group('Impeller AndroidManifest.xml setting', () {
132+
// Adds a key-value `<meta-data>` pair to the `<application>` tag in the
133+
// cooresponding `AndroidManifest.xml` file, right before the closing
134+
// `</application>` tag.
135+
void writeManifestMetadata({
136+
required String projectPath,
137+
required String name,
138+
required String value,
139+
}) {
140+
final String manifestPath = globals.fs.path.join(
141+
projectPath,
142+
'android',
143+
'app',
144+
'src',
145+
'main',
146+
'AndroidManifest.xml',
147+
);
148+
149+
// It would be unnecessarily complicated to parse this XML file and
150+
// insert the key-value pair, so we just insert it right before the
151+
// closing </application> tag.
152+
final String oldManifest = globals.fs.file(manifestPath).readAsStringSync();
153+
final String newManifest = oldManifest.replaceFirst(
154+
'</application>',
155+
' <meta-data\n'
156+
' android:name="$name"\n'
157+
' android:value="$value" />\n'
158+
' </application>',
159+
);
160+
globals.fs.file(manifestPath).writeAsStringSync(newManifest);
161+
}
162+
163+
testUsingContext('a default APK build reports Impeller as disabled', () async {
164+
final String projectPath = await createProject(
165+
tempDir,
166+
arguments: <String>['--no-pub', '--template=app', '--platform=android']
167+
);
168+
169+
await runBuildApkCommand(projectPath);
170+
171+
expect(
172+
fakeAnalytics.sentEvents,
173+
contains(
174+
Event.flutterBuildInfo(
175+
label: 'manifest-impeller-disabled',
176+
buildType: 'android',
177+
),
178+
),
179+
);
180+
}, overrides: <Type, Generator>{
181+
Analytics: () => fakeAnalytics,
182+
AndroidBuilder: () => FakeAndroidBuilder(),
183+
FlutterProjectFactory: () => FakeFlutterProjectFactory(tempDir),
184+
});
185+
186+
testUsingContext('EnableImpeller="true" reports an enabled event', () async {
187+
final String projectPath = await createProject(
188+
tempDir,
189+
arguments: <String>['--no-pub', '--template=app', '--platform=android']
190+
);
191+
192+
writeManifestMetadata(
193+
projectPath: projectPath,
194+
name: 'io.flutter.embedding.android.EnableImpeller',
195+
value: 'true',
196+
);
197+
198+
await runBuildApkCommand(projectPath);
199+
200+
expect(
201+
fakeAnalytics.sentEvents,
202+
contains(
203+
Event.flutterBuildInfo(
204+
label: 'manifest-impeller-enabled',
205+
buildType: 'android',
206+
),
207+
),
208+
);
209+
}, overrides: <Type, Generator>{
210+
Analytics: () => fakeAnalytics,
211+
AndroidBuilder: () => FakeAndroidBuilder(),
212+
FlutterProjectFactory: () => FakeFlutterProjectFactory(tempDir),
213+
});
214+
215+
testUsingContext('EnableImpeller="false" reports an disabled event', () async {
216+
final String projectPath = await createProject(
217+
tempDir,
218+
arguments: <String>['--no-pub', '--template=app', '--platform=android']
219+
);
220+
221+
writeManifestMetadata(
222+
projectPath: projectPath,
223+
name: 'io.flutter.embedding.android.EnableImpeller',
224+
value: 'false',
225+
);
226+
227+
await runBuildApkCommand(projectPath);
228+
229+
expect(
230+
fakeAnalytics.sentEvents,
231+
contains(
232+
Event.flutterBuildInfo(
233+
label: 'manifest-impeller-disabled',
234+
buildType: 'android',
235+
),
236+
),
237+
);
238+
}, overrides: <Type, Generator>{
239+
Analytics: () => fakeAnalytics,
240+
AndroidBuilder: () => FakeAndroidBuilder(),
241+
FlutterProjectFactory: () => FakeFlutterProjectFactory(tempDir),
242+
});
243+
});
130244
});
131245

132246
group('Gradle', () {

0 commit comments

Comments
 (0)