shill: cellular: Add Cellular.SIMPresent property.

This CL adds Cellular.SIMPresent property to indicate whether a SIM card
is present on a GSM or LTE modem.

BUG=chromium-os:34002
TEST=Tested the following:
1. Build and run unit tests.
2. Verify that the value of Cellular.SIMPresent property correctly
   indicates the presence of a SIM card on Novatel E396 and E362 modems.
CQ-DEPEND=I01a2c1dcf00fee78ce1f9f6c51a1df76327ee044

Change-Id: Id94f8f48f66458c20a519aaefcdfe7d6a59d2e8b
Reviewed-on: https://gerrit.chromium.org/gerrit/35810
Reviewed-by: Darin Petkov <petkov@chromium.org>
Commit-Ready: Ben Chan <benchan@chromium.org>
Tested-by: Ben Chan <benchan@chromium.org>
diff --git a/cellular_capability_gsm.cc b/cellular_capability_gsm.cc
index e7b887b..49f56d2 100644
--- a/cellular_capability_gsm.cc
+++ b/cellular_capability_gsm.cc
@@ -61,7 +61,8 @@
       get_imsi_retries_(0),
       get_imsi_retry_delay_milliseconds_(kGetIMSIRetryDelayMilliseconds),
       scanning_(false),
-      scan_interval_(0) {
+      scan_interval_(0),
+      sim_present_(false) {
   SLOG(Cellular, 2) << "Cellular capability constructed: GSM";
   PropertyStore *store = cellular->mutable_store();
   store->RegisterConstString(flimflam::kSelectedNetworkProperty,
@@ -72,6 +73,7 @@
                            &provider_requires_roaming_);
   store->RegisterConstBool(flimflam::kScanningProperty, &scanning_);
   store->RegisterUint16(flimflam::kScanIntervalProperty, &scan_interval_);
+  store->RegisterConstBool(shill::kSIMPresentProperty, &sim_present_);
   HelpRegisterDerivedKeyValueStore(
       flimflam::kSIMLockStatusProperty,
       &CellularCapabilityGSM::SimLockStatusToProperty,
@@ -146,9 +148,9 @@
   ResultCallback cb_ignore_error =
       Bind(&CellularCapabilityGSM::StepCompletedCallback,
            weak_ptr_factory_.GetWeakPtr(), ResultCallback(), true, tasks);
-  // Chrome uses IMSI to determine if a SIM is present before allowing the
-  // modem to be enabled, so shill needs to obtain IMSI even before the device
-  // is enabled.
+  // Chrome checks if a SIM is present before allowing the modem to be enabled,
+  // so shill needs to obtain IMSI, as an indicator of SIM presence, even
+  // before the device is enabled.
   tasks->push_back(Bind(&CellularCapabilityGSM::GetIMSI,
                         weak_ptr_factory_.GetWeakPtr(), cb_ignore_error));
   RunNextStep(tasks);
@@ -923,9 +925,11 @@
   if (error.IsSuccess()) {
     SLOG(Cellular, 2) << "IMSI: " << imsi;
     imsi_ = imsi;
+    sim_present_ = true;
     SetHomeProvider();
     callback.Run(error);
   } else {
+    sim_present_ = false;
     if (get_imsi_retries_++ < kGetIMSIRetryLimit) {
       SLOG(Cellular, 2) << "GetIMSI failed - " << error << ". Retrying";
       base::Callback<void(void)> retry_get_imsi_cb =
diff --git a/cellular_capability_gsm.h b/cellular_capability_gsm.h
index 9fadc6a..8631aa9 100644
--- a/cellular_capability_gsm.h
+++ b/cellular_capability_gsm.h
@@ -236,6 +236,7 @@
   bool scanning_;
   uint16 scan_interval_;
   SimLockStatus sim_lock_status_;
+  bool sim_present_;
   Stringmaps apn_list_;
 
   static unsigned int friendly_service_name_id_;
diff --git a/cellular_capability_gsm_unittest.cc b/cellular_capability_gsm_unittest.cc
index c364fc3..5829ed9 100644
--- a/cellular_capability_gsm_unittest.cc
+++ b/cellular_capability_gsm_unittest.cc
@@ -370,8 +370,10 @@
   ResultCallback callback = Bind(&CellularCapabilityGSMTest::TestCallback,
                                  Unretained(this));
   EXPECT_TRUE(capability_->imsi_.empty());
+  EXPECT_FALSE(capability_->sim_present_);
   capability_->GetIMSI(callback);
   EXPECT_EQ(kIMSI, capability_->imsi_);
+  EXPECT_TRUE(capability_->sim_present_);
   capability_->imsi_.clear();
   InitProviderDB();
   capability_->GetIMSI(callback);
@@ -394,6 +396,7 @@
   ResultCallback callback = Bind(&CellularCapabilityGSMTest::TestCallback,
                                  Unretained(this));
   EXPECT_TRUE(capability_->imsi_.empty());
+  EXPECT_FALSE(capability_->sim_present_);
 
   capability_->get_imsi_retries_ = 0;
   EXPECT_EQ(CellularCapabilityGSM::kGetIMSIRetryDelayMilliseconds,
@@ -408,6 +411,7 @@
   EXPECT_EQ(CellularCapabilityGSM::kGetIMSIRetryLimit + 1,
             capability_->get_imsi_retries_);
   EXPECT_TRUE(capability_->imsi_.empty());
+  EXPECT_FALSE(capability_->sim_present_);
 }
 
 TEST_F(CellularCapabilityGSMTest, GetMSISDN) {
diff --git a/cellular_capability_universal.cc b/cellular_capability_universal.cc
index 2d2b64b..f90819c 100644
--- a/cellular_capability_universal.cc
+++ b/cellular_capability_universal.cc
@@ -128,7 +128,8 @@
       provider_requires_roaming_(false),
       scanning_supported_(true),
       scanning_(false),
-      scan_interval_(0) {
+      scan_interval_(0),
+      sim_present_(false) {
   SLOG(Cellular, 2) << "Cellular capability constructed: Universal";
   PropertyStore *store = cellular->mutable_store();
 
@@ -160,6 +161,7 @@
       flimflam::kSIMLockStatusProperty,
       &CellularCapabilityUniversal::SimLockStatusToProperty,
       NULL);
+  store->RegisterConstBool(shill::kSIMPresentProperty, &sim_present_);
   store->RegisterConstStringmaps(flimflam::kCellularApnListProperty,
                                  &apn_list_);
 }
@@ -1029,9 +1031,11 @@
     // Clear all data about the sim
     imsi_ = "";
     spn_ = "";
+    sim_present_ = false;
     OnSimIdentifierChanged("");
     OnOperatorIdChanged("");
   } else {
+    sim_present_ = true;
     scoped_ptr<DBusPropertiesProxyInterface> properties_proxy(
         proxy_factory()->CreateDBusPropertiesProxy(sim_path,
                                                    cellular()->dbus_owner()));
diff --git a/cellular_capability_universal.h b/cellular_capability_universal.h
index bb1466b..c92c99e 100644
--- a/cellular_capability_universal.h
+++ b/cellular_capability_universal.h
@@ -133,6 +133,7 @@
   FRIEND_TEST(CellularCapabilityUniversalTest, Scan);
   FRIEND_TEST(CellularCapabilityUniversalTest, ScanFailure);
   FRIEND_TEST(CellularCapabilityUniversalTest, SetHomeProvider);
+  FRIEND_TEST(CellularCapabilityUniversalTest, SimPathChanged);
   FRIEND_TEST(CellularCapabilityUniversalTest, SimPropertiesChanged);
   FRIEND_TEST(CellularCapabilityUniversalTest, StartModem);
   FRIEND_TEST(CellularCapabilityUniversalTest, StopModem);
@@ -291,6 +292,7 @@
   SimLockStatus sim_lock_status_;
   Stringmaps apn_list_;
   std::string sim_path_;
+  bool sim_present_;
   DBus::Path bearer_path_;
 
   // If the modem is not in a state to be enabled when StartModem is called,
diff --git a/cellular_capability_universal_unittest.cc b/cellular_capability_universal_unittest.cc
index f89a91e..43dc71d 100644
--- a/cellular_capability_universal_unittest.cc
+++ b/cellular_capability_universal_unittest.cc
@@ -488,6 +488,61 @@
                                        vector<string>());
 }
 
+TEST_F(CellularCapabilityUniversalTest, SimPathChanged) {
+  // Set up mock modem SIM properties
+  const char kImsi[] = "310100000001";
+  const char kSimIdentifier[] = "9999888";
+  const char kOperatorIdentifier[] = "310240";
+  const char kOperatorName[] = "Custom SPN";
+  DBusPropertiesMap sim_properties;
+  sim_properties[MM_SIM_PROPERTY_IMSI].writer().append_string(kImsi);
+  sim_properties[MM_SIM_PROPERTY_SIMIDENTIFIER].writer()
+      .append_string(kSimIdentifier);
+  sim_properties[MM_SIM_PROPERTY_OPERATORIDENTIFIER].writer()
+      .append_string(kOperatorIdentifier);
+  sim_properties[MM_SIM_PROPERTY_OPERATORNAME].writer()
+      .append_string(kOperatorName);
+
+  EXPECT_CALL(*properties_proxy_, GetAll(MM_DBUS_INTERFACE_SIM))
+      .Times(1).WillOnce(Return(sim_properties));
+
+  EXPECT_FALSE(capability_->sim_present_);
+  EXPECT_TRUE(capability_->sim_proxy_ == NULL);
+  EXPECT_EQ("", capability_->sim_path_);
+  EXPECT_EQ("", capability_->imsi_);
+  EXPECT_EQ("", capability_->sim_identifier_);
+  EXPECT_EQ("", capability_->operator_id_);
+  EXPECT_EQ("", capability_->spn_);
+
+  capability_->OnSimPathChanged(kSimPath);
+  EXPECT_TRUE(capability_->sim_present_);
+  EXPECT_TRUE(capability_->sim_proxy_ != NULL);
+  EXPECT_EQ(kSimPath, capability_->sim_path_);
+  EXPECT_EQ(kImsi, capability_->imsi_);
+  EXPECT_EQ(kSimIdentifier, capability_->sim_identifier_);
+  EXPECT_EQ(kOperatorIdentifier, capability_->operator_id_);
+  EXPECT_EQ(kOperatorName, capability_->spn_);
+
+  // Changing to the same SIM path should be a no-op.
+  capability_->OnSimPathChanged(kSimPath);
+  EXPECT_TRUE(capability_->sim_present_);
+  EXPECT_TRUE(capability_->sim_proxy_ != NULL);
+  EXPECT_EQ(kSimPath, capability_->sim_path_);
+  EXPECT_EQ(kImsi, capability_->imsi_);
+  EXPECT_EQ(kSimIdentifier, capability_->sim_identifier_);
+  EXPECT_EQ(kOperatorIdentifier, capability_->operator_id_);
+  EXPECT_EQ(kOperatorName, capability_->spn_);
+
+  capability_->OnSimPathChanged("");
+  EXPECT_FALSE(capability_->sim_present_);
+  EXPECT_TRUE(capability_->sim_proxy_ == NULL);
+  EXPECT_EQ("", capability_->sim_path_);
+  EXPECT_EQ("", capability_->imsi_);
+  EXPECT_EQ("", capability_->sim_identifier_);
+  EXPECT_EQ("", capability_->operator_id_);
+  EXPECT_EQ("", capability_->spn_);
+}
+
 TEST_F(CellularCapabilityUniversalTest, SimPropertiesChanged) {
   // Set up mock modem properties
   DBusPropertiesMap modem_properties;
diff --git a/doc/device-api.txt b/doc/device-api.txt
index 0764f8e..c5cdb29 100644
--- a/doc/device-api.txt
+++ b/doc/device-api.txt
@@ -367,6 +367,11 @@
 				time locking can be enabled (LockEnabled is
 				True).
 
+		boolean Cellular.SIMPresent [readonly]
+
+			(Cellular only) For GSM or LTE modems, indicates
+			whether a SIM card is present or not.
+
 		array{string} Cellular.SupportedCarriers [readonly]
 
 			(Cellular only) A list of supported carriers. Each