dom/apps/src/OperatorApps.jsm
author Marco Castelluccio <mar.castelluccio@studenti.unina.it>
Mon, 07 Apr 2014 10:42:56 -0400
changeset 192241 a3d8c7c4f462e088c9b9cc817d6879e9a17e02a2
parent 192239 ed09e2b74d14506367c1c414d998b5799ea20bad
child 192242 bbac2a994298bf25bc92451dd791583d1b4a5737
permissions -rw-r--r--
Bug 991246 - Fix "aIdsApp is undefined" error in OperatorApps.jsm. r=cjc, a=lsblakk

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

const Cu = Components.utils;
const Cc = Components.classes;
const Ci = Components.interfaces;

this.EXPORTED_SYMBOLS = ["OperatorAppsRegistry"];

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/FileUtils.jsm");
Cu.import("resource://gre/modules/Webapps.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/osfile.jsm");
Cu.import("resource://gre/modules/AppsUtils.jsm");
Cu.import("resource://gre/modules/Task.jsm");

let Path = OS.Path;

#ifdef MOZ_B2G_RIL
XPCOMUtils.defineLazyServiceGetter(this, "iccProvider",
                                   "@mozilla.org/ril/content-helper;1",
                                   "nsIIccProvider");
#endif

function debug(aMsg) {
  //dump("-*-*- OperatorApps.jsm : " + aMsg + "\n");
}

// Single Variant source dir will be set in PREF_SINGLE_VARIANT_DIR
// preference.
// if PREF_SINGLE_VARIANT_DIR does not exist or has not value, it will use (as
// single variant source) the value of
// DIRECTORY_NAME + "/" + SINGLE_VARIANT_SOURCE_DIR value instead.
// SINGLE_VARIANT_CONF_FILE will be stored on Single Variant Source.
// Apps will be stored on an app per directory basis, hanging from
// SINGLE_VARIANT_SOURCE_DIR
const DIRECTORY_NAME = "webappsDir";
const SINGLE_VARIANT_SOURCE_DIR = "svoperapps";
const SINGLE_VARIANT_CONF_FILE  = "singlevariantconf.json";
const PREF_FIRST_RUN_WITH_SIM   = "dom.webapps.firstRunWithSIM";
const PREF_SINGLE_VARIANT_DIR   = "dom.mozApps.single_variant_sourcedir";
const METADATA                  = "metadata.json";
const UPDATEMANIFEST            = "update.webapp";
const MANIFEST                  = "manifest.webapp";
const APPLICATION_ZIP           = "application.zip";

function isFirstRunWithSIM() {
  try {
    if (Services.prefs.prefHasUserValue(PREF_FIRST_RUN_WITH_SIM)) {
      return Services.prefs.getBoolPref(PREF_FIRST_RUN_WITH_SIM);
    }
  } catch(e) {
    debug ("Error getting pref. " + e);
  }
  return true;
}

#ifdef MOZ_B2G_RIL
let iccListener = {
  notifyStkCommand: function() {},

  notifyStkSessionEnd: function() {},

  notifyCardStateChanged: function() {},

  notifyIccInfoChanged: function() {
    // TODO: Bug 927709 - OperatorApps for multi-sim
    // In Multi-sim, there is more than one client in iccProvider. Each
    // client represents a icc service. To maintain the backward compatibility
    // with single sim, we always use client 0 for now. Adding support for
    // multiple sim will be addressed in bug 927709, if needed.
    let clientId = 0;
    let iccInfo = iccProvider.getIccInfo(clientId);
    if (iccInfo && iccInfo.mcc && iccInfo.mnc) {
      debug("******* iccListener cardIccInfo MCC-MNC: " + iccInfo.mcc +
            "-" + iccInfo.mnc);
      iccProvider.unregisterIccMsg(clientId, this);
      OperatorAppsRegistry._installOperatorApps(iccInfo.mcc, iccInfo.mnc);
    }
  }
};
#endif

this.OperatorAppsRegistry = {

  _baseDirectory: null,

  init: function() {
    debug("init");
#ifdef MOZ_B2G_RIL
    if (isFirstRunWithSIM()) {
      debug("First Run with SIM");
      Task.spawn(function() {
        try {
          yield this._initializeSourceDir();
          // TODO: Bug 927709 - OperatorApps for multi-sim
          // In Multi-sim, there is more than one client in iccProvider. Each
          // client represents a icc service. To maintain the backward
          // compatibility with single sim, we always use client 0 for now.
          // Adding support for multiple sim will be addressed in bug 927709, if
          // needed.
          let clientId = 0;
          let iccInfo = iccProvider.getIccInfo(clientId);
          let mcc = 0;
          let mnc = 0;
          if (iccInfo && iccInfo.mcc) {
            mcc = iccInfo.mcc;
          }
          if (iccInfo && iccInfo.mnc) {
            mnc = iccInfo.mnc;
          }
          if (mcc && mnc) {
            this._installOperatorApps(mcc, mnc);
          } else {
            iccProvider.registerIccMsg(clientId, iccListener);
          }
        } catch (e) {
          debug("Error Initializing OperatorApps. " + e);
        }
      }.bind(this));
    } else {
      debug("No First Run with SIM");
    }
#endif
  },

  _copyDirectory: function(aOrg, aDst) {
    debug("copying " + aOrg + " to " + aDst);
    return aDst && Task.spawn(function() {
      try {
        let orgDir = Cc["@mozilla.org/file/local;1"]
                       .createInstance(Ci.nsIFile);
        orgDir.initWithPath(aOrg);
        if (!orgDir.isDirectory()) {
          return;
        }

        let dstDir = Cc["@mozilla.org/file/local;1"]
                       .createInstance(Ci.nsIFile);
        dstDir.initWithPath(aDst);
        if (!dstDir.exists()) {
          dstDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
        }

        let entries = orgDir.directoryEntries;
        while (entries.hasMoreElements()) {
          let entry = entries.getNext().QueryInterface(Ci.nsIFile);

          if (!entry.isDirectory()) {
            // Remove the file, because copyTo doesn't overwrite files.
            let dstFile = dstDir.clone();
            dstFile.append(entry.leafName);
            if(dstFile.exists()) {
              dstFile.remove(false);
            }

            entry.copyTo(dstDir, entry.leafName);
          } else {
            yield this._copyDirectory(entry.path,
                                      Path.join(aDst, entry.name));
          }
        }
      } catch (e) {
        debug("Error copying " + aOrg + " to " + aDst + ". " + e);
      }
    }.bind(this));
  },

  _initializeSourceDir: function() {
    return Task.spawn(function() {
      let svFinalDirName;
      try {
        svFinalDirName = Services.prefs.getCharPref(PREF_SINGLE_VARIANT_DIR);
      } catch(e) {
        debug ("Error getting pref. " + e);
        this.appsDir = FileUtils.getFile(DIRECTORY_NAME,
                                         [SINGLE_VARIANT_SOURCE_DIR]).path;
        return;
      }
      // If SINGLE_VARIANT_CONF_FILE is in PREF_SINGLE_VARIANT_DIR return
      // PREF_SINGLE_VARIANT_DIR as sourceDir, else go to
      // DIRECTORY_NAME + SINGLE_VARIANT_SOURCE_DIR and move all apps (and
      // configuration file) to PREF_SINGLE_VARIANT_DIR and return
      // PREF_SINGLE_VARIANT_DIR as sourceDir.
      let svFinalDir = Cc["@mozilla.org/file/local;1"]
                         .createInstance(Ci.nsIFile);
      svFinalDir.initWithPath(svFinalDirName);
      if (!svFinalDir.exists()) {
        svFinalDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
      }

      let svIndex = svFinalDir.clone();
      svIndex.append(SINGLE_VARIANT_CONF_FILE);
      if (!svIndex.exists()) {
        let svSourceDir = FileUtils.getFile(DIRECTORY_NAME,
                                            [SINGLE_VARIANT_SOURCE_DIR]);

        yield this._copyDirectory(svSourceDir.path, svFinalDirName);

        debug("removing directory:" + svSourceDir.path);
        try {
          svSourceDir.remove(true);
        } catch(ex) { }
      }

      this.appsDir = svFinalDirName;
    }.bind(this));
  },

  set appsDir(aDir) {
    debug("appsDir SET: " + aDir);
    if (aDir) {
      this._baseDirectory = Cc["@mozilla.org/file/local;1"]
          .createInstance(Ci.nsILocalFile);
      this._baseDirectory.initWithPath(aDir);
    } else {
      this._baseDirectory = null;
    }
  },

  get appsDir() {
    return this._baseDirectory;
  },

  eraseVariantAppsNotInList: function(aIdsApp) {
    if (!aIdsApp || !Array.isArray(aIdsApp)) {
      aIdsApp = [ ];
    }

    let svDir;
    try {
      svDir = this.appsDir.clone();
    } catch (e) {
      debug("eraseVariantAppsNotInList --> Error getting Dir "+
             svDir.path + ". " + e);
      return;
    }

    if (!svDir || !svDir.exists()) {
      return;
    }

    let entries = svDir.directoryEntries;
    while (entries.hasMoreElements()) {
      let entry = entries.getNext().QueryInterface(Ci.nsIFile);
      if (entry.isDirectory() && aIdsApp.indexOf(entry.leafName) < 0) {
        try{
          entry.remove(true);
        } catch(e) {
          debug("Error removing [" + entry.path + "]." + e);
        }
      }
    }
  },

  _launchInstall: function(isPackage, aId, aMetadata, aManifest) {
    if (!aManifest) {
      debug("Error: The application " + aId + " does not have a manifest");
      return;
    }

    let appData = {
      app: {
        installOrigin: aMetadata.installOrigin,
        origin: aMetadata.origin,
        manifestURL: aMetadata.manifestURL,
        manifestHash: AppsUtils.computeHash(JSON.stringify(aManifest))
      },
      appId: undefined,
      isBrowser: false,
      isPackage: isPackage,
      forceSuccessAck: true
    };

    if (isPackage) {
      debug("aId:" + aId + ". Installing as packaged app.");
      let installPack = this.appsDir.clone();
      installPack.append(aId);
      installPack.append(APPLICATION_ZIP);

      if (!installPack.exists()) {
        debug("SV " + installPack.path + " file do not exists for app " + aId);
        return;
      }

      appData.app.localInstallPath = installPack.path;
      appData.app.updateManifest = aManifest;
      DOMApplicationRegistry.confirmInstall(appData);
    } else {
      debug("aId:" + aId + ". Installing as hosted app.");
      appData.app.manifest = aManifest;
      DOMApplicationRegistry.confirmInstall(appData);
    }
  },

  _installOperatorApps: function(aMcc, aMnc) {
    Task.spawn(function() {
      debug("Install operator apps ---> mcc:"+ aMcc + ", mnc:" + aMnc);
      if (!isFirstRunWithSIM()) {
        debug("Operator apps already installed.");
        return;
      }

      let aIdsApp = yield this._getSingleVariantApps(aMcc, aMnc);
      debug("installOperatorApps --> aIdsApp:" + JSON.stringify(aIdsApp));
      for (let i = 0; i < aIdsApp.length; i++) {
        let aId = aIdsApp[i];
        let aMetadata = yield AppsUtils.loadJSONAsync(
                           Path.join(this.appsDir.path, aId, METADATA));
        if (!aMetadata) {
          debug("Error reading metadata file");
          return;
        }

        debug("metadata:" + JSON.stringify(aMetadata));
        let isPackage = true;
        let manifest;
        let manifests = [UPDATEMANIFEST, MANIFEST];
        for (let j = 0; j < manifests.length; j++) {
          manifest = yield AppsUtils.loadJSONAsync(
                        Path.join(this.appsDir.path, aId, manifests[j]));

          if (!manifest) {
            isPackage = false;
          } else {
            break;
          }
        }
        if (manifest) {
          this._launchInstall(isPackage, aId, aMetadata, manifest);
        } else {
          debug ("Error. Neither " + UPDATEMANIFEST + " file nor " + MANIFEST +
                 " file for " + aId + " app.");
        }
      }
      this.eraseVariantAppsNotInList(aIdsApp);
      Services.prefs.setBoolPref(PREF_FIRST_RUN_WITH_SIM, false);
      Services.prefs.savePrefFile(null);
    }.bind(this)).then(null, function(aError) {
        debug("Error: " + aError);
    });
  },

  _getSingleVariantApps: function(aMcc, aMnc) {

    function normalizeCode(aCode) {
      let ncode = "" + aCode;
      while (ncode.length < 3) {
        ncode = "0" + ncode;
      }
      return ncode;
    }

    return Task.spawn(function*() {
      let key = normalizeCode(aMcc) + "-" + normalizeCode(aMnc);
      let file = Path.join(this.appsDir.path, SINGLE_VARIANT_CONF_FILE);
      let aData = yield AppsUtils.loadJSONAsync(file);

      if (!aData || !(key in aData)) {
        return [];
      }

      return aData[key];
    }.bind(this));
  }
};

OperatorAppsRegistry.init();