android_lifecycles_test.dart 5.9 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
// Copyright 2014 The Flutter 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 'dart:async';
import 'dart:convert';
import 'dart:io';

import 'package:path/path.dart' as path;

import '../framework/devices.dart';
import '../framework/framework.dart';
import '../framework/task_result.dart';
import '../framework/utils.dart';

const String _kOrgName = 'com.example.activitydestroy';

final RegExp _lifecycleSentinelRegExp = RegExp(r'==== lifecycle\: (.+) ====');

20 21
/// Tests the following Android lifecycles: Activity#onStop(), Activity#onResume(), Activity#onPause(),
/// and Activity#onDestroy() from Dart perspective in debug, profile, and release modes.
22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
TaskFunction androidLifecyclesTest({
  Map<String, String>? environment,
}) {
  final Directory tempDir = Directory.systemTemp
      .createTempSync('flutter_devicelab_activity_destroy.');
  return () async {
    try {
      section('Create app');
      await inDirectory(tempDir, () async {
        await flutter(
          'create',
          options: <String>[
            '--platforms',
            'android',
            '--org',
            _kOrgName,
            'app',
          ],
          environment: environment,
        );
      });

      final File mainDart = File(path.join(
        tempDir.absolute.path,
        'app',
        'lib',
        'main.dart',
      ));
      if (!mainDart.existsSync()) {
        return TaskResult.failure('${mainDart.path} does not exist');
      }

      section('Patch lib/main.dart');
      await mainDart.writeAsString(r'''
 import 'package:flutter/widgets.dart';

class LifecycleObserver extends WidgetsBindingObserver {
  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    print('==== lifecycle: $state ====');
  }
}

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  WidgetsBinding.instance.addObserver(LifecycleObserver());
  runApp(Container());
}
''', flush: true);

      Future<TaskResult> runTestFor(String mode) async {
73 74 75 76
        final AndroidDevice device = await devices.workingDevice as AndroidDevice;
        await device.unlock();

        section('Flutter run on device running API level ${device.apiLevel} (mode: $mode)');
77 78 79 80 81 82 83 84 85 86

        late Process run;
        await inDirectory(path.join(tempDir.path, 'app'), () async {
          run = await startProcess(
            path.join(flutterDirectory.path, 'bin', 'flutter'),
            flutterCommandArgs('run', <String>['--$mode']),
          );
        });

        final StreamController<String> lifecyles = StreamController<String>();
87
        final StreamIterator<String> lifecycleItr = StreamIterator<String>(lifecyles.stream);
88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109

        final StreamSubscription<void> stdout = run.stdout
          .transform<String>(utf8.decoder)
          .transform<String>(const LineSplitter())
          .listen((String log) {
            final RegExpMatch? match = _lifecycleSentinelRegExp.firstMatch(log);
              print('stdout: $log');
              if (match == null) {
                return;
              }
              final String lifecycle = match[1]!;
              print('stdout: Found app lifecycle: $lifecycle');
              lifecyles.add(lifecycle);
          });

        final StreamSubscription<void> stderr = run.stderr
          .transform<String>(utf8.decoder)
          .transform<String>(const LineSplitter())
          .listen((String log) {
            print('stderr: $log');
          });

110 111
        Future<void> expectedLifecycle(String expected) async {
          section('Wait for lifecycle: $expected (mode: $mode)');
112 113 114
          await lifecycleItr.moveNext();
          final String got = lifecycleItr.current;
          if (expected != got) {
115
            throw TaskResult.failure('expected lifecycles: `$expected`, but got` $got`');
116 117 118
          }
        }

119 120
        await expectedLifecycle('AppLifecycleState.resumed');

121 122 123
        section('Toggling app switch (mode: $mode)');
        await device.shellExec('input', <String>['keyevent', 'KEYCODE_APP_SWITCH']);

124 125 126 127
        await expectedLifecycle('AppLifecycleState.inactive');
        if (device.apiLevel == 28) { // Device lab currently runs 28.
          await expectedLifecycle('AppLifecycleState.paused');
          await expectedLifecycle('AppLifecycleState.detached');
128 129 130
        }

        section('Bring activity to foreground (mode: $mode)');
131
        await device.shellExec('am', <String>['start', '-n', '$_kOrgName.app/.MainActivity']);
132

133
        await expectedLifecycle('AppLifecycleState.resumed');
134 135 136 137

        section('Launch Settings app (mode: $mode)');
        await device.shellExec('am', <String>['start', '-a', 'android.settings.SETTINGS']);

138 139 140 141
        await expectedLifecycle('AppLifecycleState.inactive');
        if (device.apiLevel == 28) { // Device lab currently runs 28.
          await expectedLifecycle('AppLifecycleState.paused');
          await expectedLifecycle('AppLifecycleState.detached');
142 143 144
        }

        section('Bring activity to foreground (mode: $mode)');
145
        await device.shellExec('am', <String>['start', '-n', '$_kOrgName.app/.MainActivity']);
146

147
        await expectedLifecycle('AppLifecycleState.resumed');
148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175

        run.kill();

        section('Stop subscriptions (mode: $mode)');

        await lifecycleItr.cancel();
        await lifecyles.close();
        await stdout.cancel();
        await stderr.cancel();
        return TaskResult.success(null);
      }

      final TaskResult debugResult = await runTestFor('debug');
      if (debugResult.failed) {
        return debugResult;
      }

      final TaskResult profileResult = await runTestFor('profile');
      if (profileResult.failed) {
        return profileResult;
      }

      final TaskResult releaseResult = await runTestFor('release');
       if (releaseResult.failed) {
        return releaseResult;
      }

      return TaskResult.success(null);
176 177
    } on TaskResult catch (error) {
      return error;
178 179 180 181 182
    } finally {
      rmTree(tempDir);
    }
  };
}