Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. | 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "chrome/browser/chromeos/arc/arc_session_manager.h" | 5 #include "chrome/browser/chromeos/arc/arc_session_manager.h" |
| 6 | 6 |
| 7 #include <utility> | 7 #include <utility> |
| 8 | 8 |
| 9 #include "ash/common/shelf/shelf_delegate.h" | 9 #include "ash/common/shelf/shelf_delegate.h" |
| 10 #include "ash/common/wm_shell.h" | 10 #include "ash/common/wm_shell.h" |
| (...skipping 92 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 103 ArcSessionManager* ArcSessionManager::Get() { | 103 ArcSessionManager* ArcSessionManager::Get() { |
| 104 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); | 104 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| 105 return g_arc_session_manager; | 105 return g_arc_session_manager; |
| 106 } | 106 } |
| 107 | 107 |
| 108 // static | 108 // static |
| 109 void ArcSessionManager::RegisterProfilePrefs( | 109 void ArcSessionManager::RegisterProfilePrefs( |
| 110 user_prefs::PrefRegistrySyncable* registry) { | 110 user_prefs::PrefRegistrySyncable* registry) { |
| 111 // TODO(dspaid): Implement a mechanism to allow this to sync on first boot | 111 // TODO(dspaid): Implement a mechanism to allow this to sync on first boot |
| 112 // only. | 112 // only. |
| 113 registry->RegisterBooleanPref(prefs::kArcDataRemoveRequested, false); | |
| 113 registry->RegisterBooleanPref(prefs::kArcEnabled, false); | 114 registry->RegisterBooleanPref(prefs::kArcEnabled, false); |
| 114 registry->RegisterBooleanPref(prefs::kArcSignedIn, false); | 115 registry->RegisterBooleanPref(prefs::kArcSignedIn, false); |
| 115 registry->RegisterBooleanPref(prefs::kArcTermsAccepted, false); | 116 registry->RegisterBooleanPref(prefs::kArcTermsAccepted, false); |
| 116 registry->RegisterBooleanPref(prefs::kArcBackupRestoreEnabled, true); | 117 registry->RegisterBooleanPref(prefs::kArcBackupRestoreEnabled, true); |
| 117 registry->RegisterBooleanPref(prefs::kArcLocationServiceEnabled, true); | 118 registry->RegisterBooleanPref(prefs::kArcLocationServiceEnabled, true); |
| 118 } | 119 } |
| 119 | 120 |
| 120 // static | 121 // static |
| 121 void ArcSessionManager::DisableUIForTesting() { | 122 void ArcSessionManager::DisableUIForTesting() { |
| 122 g_disable_ui_for_testing = true; | 123 g_disable_ui_for_testing = true; |
| (...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 188 bool ArcSessionManager::IsArcKioskMode() { | 189 bool ArcSessionManager::IsArcKioskMode() { |
| 189 return user_manager::UserManager::Get()->IsLoggedInAsArcKioskApp(); | 190 return user_manager::UserManager::Get()->IsLoggedInAsArcKioskApp(); |
| 190 } | 191 } |
| 191 | 192 |
| 192 void ArcSessionManager::OnBridgeStopped(ArcBridgeService::StopReason reason) { | 193 void ArcSessionManager::OnBridgeStopped(ArcBridgeService::StopReason reason) { |
| 193 // TODO(crbug.com/625923): Use |reason| to report more detailed errors. | 194 // TODO(crbug.com/625923): Use |reason| to report more detailed errors. |
| 194 if (arc_sign_in_timer_.IsRunning()) { | 195 if (arc_sign_in_timer_.IsRunning()) { |
| 195 OnProvisioningFinished(ProvisioningResult::ARC_STOPPED); | 196 OnProvisioningFinished(ProvisioningResult::ARC_STOPPED); |
| 196 } | 197 } |
| 197 | 198 |
| 198 if (clear_required_) { | 199 if (profile_->GetPrefs()->GetBoolean(prefs::kArcDataRemoveRequested)) { |
| 199 // This should be always true, but just in case as this is looked at | 200 // This should be always true, but just in case as this is looked at |
| 200 // inside RemoveArcData() at first. | 201 // inside RemoveArcData() at first. |
| 201 DCHECK(arc_bridge_service()->stopped()); | 202 DCHECK(arc_bridge_service()->stopped()); |
| 202 RemoveArcData(); | 203 RemoveArcData(); |
| 203 } else { | 204 } else { |
| 204 // To support special "Stop and enable ARC" procedure for enterprise, | 205 // To support special "Stop and enable ARC" procedure for enterprise, |
| 205 // here call OnArcDataRemoved(true) as if the data removal is successfully | 206 // here call MaybeReenableArc() |
| 206 // done. | 207 MaybeReenableArc(); |
|
hidehiko
2016/12/06 05:05:30
PostTask is the workaround for crbug.com/665316, a
khmel
2016/12/06 18:16:13
It seems that problem quite complicated based on t
| |
| 207 // TODO(hidehiko): Restructure the code. crbug.com/665316 | |
| 208 base::ThreadTaskRunnerHandle::Get()->PostTask( | |
| 209 FROM_HERE, base::Bind(&ArcSessionManager::OnArcDataRemoved, | |
| 210 weak_ptr_factory_.GetWeakPtr(), true)); | |
| 211 } | 208 } |
| 212 } | 209 } |
| 213 | 210 |
| 214 void ArcSessionManager::RemoveArcData() { | 211 void ArcSessionManager::RemoveArcData() { |
| 212 // Ignore redundant data removal request. | |
| 213 if (state() == State::DELETING_DATA_FOLDER) | |
| 214 return; | |
| 215 | |
| 216 // OnArcDataRemoved resets this flag. | |
| 217 profile_->GetPrefs()->SetBoolean(prefs::kArcDataRemoveRequested, true); | |
| 218 | |
| 215 if (!arc_bridge_service()->stopped()) { | 219 if (!arc_bridge_service()->stopped()) { |
| 216 // Just set a flag. On bridge stopped, this will be re-called, | 220 // Just set a flag. On bridge stopped, this will be re-called, |
| 217 // then session manager should remove the data. | 221 // then session manager should remove the data. |
| 218 clear_required_ = true; | |
| 219 return; | 222 return; |
| 220 } | 223 } |
| 221 clear_required_ = false; | 224 |
| 225 SetState(State::DELETING_DATA_FOLDER); | |
| 222 chromeos::DBusThreadManager::Get()->GetSessionManagerClient()->RemoveArcData( | 226 chromeos::DBusThreadManager::Get()->GetSessionManagerClient()->RemoveArcData( |
| 223 cryptohome::Identification( | 227 cryptohome::Identification( |
| 224 multi_user_util::GetAccountIdFromProfile(profile_)), | 228 multi_user_util::GetAccountIdFromProfile(profile_)), |
| 225 base::Bind(&ArcSessionManager::OnArcDataRemoved, | 229 base::Bind(&ArcSessionManager::OnArcDataRemoved, |
| 226 weak_ptr_factory_.GetWeakPtr())); | 230 weak_ptr_factory_.GetWeakPtr())); |
| 227 } | 231 } |
| 228 | 232 |
| 229 void ArcSessionManager::OnArcDataRemoved(bool success) { | 233 void ArcSessionManager::OnArcDataRemoved(bool success) { |
| 230 LOG_IF(ERROR, !success) << "Required ARC user data wipe failed."; | 234 LOG_IF(ERROR, !success) << "Required ARC user data wipe failed."; |
| 231 | 235 |
| 236 // TODO(khmel): Browser tests may shutdown profile by itself. Update browser | |
| 237 // tests and remove this check. | |
| 238 if (state() == State::NOT_INITIALIZED) | |
| 239 return; | |
| 240 | |
| 241 profile_->GetPrefs()->SetBoolean(prefs::kArcDataRemoveRequested, false); | |
| 242 DCHECK_EQ(state(), State::DELETING_DATA_FOLDER); | |
| 243 SetState(State::STOPPED); | |
| 244 | |
| 245 MaybeReenableArc(); | |
| 246 } | |
| 247 | |
| 248 void ArcSessionManager::MaybeReenableArc() { | |
| 232 // Here check if |reenable_arc_| is marked or not. | 249 // Here check if |reenable_arc_| is marked or not. |
| 233 // The only case this happens should be in the special case for enterprise | 250 // The only case this happens should be in the special case for enterprise |
| 234 // "on managed lost" case. In that case, OnBridgeStopped() should trigger | 251 // "on managed lost" case. In that case, OnBridgeStopped() should trigger |
| 235 // the RemoveArcData(), then this. | 252 // the RemoveArcData(), then this. |
| 236 // TODO(hidehiko): Restructure the code. | 253 if (!reenable_arc_ || !IsArcEnabled()) |
| 237 if (!reenable_arc_) | |
| 238 return; | 254 return; |
| 239 | 255 |
| 240 // Restart ARC anyway. Let the enterprise reporting instance decide whether | 256 // Restart ARC anyway. Let the enterprise reporting instance decide whether |
| 241 // the ARC user data wipe is still required or not. | 257 // the ARC user data wipe is still required or not. |
| 242 reenable_arc_ = false; | 258 reenable_arc_ = false; |
| 243 VLOG(1) << "Reenable ARC"; | 259 VLOG(1) << "Reenable ARC"; |
| 244 EnableArc(); | 260 EnableArc(); |
| 245 } | 261 } |
| 246 | 262 |
| 247 void ArcSessionManager::OnProvisioningFinished(ProvisioningResult result) { | 263 void ArcSessionManager::OnProvisioningFinished(ProvisioningResult result) { |
| (...skipping 21 matching lines...) Expand all Loading... | |
| 269 UpdateProvisioningResultUMA(result, | 285 UpdateProvisioningResultUMA(result, |
| 270 policy_util::IsAccountManaged(profile_)); | 286 policy_util::IsAccountManaged(profile_)); |
| 271 if (result != ProvisioningResult::SUCCESS) | 287 if (result != ProvisioningResult::SUCCESS) |
| 272 UpdateOptInCancelUMA(OptInCancelReason::CLOUD_PROVISION_FLOW_FAIL); | 288 UpdateOptInCancelUMA(OptInCancelReason::CLOUD_PROVISION_FLOW_FAIL); |
| 273 } | 289 } |
| 274 | 290 |
| 275 if (result == ProvisioningResult::SUCCESS) { | 291 if (result == ProvisioningResult::SUCCESS) { |
| 276 if (support_host_) | 292 if (support_host_) |
| 277 support_host_->Close(); | 293 support_host_->Close(); |
| 278 | 294 |
| 295 // No data removal request is expected on Arc boot. Removing Arc data is | |
| 296 // always accompanied with request to stop the bridge. | |
| 297 DCHECK(!profile_->GetPrefs()->GetBoolean(prefs::kArcDataRemoveRequested)); | |
| 298 | |
| 279 if (profile_->GetPrefs()->GetBoolean(prefs::kArcSignedIn)) | 299 if (profile_->GetPrefs()->GetBoolean(prefs::kArcSignedIn)) |
| 280 return; | 300 return; |
| 281 | 301 |
| 282 profile_->GetPrefs()->SetBoolean(prefs::kArcSignedIn, true); | 302 profile_->GetPrefs()->SetBoolean(prefs::kArcSignedIn, true); |
| 283 // Don't show Play Store app for ARC Kiosk because the only one UI in kiosk | 303 // Don't show Play Store app for ARC Kiosk because the only one UI in kiosk |
| 284 // mode must be the kiosk app and device is not needed for opt-in. | 304 // mode must be the kiosk app and device is not needed for opt-in. |
| 285 if (!IsOptInVerificationDisabled() && !IsArcKioskMode()) { | 305 if (!IsOptInVerificationDisabled() && !IsArcKioskMode()) { |
| 286 playstore_launcher_.reset( | 306 playstore_launcher_.reset( |
| 287 new ArcAppLauncher(profile_, kPlayStoreAppId, true)); | 307 new ArcAppLauncher(profile_, kPlayStoreAppId, true)); |
| 288 } | 308 } |
| (...skipping 116 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 405 if (!g_disable_ui_for_testing || | 425 if (!g_disable_ui_for_testing || |
| 406 g_enable_check_android_management_for_testing) { | 426 g_enable_check_android_management_for_testing) { |
| 407 ArcAndroidManagementChecker::StartClient(); | 427 ArcAndroidManagementChecker::StartClient(); |
| 408 } | 428 } |
| 409 pref_change_registrar_.Init(profile_->GetPrefs()); | 429 pref_change_registrar_.Init(profile_->GetPrefs()); |
| 410 pref_change_registrar_.Add( | 430 pref_change_registrar_.Add( |
| 411 prefs::kArcEnabled, | 431 prefs::kArcEnabled, |
| 412 base::Bind(&ArcSessionManager::OnOptInPreferenceChanged, | 432 base::Bind(&ArcSessionManager::OnOptInPreferenceChanged, |
| 413 weak_ptr_factory_.GetWeakPtr())); | 433 weak_ptr_factory_.GetWeakPtr())); |
| 414 if (profile_->GetPrefs()->GetBoolean(prefs::kArcEnabled)) { | 434 if (profile_->GetPrefs()->GetBoolean(prefs::kArcEnabled)) { |
| 415 OnOptInPreferenceChanged(); | 435 // Don't start ARC if there is a pending request to remove the data. Restart |
| 436 // ARC once data removal finishes. | |
| 437 if (profile_->GetPrefs()->GetBoolean(prefs::kArcDataRemoveRequested)) { | |
| 438 reenable_arc_ = true; | |
| 439 RemoveArcData(); | |
| 440 } else { | |
| 441 OnOptInPreferenceChanged(); | |
| 442 } | |
| 416 } else { | 443 } else { |
| 417 RemoveArcData(); | 444 RemoveArcData(); |
| 418 PrefServiceSyncableFromProfile(profile_)->AddObserver(this); | 445 PrefServiceSyncableFromProfile(profile_)->AddObserver(this); |
| 419 OnIsSyncingChanged(); | 446 OnIsSyncingChanged(); |
| 420 } | 447 } |
| 421 } | 448 } |
| 422 | 449 |
| 423 void ArcSessionManager::OnIsSyncingChanged() { | 450 void ArcSessionManager::OnIsSyncingChanged() { |
| 424 sync_preferences::PrefServiceSyncable* const pref_service_syncable = | 451 sync_preferences::PrefServiceSyncable* const pref_service_syncable = |
| 425 PrefServiceSyncableFromProfile(profile_); | 452 PrefServiceSyncableFromProfile(profile_); |
| (...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 485 DCHECK(profile_); | 512 DCHECK(profile_); |
| 486 | 513 |
| 487 // TODO(dspaid): Move code from OnSyncedPrefChanged into this method. | 514 // TODO(dspaid): Move code from OnSyncedPrefChanged into this method. |
| 488 OnSyncedPrefChanged(prefs::kArcEnabled, IsArcManaged()); | 515 OnSyncedPrefChanged(prefs::kArcEnabled, IsArcManaged()); |
| 489 | 516 |
| 490 const bool arc_enabled = IsArcEnabled(); | 517 const bool arc_enabled = IsArcEnabled(); |
| 491 for (auto& observer : observer_list_) | 518 for (auto& observer : observer_list_) |
| 492 observer.OnOptInEnabled(arc_enabled); | 519 observer.OnOptInEnabled(arc_enabled); |
| 493 | 520 |
| 494 if (!arc_enabled) { | 521 if (!arc_enabled) { |
| 522 // Reset any pending request to re-enable Arc. | |
| 523 reenable_arc_ = false; | |
| 495 StopArc(); | 524 StopArc(); |
| 496 RemoveArcData(); | 525 RemoveArcData(); |
| 497 return; | 526 return; |
| 498 } | 527 } |
| 499 | 528 |
| 500 if (state_ == State::ACTIVE) | 529 if (state_ == State::ACTIVE) |
| 501 return; | 530 return; |
| 502 | 531 |
| 532 if (state_ == State::DELETING_DATA_FOLDER) { | |
| 533 // Data removal request is in progress. Set flag to re-enable Arc once it is | |
| 534 // finished. | |
| 535 reenable_arc_ = true; | |
| 536 return; | |
| 537 } | |
| 538 | |
| 503 if (support_host_) | 539 if (support_host_) |
| 504 support_host_->SetArcManaged(IsArcManaged()); | 540 support_host_->SetArcManaged(IsArcManaged()); |
| 505 | 541 |
| 506 // For ARC Kiosk we skip ToS because it is very likely that near the device | 542 // For ARC Kiosk we skip ToS because it is very likely that near the device |
| 507 // there will be no one who is eligible to accept them. | 543 // there will be no one who is eligible to accept them. |
| 508 // TODO(poromov): Move to more Kiosk dedicated set-up phase. | 544 // TODO(poromov): Move to more Kiosk dedicated set-up phase. |
| 509 if (IsArcKioskMode()) | 545 if (IsArcKioskMode()) |
| 510 profile_->GetPrefs()->SetBoolean(prefs::kArcTermsAccepted, true); | 546 profile_->GetPrefs()->SetBoolean(prefs::kArcTermsAccepted, true); |
| 511 | 547 |
| 512 // If it is marked that sign in has been successfully done, then directly | 548 // If it is marked that sign in has been successfully done, then directly |
| (...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 557 // Need user's explicit Terms Of Service agreement. | 593 // Need user's explicit Terms Of Service agreement. |
| 558 StartTermsOfServiceNegotiation(); | 594 StartTermsOfServiceNegotiation(); |
| 559 } | 595 } |
| 560 | 596 |
| 561 void ArcSessionManager::ShutdownBridge() { | 597 void ArcSessionManager::ShutdownBridge() { |
| 562 arc_sign_in_timer_.Stop(); | 598 arc_sign_in_timer_.Stop(); |
| 563 playstore_launcher_.reset(); | 599 playstore_launcher_.reset(); |
| 564 terms_of_service_negotiator_.reset(); | 600 terms_of_service_negotiator_.reset(); |
| 565 android_management_checker_.reset(); | 601 android_management_checker_.reset(); |
| 566 arc_bridge_service()->RequestStop(); | 602 arc_bridge_service()->RequestStop(); |
| 567 if (state_ != State::NOT_INITIALIZED) | 603 if (state_ != State::NOT_INITIALIZED && state_ != State::DELETING_DATA_FOLDER) |
| 568 SetState(State::STOPPED); | 604 SetState(State::STOPPED); |
| 569 for (auto& observer : observer_list_) | 605 for (auto& observer : observer_list_) |
| 570 observer.OnShutdownBridge(); | 606 observer.OnShutdownBridge(); |
| 571 } | 607 } |
| 572 | 608 |
| 573 void ArcSessionManager::AddObserver(Observer* observer) { | 609 void ArcSessionManager::AddObserver(Observer* observer) { |
| 574 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); | 610 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| 575 observer_list_.AddObserver(observer); | 611 observer_list_.AddObserver(observer); |
| 576 } | 612 } |
| 577 | 613 |
| 578 void ArcSessionManager::RemoveObserver(Observer* observer) { | 614 void ArcSessionManager::RemoveObserver(Observer* observer) { |
| 579 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); | 615 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| 580 observer_list_.RemoveObserver(observer); | 616 observer_list_.RemoveObserver(observer); |
| 581 } | 617 } |
| 582 | 618 |
| 583 // This is the special method to support enterprise mojo API. | 619 // This is the special method to support enterprise mojo API. |
| 584 // TODO(hidehiko): Remove this. | 620 // TODO(hidehiko): Remove this. |
| 585 void ArcSessionManager::StopAndEnableArc() { | 621 void ArcSessionManager::StopAndEnableArc() { |
| 586 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); | 622 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| 587 DCHECK(!arc_bridge_service()->stopped()); | 623 DCHECK(!arc_bridge_service()->stopped()); |
| 588 reenable_arc_ = true; | 624 reenable_arc_ = true; |
| 589 StopArc(); | 625 StopArc(); |
| 590 } | 626 } |
| 591 | 627 |
| 592 void ArcSessionManager::StartArc() { | 628 void ArcSessionManager::StartArc() { |
| 593 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); | 629 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| 630 | |
| 631 // Arc must be started only if no pending data removal request exists. | |
| 632 DCHECK(!profile_->GetPrefs()->GetBoolean(prefs::kArcDataRemoveRequested)); | |
| 633 | |
| 594 arc_bridge_service()->RequestStart(); | 634 arc_bridge_service()->RequestStart(); |
| 595 SetState(State::ACTIVE); | 635 SetState(State::ACTIVE); |
| 596 } | 636 } |
| 597 | 637 |
| 598 void ArcSessionManager::OnArcSignInTimeout() { | 638 void ArcSessionManager::OnArcSignInTimeout() { |
| 599 LOG(ERROR) << "Timed out waiting for first sign in."; | 639 LOG(ERROR) << "Timed out waiting for first sign in."; |
| 600 OnProvisioningFinished(ProvisioningResult::OVERALL_SIGN_IN_TIMEOUT); | 640 OnProvisioningFinished(ProvisioningResult::OVERALL_SIGN_IN_TIMEOUT); |
| 601 } | 641 } |
| 602 | 642 |
| 603 void ArcSessionManager::CancelAuthCode() { | 643 void ArcSessionManager::CancelAuthCode() { |
| (...skipping 238 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 842 const ArcSessionManager::State& state) { | 882 const ArcSessionManager::State& state) { |
| 843 switch (state) { | 883 switch (state) { |
| 844 case ArcSessionManager::State::NOT_INITIALIZED: | 884 case ArcSessionManager::State::NOT_INITIALIZED: |
| 845 return os << "NOT_INITIALIZED"; | 885 return os << "NOT_INITIALIZED"; |
| 846 case ArcSessionManager::State::STOPPED: | 886 case ArcSessionManager::State::STOPPED: |
| 847 return os << "STOPPED"; | 887 return os << "STOPPED"; |
| 848 case ArcSessionManager::State::SHOWING_TERMS_OF_SERVICE: | 888 case ArcSessionManager::State::SHOWING_TERMS_OF_SERVICE: |
| 849 return os << "SHOWING_TERMS_OF_SERVICE"; | 889 return os << "SHOWING_TERMS_OF_SERVICE"; |
| 850 case ArcSessionManager::State::CHECKING_ANDROID_MANAGEMENT: | 890 case ArcSessionManager::State::CHECKING_ANDROID_MANAGEMENT: |
| 851 return os << "CHECKING_ANDROID_MANAGEMENT"; | 891 return os << "CHECKING_ANDROID_MANAGEMENT"; |
| 892 case ArcSessionManager::State::DELETING_DATA_FOLDER: | |
| 893 return os << "DELETING_DATA_FOLDER"; | |
| 852 case ArcSessionManager::State::ACTIVE: | 894 case ArcSessionManager::State::ACTIVE: |
| 853 return os << "ACTIVE"; | 895 return os << "ACTIVE"; |
| 854 } | 896 } |
| 855 | 897 |
| 856 // Some compiler reports an error even if all values of an enum-class are | 898 // Some compiler reports an error even if all values of an enum-class are |
| 857 // covered indivisually in a switch statement. | 899 // covered indivisually in a switch statement. |
| 858 NOTREACHED(); | 900 NOTREACHED(); |
| 859 return os; | 901 return os; |
| 860 } | 902 } |
| 861 | 903 |
| 862 } // namespace arc | 904 } // namespace arc |
| OLD | NEW |