41ffaa7f2gated picture-in-picture on FEATURE_AUTOMOTIVE and added a settings case for it, but the assertion that case leads with — a stored auto-PiP true surviving a read — needs supportsPictureInPicture() to be true, and that gate ends in Platform.isAndroid || isIOS || isMacOS. The term is false and unmockable on the Linux and Windows runners, so the case passed on a macOS host and could never pass in CI: sanity checks have been red for six commits on this one failure out of 4723.f13f5af6erecorded it as a pre-existing Windows-host failure, but it entered in this window and is red on Linux too. Extract the gate's decision into a pure pictureInPictureAllowed that takes the host's own capability as a parameter, the way driver_distraction.dart already splits automotivePlaybackAllowed from its ambient wrapper. The boolean algebra is unchanged, so the three callers keep their behaviour; what changes is that the automotive and TV vetoes become observable where every Platform branch is false, instead of being vacuous on the host that gates the release. The settings case keeps the pref-level contract on both host classes: a stored true survives where the host supports PiP, and the gate pins it off where it does not. Verified with the host term forced false to emulate a Linux runner: both files stay green, as does the full suite on macOS.
145 lines
4.8 KiB
Dart
145 lines
4.8 KiB
Dart
import 'dart:async';
|
|
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:plezy/utils/platform_detector.dart';
|
|
|
|
void main() {
|
|
setUp(() {
|
|
TvDetectionService.debugReset();
|
|
addTearDown(TvDetectionService.debugReset);
|
|
});
|
|
|
|
test('concurrent callers wait for TV detection', () async {
|
|
final detection = Completer<void>();
|
|
TvDetectionService.debugDetectionGate = detection.future;
|
|
|
|
final first = TvDetectionService.getInstance(forceTv: true);
|
|
var secondCompleted = false;
|
|
final second = TvDetectionService.getInstance();
|
|
unawaited(second.then((_) => secondCompleted = true));
|
|
await Future<void>.delayed(Duration.zero);
|
|
|
|
expect(secondCompleted, isFalse);
|
|
detection.complete();
|
|
|
|
final instances = await Future.wait([first, second]);
|
|
expect(identical(instances.first, instances.last), isTrue);
|
|
expect(instances.first.isTV, isTrue);
|
|
});
|
|
|
|
group('detectAndroidTvFromSystemFeatures', () {
|
|
test('detects leanback devices', () {
|
|
final detection = detectAndroidTvFromSystemFeatures([
|
|
'android.software.leanback',
|
|
'android.hardware.touchscreen',
|
|
]);
|
|
|
|
expect(detection.isTv, isTrue);
|
|
expect(detection.reasons, contains('leanback'));
|
|
expect(detection.reasons, isNot(contains('no_touchscreen')));
|
|
});
|
|
|
|
test('detects Fire TV even when touchscreen is present', () {
|
|
final detection = detectAndroidTvFromSystemFeatures(['amazon.hardware.fire_tv', 'android.hardware.touchscreen']);
|
|
|
|
expect(detection.isTv, isTrue);
|
|
expect(detection.reasons, contains('fire_tv'));
|
|
expect(detection.reasons, isNot(contains('no_touchscreen')));
|
|
});
|
|
|
|
test('detects devices without real touchscreen capability', () {
|
|
final detection = detectAndroidTvFromSystemFeatures(['android.hardware.faketouch']);
|
|
|
|
expect(detection.isTv, isTrue);
|
|
expect(detection.reasons, contains('no_touchscreen'));
|
|
});
|
|
|
|
test('detects television feature', () {
|
|
final detection = detectAndroidTvFromSystemFeatures([
|
|
'android.hardware.type.television',
|
|
'android.hardware.touchscreen',
|
|
]);
|
|
|
|
expect(detection.isTv, isTrue);
|
|
expect(detection.reasons, contains('television_feature'));
|
|
});
|
|
|
|
test('does not classify touchscreen-only devices as TV', () {
|
|
final detection = detectAndroidTvFromSystemFeatures(['android.hardware.touchscreen']);
|
|
|
|
expect(detection.isTv, isFalse);
|
|
expect(detection.reasons, isEmpty);
|
|
});
|
|
|
|
test('does not classify empty feature lists as no-touchscreen TVs', () {
|
|
final detection = detectAndroidTvFromSystemFeatures(const []);
|
|
|
|
expect(detection.isTv, isFalse);
|
|
expect(detection.reasons, isEmpty);
|
|
});
|
|
|
|
test('classifies automotive head units as cars, not TVs', () {
|
|
final detection = detectAndroidTvFromSystemFeatures([
|
|
'android.hardware.type.automotive',
|
|
'android.hardware.touchscreen',
|
|
]);
|
|
|
|
expect(detection.isAutomotive, isTrue);
|
|
expect(detection.isTv, isFalse);
|
|
});
|
|
|
|
test('rotary-only head units are cars despite reporting no touchscreen', () {
|
|
final detection = detectAndroidTvFromSystemFeatures(['android.hardware.type.automotive']);
|
|
|
|
expect(detection.isAutomotive, isTrue);
|
|
expect(detection.isTv, isFalse);
|
|
expect(detection.reasons, contains('no_touchscreen'));
|
|
});
|
|
|
|
test('automotive vetoes a stray leanback flag from an OEM image', () {
|
|
final detection = detectAndroidTvFromSystemFeatures([
|
|
'android.hardware.type.automotive',
|
|
'android.software.leanback',
|
|
'android.hardware.touchscreen',
|
|
]);
|
|
|
|
expect(detection.isAutomotive, isTrue);
|
|
expect(detection.isTv, isFalse);
|
|
});
|
|
|
|
test('ordinary devices are not automotive', () {
|
|
final detection = detectAndroidTvFromSystemFeatures(['android.hardware.touchscreen']);
|
|
|
|
expect(detection.isAutomotive, isFalse);
|
|
});
|
|
});
|
|
|
|
group('pictureInPictureAllowed', () {
|
|
bool allowed({bool host = true, bool appleTv = false, bool tv = false, bool automotive = false}) =>
|
|
pictureInPictureAllowed(
|
|
hostSupportsPictureInPicture: host,
|
|
isAppleTv: appleTv,
|
|
isTv: tv,
|
|
isAutomotive: automotive,
|
|
);
|
|
|
|
test('a plain handheld host may float a player', () {
|
|
expect(allowed(), isTrue);
|
|
});
|
|
|
|
test('automotive vetoes a host that otherwise supports PiP', () {
|
|
expect(allowed(automotive: true), isFalse);
|
|
});
|
|
|
|
test('TV form factors veto a host that otherwise supports PiP', () {
|
|
expect(allowed(tv: true), isFalse);
|
|
expect(allowed(appleTv: true), isFalse);
|
|
});
|
|
|
|
test('a host without PiP is never allowed, whatever the form factor', () {
|
|
expect(allowed(host: false), isFalse);
|
|
expect(allowed(host: false, automotive: true), isFalse);
|
|
});
|
|
});
|
|
}
|