mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-11 12:27:50 +01:00
phone_focus_mode is built for a rooted phone: the hosts layer is a read-only
bind mount over /system/etc/hosts, chosen so that even root-in-a-terminal
cannot overwrite it. None of that ports to a stock locked device. This splits
out the parts that are device-independent so one policy can drive both.
python_pkg/focus_policy parses config.sh rather than copying it, so the shell
config stays the source of truth and the two cannot drift. It rejects the
shipped REDACTED_* coordinate placeholder instead of defaulting to (0,0),
which would move "home" to the Atlantic and quietly disable the location gate.
blocklist.py converts the hosts file to domains for a DoT resolver or a local
VpnService. Three properties of the real 194k-line file make the obvious parser
wrong, and all three are handled: generate_hosts_file.sh *comments out*
deliberate unblocks rather than deleting them, so stripping '#' re-blocks
facebook/messenger/linkedin/4chan; the StevenBlack header carries loopback and
IPv6 housekeeping that would blackhole localhost; and ~1060 upstream annotation
tokens ('#[adware.zeno]', '"spam') render as address=/"spam/, which makes
dnsmasq refuse to start -- taking DNS down rather than blocking anything.
Verified with dnsmasq --test over all 185681 generated domains.
strip_workout_hosts.py had the same class of bug on the live rooted phone:
exact set membership meant the allowlisted googlevideo.com never matched the
dynamic r*---sn-*.googlevideo.com hosts YouTube actually streams from, so the
workout unblock was only partly working. Now suffix-matched (17 lines stripped,
was 14). The three new tests were checked against the original code in a
throwaway worktree; two of them fail there, so they pin the fix rather than
passing vacuously.
focus_owner is a device-owner app that deliberately does nothing yet. It ships
clearDeviceOwnerApp() implemented and tested first, because dpm
remove-active-admin does not work on a device owner: an app that locks the
device down before proving it can let go leaves a soft-brick recoverable only
through fastboot on a locked bootloader. Built, installed and launched on the
Pixel 6a; dumpsys afterwards confirms Device Owner Type -1 and no user
restrictions, i.e. the phone is unchanged.
Measured while scoping this, and not yet reflected in any enforcement code:
on Android 16 both pm suspend and pm disable-user are cleared by a reboot, so
shell-level app blocking is per-session only. setApplicationHidden is assumed
to persist under Device Owner but is untested.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AwepyL5Lxs5QKbKkMpxxxL
148 lines
5.3 KiB
Dart
148 lines
5.3 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:focus_owner/device_policy.dart';
|
|
import 'package:focus_owner/main.dart';
|
|
|
|
/// Builds a channel whose handler answers from [responses], recording calls.
|
|
({MethodChannel channel, List<MethodCall> calls}) _fakeChannel(
|
|
Map<String, Object?> responses,
|
|
) {
|
|
const channel = MethodChannel('test/device_policy');
|
|
final calls = <MethodCall>[];
|
|
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
|
.setMockMethodCallHandler(channel, (call) async {
|
|
calls.add(call);
|
|
if (!responses.containsKey(call.method)) {
|
|
throw PlatformException(code: 'unimplemented', message: 'no stub');
|
|
}
|
|
return responses[call.method];
|
|
});
|
|
return (channel: channel, calls: calls);
|
|
}
|
|
|
|
Map<String, Object?> _statusMap({bool isDeviceOwner = false}) => {
|
|
'packageName': 'com.kuhy.focus_owner',
|
|
'isDeviceOwner': isDeviceOwner,
|
|
'isAdminActive': isDeviceOwner,
|
|
'sdkInt': 36,
|
|
'restrictionsApplied': false,
|
|
};
|
|
|
|
void main() {
|
|
group('DevicePolicyStatus', () {
|
|
test('parses the native status map', () {
|
|
final status = DevicePolicyStatus.fromMap(_statusMap(isDeviceOwner: true));
|
|
expect(status.packageName, 'com.kuhy.focus_owner');
|
|
expect(status.isDeviceOwner, isTrue);
|
|
expect(status.sdkInt, 36);
|
|
});
|
|
|
|
test('this build never reports restrictions as applied', () {
|
|
// The app is provisioning-capable but inert; enforcement ships only
|
|
// after the release path is verified on a device.
|
|
expect(
|
|
DevicePolicyStatus.fromMap(_statusMap()).restrictionsApplied,
|
|
isFalse,
|
|
);
|
|
});
|
|
});
|
|
|
|
group('DevicePolicy', () {
|
|
test('status() invokes the native status method', () async {
|
|
final fake = _fakeChannel({'status': _statusMap()});
|
|
final status = await DevicePolicy(fake.channel).status();
|
|
expect(fake.calls.single.method, 'status');
|
|
expect(status.isDeviceOwner, isFalse);
|
|
});
|
|
|
|
test('releaseDeviceOwner() forwards the native result', () async {
|
|
final fake = _fakeChannel({'releaseDeviceOwner': true});
|
|
expect(await DevicePolicy(fake.channel).releaseDeviceOwner(), isTrue);
|
|
expect(fake.calls.single.method, 'releaseDeviceOwner');
|
|
});
|
|
|
|
test('releaseDeviceOwner() reports failure rather than throwing', () async {
|
|
// The button that calls this is the last resort before a factory reset,
|
|
// so a failure must surface in the UI, never as an unhandled error.
|
|
final fake = _fakeChannel({'releaseDeviceOwner': false});
|
|
expect(await DevicePolicy(fake.channel).releaseDeviceOwner(), isFalse);
|
|
});
|
|
|
|
test('releaseDeviceOwner() treats a null reply as failure', () async {
|
|
final fake = _fakeChannel({'releaseDeviceOwner': null});
|
|
expect(await DevicePolicy(fake.channel).releaseDeviceOwner(), isFalse);
|
|
});
|
|
});
|
|
|
|
group('StatusPage', () {
|
|
testWidgets('hides the release button when not device owner',
|
|
(tester) async {
|
|
final fake = _fakeChannel({'status': _statusMap()});
|
|
await tester.pumpWidget(
|
|
MaterialApp(home: StatusPage(policy: DevicePolicy(fake.channel))),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text('Release device owner'), findsNothing);
|
|
expect(find.textContaining('Nothing is enforced'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('shows the release button when device owner', (tester) async {
|
|
final fake = _fakeChannel({'status': _statusMap(isDeviceOwner: true)});
|
|
await tester.pumpWidget(
|
|
MaterialApp(home: StatusPage(policy: DevicePolicy(fake.channel))),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text('Release device owner'), findsOneWidget);
|
|
expect(find.text('none (inert build)'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('release is confirmed before it runs', (tester) async {
|
|
final fake = _fakeChannel({
|
|
'status': _statusMap(isDeviceOwner: true),
|
|
'releaseDeviceOwner': true,
|
|
});
|
|
await tester.pumpWidget(
|
|
MaterialApp(home: StatusPage(policy: DevicePolicy(fake.channel))),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
await tester.tap(find.text('Release device owner'));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('Release device owner?'), findsOneWidget);
|
|
|
|
// Cancelling must not call through to the platform.
|
|
await tester.tap(find.text('Cancel'));
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
fake.calls.where((c) => c.method == 'releaseDeviceOwner'),
|
|
isEmpty,
|
|
);
|
|
});
|
|
|
|
testWidgets('confirming release calls the platform', (tester) async {
|
|
final fake = _fakeChannel({
|
|
'status': _statusMap(isDeviceOwner: true),
|
|
'releaseDeviceOwner': true,
|
|
});
|
|
await tester.pumpWidget(
|
|
MaterialApp(home: StatusPage(policy: DevicePolicy(fake.channel))),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
await tester.tap(find.text('Release device owner'));
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(find.text('Release'));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
fake.calls.where((c) => c.method == 'releaseDeviceOwner'),
|
|
hasLength(1),
|
|
);
|
|
expect(find.text('Device owner released'), findsOneWidget);
|
|
});
|
|
});
|
|
}
|