Source: js/eu_roaming_manager.js

/* globals SIMSlotManager, Notification, MozActivity, Promise,
           LazyLoader, BaseModule */
'use strict';

(function() {
  var EU_ROAMING_FILE_PATH = '/resources/eu-roaming.json';

  /**
   * EuRoamingManager observes the mobile codes of the current connected
   * operator and display a notification when necessary.
   *
   * @class EuRoamingManager
   */
  function EuRoamingManager(core) {
    this._connections = Array.slice(core.mobileConnections || []);

    this._simMobileCodes = [];
    this._curNetworkMobileCodes = [];

    this._homeOperatorList = null;
    this._foreignOperatorList = null;
  }

  BaseModule.create(EuRoamingManager, {
    name: 'EuRoamingManager',
    TAG_PREFIX: 'euRoamingNotificaton',
    EU_ROAMING_ENABLED_KEY: 'eu-roaming.enabled',
    EU_ROAMING_NOTIFICATION_STATE_KEY: 'eu-roaming.notification.state',
    NOTIFICATION_STATES: {
      UNAVAILABLE: 'unavailable',
      DISPLAYED: 'displayed',
      OPENED: 'opened'
    },

    /**
     * Starts the module.
     *
     * @memberof EuRoamingManager.prototype
     */
    _start: function() {
      // Clear existing notifications.
      this._clearNotifications();
      this._init();
    },

    /**
     * Load the EU roaming file and register related listeners when necessary.
     *
     * @memberof EuRoamingManager.prototype
     * @returns {Promise}
     */
    _init: function() {
      var that = this;

      return this._loadJSON(EU_ROAMING_FILE_PATH)
      .then(function(result) {
        if (!result) {
          return;
        }

        that._homeOperatorList = result.home;
        that._foreignOperatorList = result.foreign;

        if (that._homeOperatorList &&
            that._foreignOperatorList &&
            Object.keys(that._homeOperatorList).length > 0 &&
            Object.keys(that._foreignOperatorList).length > 0) {
          that._initValues();
          that._addSimChangeListener();
          that._addDataChangeListener();

          // Check the current state ar first.
          that._connections.forEach(
            that._tryShowEuRoamingNotification.bind(that));
        }
      });
    },

    /**
     * Initialzes values.
     *
     * @memberof EuRoamingManager.prototype
     */
    _initValues: function() {
      // Initialize the default values.
      SIMSlotManager.getSlots().forEach(function(simSlot, index) {
        this._curNetworkMobileCodes.push(null);

        var iccInfo = simSlot && simSlot.simCard && simSlot.simCard.iccInfo;
        if (iccInfo) {
          this._simMobileCodes.push({ mcc: iccInfo.mcc, mnc: iccInfo.mnc });
        } else {
          this._simMobileCodes.push(null);
        }
      }, this);
    },

    /**
     * Check if the mobile codes is a valid EU romaing home operator.
     *
     * @memberof EuRoamingManager.prototype
     * @param {String} mcc
     * @param {String} mnc
     * @returns {Boolean}
     */
    _isEURoamingHomeOperator: function(mcc, mnc) {
      if (!this._homeOperatorList || !mcc || !mnc) {
        return false;
      }
      return !!this._homeOperatorList[mcc][mnc];
    },

    /**
     * Check if the mobile codes is a valid EU romaing foreign operator.
     *
     * @memberof EuRoamingManager.prototype
     * @param {String} mcc
     * @param {String} mnc
     * @returns {Boolean}
     */
    _isEURoamingForeignOperator: function(mcc, mnc) {
      if (!this._foreignOperatorList || !mcc || !mnc) {
        return false;
      }
      return !!this._foreignOperatorList[mcc][mnc];
    },

    /**
     * Add listeners on events related to the change of the mobile codes stored
     * on the icc card.
     *
     * @memberof EuRoamingManager.prototype
     */
    _addSimChangeListener: function() {
      ['updated', 'iccinfochange', 'cardstatechange'].forEach(
        function(eventName) {
          window.addEventListener('simslot-' + eventName,
            this._onSimChanged.bind(this));
      }, this);
    },

    /**
     * Add listeners on events related to the change of the mobile codes of the
     * currently connected data network.
     *
     * @memberof EuRoamingManager.prototype
     */
    _addDataChangeListener: function() {
      this._connections.forEach(function(connection, index) {
        connection.addEventListener('datachange', function() {
          this._tryShowEuRoamingNotification(connection, index);
        }.bind(this));
      }, this);
    },

    /**
     * The function stores the mobile codes of the currently used sim card and
     * try to display the notification.
     *
     * @memberof EuRoamingManager.prototype
     */
    _onSimChanged: function(event) {
      var simSlot = event.detail;
      var iccInfo = simSlot && simSlot.simCard && simSlot.simCard.iccInfo;

      if (!iccInfo) {
        return;
      }

      this._simMobileCodes[simSlot.index] =
        { mcc: iccInfo.mcc, mnc: iccInfo.mnc };
      this._tryShowEuRoamingNotification(simSlot.conn, simSlot.index);
    },

    /**
     * The function displays the EU roaming notification when:
     *   - Roaming on the newly connected data network.
     *   - The mobile codes of the operators of the sim card are listed in the
     *     "home" section of the EU roaming file.
     *   - The mobile codes of the operators of the connected data network are
     *     listed in the "foreign" section of the EU roaming file.
     *
     * @memberof EuRoamingManager.prototype
     * @param {MobileConnection} connection
     * @param {Number} index
     * @returns {Promise}
     */
    _tryShowEuRoamingNotification: function(connection, index) {
      var data = connection.data;
      var network = data && data.network;
      if (!network) {
        return;
      }

      var simMobileCode = this._simMobileCodes[index];
      var simMcc = simMobileCode && simMobileCode.mcc;
      var simMnc = simMobileCode && simMobileCode.mnc;

      var curNetworkMobileCode = this._curNetworkMobileCodes[index];
      var curNetworkMcc = curNetworkMobileCode && curNetworkMobileCode.mcc;
      var curNetworkMnc = curNetworkMobileCode && curNetworkMobileCode.mnc;

      if (network.mcc !== curNetworkMcc || network.mnc !== curNetworkMnc) {
        // a new operator detected.
        this._curNetworkMobileCodes[index] =
          { mcc: network.mcc, mnc: network.mnc };

        if (data.roaming &&
            // check if the operator of the sim is in the EU regulation list.
            this._isEURoamingHomeOperator(simMcc, simMnc) &&
            // check if the new operator is a supported foreign operator or not.
            this._isEURoamingForeignOperator(network.mcc, network.mnc)) {
          return this._getState(this.EU_ROAMING_NOTIFICATION_STATE_KEY + index)
          .then(function(notificationState) {
            this._setState(this.EU_ROAMING_ENABLED_KEY + index, true);
            // display the notification if it has never been opened before.
            if (notificationState !== this.NOTIFICATION_STATES.OPENED) {
              this._showNotification(index);
              return this._setState(
                this.EU_ROAMING_NOTIFICATION_STATE_KEY + index,
                this.NOTIFICATION_STATES.DISPLAYED);
            }
          }.bind(this)).catch(function(err) {
            console.error(err);
          });
        } else {
          return Promise.all([
            this._setState(this.EU_ROAMING_ENABLED_KEY + index, false),
            this._setState(this.EU_ROAMING_NOTIFICATION_STATE_KEY + index,
            this.NOTIFICATION_STATES.UNAVAILABLE)
          ]).catch(function(err) {
            console.error(err);
          });
        }
      }
    },

    /**
     * Clear all notifications of EU roaming.
     *
     * @memberof EuRoamingManager.prototype
     * @returns {Promise}
     */
    _clearNotifications: function() {
      var that = this;
      return Notification.get().then(function(notifications) {
        notifications.forEach(function(notification) {
          var tag = notification && notification.tag;
          if (!tag || !tag.startsWith(that.TAG_PREFIX)) {
            return;
          }
          notification.close();
        });
      });
    },

    /**
     * Shows the EU roaming notification of a sim card.
     *
     * @memberof EuRoamingManager.prototype
     * @param {Number} serviceId
     */
    _showNotification: function(serviceId) {
      var _ = navigator.mozL10n.get;
      var iconUrl =  window.location.origin + '/style/eu_roaming_manager/' +
        'eu_roaming.png';
      var options = {
        body: _('euRoamingNotificationMsg'),
        icon: iconUrl,
        tag: this.TAG_PREFIX + serviceId,
        mozbehavior: {
          showOnlyOnce: true
        }
      };
      var notification =
        new Notification(_('euRoamingNotificationTitle'), options);

      notification.onclick = function() {
        this._triggerSettingsActivity(serviceId);
        notification.close();
      }.bind(this);

      notification.onclose = function() {
        this._setState(this.EU_ROAMING_NOTIFICATION_STATE_KEY + serviceId,
          this.NOTIFICATION_STATES.OPENED);
      }.bind(this);
    },

    /**
     * Invokes an activity for setting the EU roaming apn.
     *
     * @memberof EuRoamingManager.prototype
     * @param {Number} serviceId
     */
    _triggerSettingsActivity: function(serviceId) {
      var activity = new MozActivity({
        name: 'configure',
        data: {
          target: 'device',
          section: 'apn-list',
          options: {
            role: 'activity',
            type: 'default',
            serviceId: serviceId
          }
        }
      });
      activity.onsuccess = function() {};
    },

    _getState: function(key) {
      return new Promise(function(resolve, reject) {
        var req = navigator.mozSettings.createLock().get(key);
        req.onsuccess = function() {
          resolve(req.result[key]);
        };
        req.onerror = function() {
          reject('get state ' + key + ' error');
        };
      });
    },

    _setState: function(key, state) {
      return new Promise(function(resolve, reject) {
        var obj = {};
        obj[key] = state;
        var req = navigator.mozSettings.createLock().set(obj);
        req.onsuccess = resolve;
        req.onerror = function() {
          reject('set state ' + key + ' error');
        };
      });
    },

    _loadJSON: function(path) {
      return LazyLoader.getJSON(path).then(function(json) {
        return Promise.resolve(json);
      }, function(error) {
        return Promise.resolve(null);
      });
    }
  });
}());