blob: 8cdc7559efad0a3853d9e46e4357d0b27b17954a [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <chrome/browser/chromeos/app_mode/arc/arc_kiosk_app_service.h>
#include "base/time/time.h"
#include "chrome/browser/chromeos/app_mode/arc/arc_kiosk_app_service_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/ui/app_list/arc/arc_app_utils.h"
#include "chrome/browser/ui/ash/multi_user/multi_user_util.h"
#include "chrome/common/pref_names.h"
#include "components/prefs/pref_service.h"
#include "ui/app_list/app_list_constants.h"
#include "ui/base/layout.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"
#include "ui/message_center/message_center.h"
#include "ui/message_center/notification_blocker.h"
namespace chromeos {
// Timeout maintenance session after 30 minutes.
constexpr base::TimeDelta kArcKioskMaintenanceSessionTimeout =
base::TimeDelta::FromMinutes(30);
// Blocks all notifications for ARC Kiosk
class ArcKioskNotificationBlocker : public message_center::NotificationBlocker {
public:
ArcKioskNotificationBlocker()
: message_center::NotificationBlocker(
message_center::MessageCenter::Get()) {
NotifyBlockingStateChanged();
}
~ArcKioskNotificationBlocker() override {}
private:
bool ShouldShowNotification(
const message_center::Notification& notification) const override {
return false;
}
bool ShouldShowNotificationAsPopup(
const message_center::Notification& notification) const override {
return false;
}
DISALLOW_COPY_AND_ASSIGN(ArcKioskNotificationBlocker);
};
// static
ArcKioskAppService* ArcKioskAppService::Create(Profile* profile) {
return new ArcKioskAppService(profile);
}
// static
ArcKioskAppService* ArcKioskAppService::Get(content::BrowserContext* context) {
return ArcKioskAppServiceFactory::GetForBrowserContext(context);
}
void ArcKioskAppService::SetDelegate(Delegate* delegate) {
delegate_ = delegate;
}
void ArcKioskAppService::Shutdown() {
ArcAppListPrefs::Get(profile_)->RemoveObserver(this);
app_manager_->RemoveObserver(this);
}
void ArcKioskAppService::OnAppRegistered(
const std::string& app_id,
const ArcAppListPrefs::AppInfo& app_info) {
if (!app_id_.empty() && app_id != app_id_)
return;
PreconditionsChanged();
}
void ArcKioskAppService::OnAppReadyChanged(const std::string& id, bool ready) {
if (!app_id_.empty() && id != app_id_)
return;
PreconditionsChanged();
}
void ArcKioskAppService::OnPackageListInitialRefreshed() {
// The app could already be registered.
PreconditionsChanged();
}
void ArcKioskAppService::OnArcKioskAppsChanged() {
PreconditionsChanged();
}
void ArcKioskAppService::OnTaskCreated(int32_t task_id,
const std::string& package_name,
const std::string& activity,
const std::string& intent) {
// Store task id of the app to stop it later when needed.
if (app_info_ && package_name == app_info_->package_name &&
activity == app_info_->activity && intent == app_info_->intent_uri) {
task_id_ = task_id;
if (delegate_)
delegate_->OnAppStarted();
}
}
void ArcKioskAppService::OnTaskDestroyed(int32_t task_id) {
if (task_id == task_id_) {
app_launcher_.reset();
task_id_ = -1;
// Trying to restart app if it was somehow closed or crashed
// as kiosk app should always be running during the session.
PreconditionsChanged();
}
}
void ArcKioskAppService::OnMaintenanceSessionCreated() {
maintenance_session_running_ = true;
PreconditionsChanged();
// Safe to bind |this| as timer is auto-cancelled on destruction.
maintenance_timeout_timer_.Start(
FROM_HERE, kArcKioskMaintenanceSessionTimeout,
base::Bind(&ArcKioskAppService::OnMaintenanceSessionFinished,
base::Unretained(this)));
}
void ArcKioskAppService::OnMaintenanceSessionFinished() {
if (!maintenance_timeout_timer_.IsRunning())
VLOG(1) << "Maintenance session timeout";
maintenance_timeout_timer_.Stop();
maintenance_session_running_ = false;
PreconditionsChanged();
}
void ArcKioskAppService::OnAppWindowLaunched() {
if (delegate_)
delegate_->OnAppWindowLaunched();
}
void ArcKioskAppService::OnIconUpdated(ArcAppIcon* icon) {
DCHECK_EQ(icon, app_icon_.get());
if (icon->image_skia().isNull()) {
app_icon_.release();
return;
}
app_manager_->UpdateNameAndIcon(app_id_, app_info_->name,
app_icon_->image_skia());
}
ArcKioskAppService::ArcKioskAppService(Profile* profile) : profile_(profile) {
ArcAppListPrefs::Get(profile_)->AddObserver(this);
app_manager_ = ArcKioskAppManager::Get();
DCHECK(app_manager_);
app_manager_->AddObserver(this);
pref_change_registrar_.reset(new PrefChangeRegistrar());
pref_change_registrar_->Init(profile_->GetPrefs());
// Kiosk app can be started only when policy compliance is reported.
pref_change_registrar_->Add(
prefs::kArcPolicyComplianceReported,
base::Bind(&ArcKioskAppService::PreconditionsChanged,
base::Unretained(this)));
notification_blocker_.reset(new ArcKioskNotificationBlocker());
PreconditionsChanged();
}
ArcKioskAppService::~ArcKioskAppService() {
maintenance_timeout_timer_.Stop();
}
void ArcKioskAppService::RequestNameAndIconUpdate() {
// Request only once when app_icon_ is not initialized.
if (!app_info_ || !app_info_->ready || app_icon_)
return;
app_icon_ = base::MakeUnique<ArcAppIcon>(profile_, app_id_,
app_list::kGridIconDimension, this);
app_icon_->image_skia().GetRepresentation(ui::GetSupportedScaleFactor(
display::Screen::GetScreen()->GetPrimaryDisplay().device_scale_factor()));
// Apply default image now and in case icon is updated then OnIconUpdated()
// will be called additionally.
OnIconUpdated(app_icon_.get());
}
void ArcKioskAppService::PreconditionsChanged() {
VLOG(2) << "Preconditions for kiosk app changed";
app_id_ = GetAppId();
if (app_id_.empty()) {
VLOG(2) << "Kiosk app is not available";
return;
}
app_info_ = ArcAppListPrefs::Get(profile_)->GetApp(app_id_);
VLOG_IF(2, app_info_ && app_info_->ready) << "Kiosk app is ready";
VLOG(2) << "Maintenance session is "
<< (maintenance_session_running_ ? "running" : "not running");
VLOG(2) << "Policy compliance is "
<< (profile_->GetPrefs()->GetBoolean(
prefs::kArcPolicyComplianceReported)
? "reported"
: "not yet reported");
VLOG(2) << "Kiosk app with id: " << app_id_ << " is "
<< (app_launcher_ ? "already launched" : "not yet launched");
if (app_info_ && app_info_->ready && !maintenance_session_running_ &&
profile_->GetPrefs()->GetBoolean(prefs::kArcPolicyComplianceReported)) {
if (!app_launcher_) {
VLOG(2) << "Starting kiosk app";
app_launcher_ = base::MakeUnique<ArcKioskAppLauncher>(
profile_, ArcAppListPrefs::Get(profile_), app_id_, this);
}
} else if (task_id_ != -1) {
VLOG(2) << "Kiosk app should be closed";
arc::CloseTask(task_id_);
}
RequestNameAndIconUpdate();
}
std::string ArcKioskAppService::GetAppId() {
AccountId account_id = multi_user_util::GetAccountIdFromProfile(profile_);
const ArcKioskAppData* app = app_manager_->GetAppByAccountId(account_id);
if (!app)
return std::string();
std::unordered_set<std::string> app_ids =
ArcAppListPrefs::Get(profile_)->GetAppsForPackage(app->package_name());
if (app_ids.empty())
return std::string();
// If |activity| and |intent| are not specified, return any app from the
// package.
if (app->activity().empty() && app->intent().empty())
return *app_ids.begin();
// Check that the app is registered for given package.
return app_ids.count(app->app_id()) ? app->app_id() : std::string();
}
} // namespace chromeos