blob: 3ff035ab1a30e16ff3b7f88c996cefcd9d833e15 [file] [log] [blame]
// Copyright 2018 The Fuchsia 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 'package:fidl_fuchsia_update/fidl_async.dart' as update;
import 'package:fidl_fuchsia_update_channelcontrol/fidl_async.dart'
as channelcontrol;
import 'package:fidl_fuchsia_recovery/fidl_async.dart' as recovery;
import 'package:flutter/foundation.dart';
import 'package:fuchsia_logger/logger.dart';
import 'package:fuchsia_services/services.dart';
import 'package:lib.settings/device_info.dart';
import 'package:lib.widgets/model.dart';
import 'package:zircon/zircon.dart';
/// Clock ID of the system monotonic clock, which measures uptime in nanoseconds.
const int _zxClockMonotonic = 0;
const Duration _uptimeRefreshInterval = Duration(seconds: 1);
/// An interface for abstracting system interactions.
abstract class SystemInterface {
int get currentTime;
Future<bool> checkForSystemUpdate();
Future<String> getCurrentChannel();
Future<void> setTargetChannel(String channel);
Future<List<String>> getChannelList();
Future<void> factoryReset();
void dispose();
}
class DefaultSystemInterfaceImpl implements SystemInterface {
/// Controller for the update manager service.
final update.ManagerProxy _updateManager = update.ManagerProxy();
/// Controller for the update channel control service.
final channelcontrol.ChannelControlProxy _channelControl =
channelcontrol.ChannelControlProxy();
/// Controller for the factory reset service.
final recovery.FactoryResetProxy _factoryReset = recovery.FactoryResetProxy();
DefaultSystemInterfaceImpl() {
StartupContext.fromStartupInfo().incoming.connectToService(_updateManager);
StartupContext.fromStartupInfo().incoming.connectToService(_channelControl);
}
@override
int get currentTime => System.clockGet(_zxClockMonotonic) ~/ 1000;
@override
Future<bool> checkForSystemUpdate() async {
final options = update.Options(
initiator: update.Initiator.user,
);
final status = await _updateManager.checkNow(options, null);
return status != update.CheckStartedResult.throttled;
}
@override
Future<String> getCurrentChannel() => _channelControl.getCurrent();
@override
Future<void> setTargetChannel(String channel) =>
_channelControl.setTarget(channel);
@override
Future<List<String>> getChannelList() => _channelControl.getTargetList();
@override
Future<void> factoryReset() async {
if (_factoryReset.ctrl.isUnbound) {
StartupContext.fromStartupInfo().incoming.connectToService(_factoryReset);
}
await _factoryReset.reset();
}
@override
void dispose() {
_updateManager.ctrl.close();
_channelControl.ctrl.close();
_factoryReset.ctrl.close();
}
}
/// Model containing state needed for the device settings app.
class DeviceSettingsModel extends Model {
/// Placeholder time of last update, used to provide visual indication update
/// was called.
///
/// This will be removed when we have a more reliable way of showing update
/// status.
/// TODO: replace with better status info from update service
DateTime _lastUpdate;
/// Holds the build tag if a release build, otherwise holds
/// the time the source code was updated.
String _buildTag;
/// Holds the time the source code was updated.
String _sourceDate;
/// Length of time since system bootup.
Duration _uptime;
Timer _uptimeRefreshTimer;
bool _started = false;
bool _showResetConfirmation = false;
ValueNotifier<bool> channelPopupShowing = ValueNotifier<bool>(false);
bool _isChannelUpdating = false;
SystemInterface _sysInterface;
DeviceSettingsModel(this._sysInterface);
DeviceSettingsModel.withDefaultSystemInterface()
: this(DefaultSystemInterfaceImpl());
DateTime get lastUpdate => _lastUpdate;
String _currentChannel;
String get currentChannel => _currentChannel;
List<String> _channels;
List<String> get channels => _channels;
/// Determines whether the confirmation dialog for factory reset should
/// be displayed.
bool get showResetConfirmation => _showResetConfirmation;
/// Returns true if we are in the middle of updating the channel. Returns
/// false otherwise.
bool get channelUpdating => _isChannelUpdating;
String get buildTag => _buildTag;
String get sourceDate => _sourceDate;
Duration get uptime => _uptime;
bool get updateCheckDisabled =>
DateTime.now().isAfter(_lastUpdate.add(Duration(seconds: 60)));
/// Checks for update from the update service
Future<void> checkForUpdates() async {
await _sysInterface.checkForSystemUpdate();
_lastUpdate = DateTime.now();
}
Future<void> selectChannel(String selectedChannel) async {
log.info('selecting channel $selectedChannel');
channelPopupShowing.value = false;
_setChannelState(updating: true);
await _sysInterface.setTargetChannel(selectedChannel);
await _update();
}
Future<void> _update() async {
_setChannelState(updating: true);
_currentChannel = await _sysInterface.getCurrentChannel();
_channels = await _sysInterface.getChannelList();
_setChannelState(updating: false);
}
void _setChannelState({bool updating}) {
if (_isChannelUpdating == updating) {
return;
}
_isChannelUpdating = updating;
notifyListeners();
}
void dispose() {
_sysInterface.dispose();
_uptimeRefreshTimer.cancel();
}
Future<void> start() async {
if (_started) {
return;
}
_started = true;
_buildTag = DeviceInfo.buildTag;
_sourceDate = DeviceInfo.sourceDate;
updateUptime();
_uptimeRefreshTimer =
Timer.periodic(_uptimeRefreshInterval, (_) => updateUptime());
await _update();
channelPopupShowing.addListener(notifyListeners);
}
void updateUptime() {
// System clock returns time since boot in nanoseconds.
_uptime = Duration(microseconds: _sysInterface.currentTime);
notifyListeners();
}
Future<void> factoryReset() async {
if (showResetConfirmation) {
log.warning('Triggering factory reset');
await _sysInterface.factoryReset();
} else {
_showResetConfirmation = true;
notifyListeners();
}
}
void cancelFactoryReset() {
_showResetConfirmation = false;
notifyListeners();
}
}