exports.isAppInstalled = function (req, res) {

  if (checkMissingParams(req, res, {bundleId: req.body.bundleId}, true)) {

    req.device.isAppInstalled(req.body.bundleId, function (error, stdout) {

      if (error !== null) {

        respondSuccess(req, res, false);

      } else {

        // We're examining the type of stdout because `isAppInstalled` uses

        // node-idevice for real iOS devices.  node-idevice passes a boolean

        // value in the second parameter of the callback function.  Other

        // functions pass back an array.  Changing the parameter type of the

        // other functions is a deeper and more dangerous change than just

        // type-checking here.

        if ((req.appium.args.udid && req.appium.args.udid.length === 40) ||

            (typeof stdout === "boolean" && stdout) ||

            (typeof stdout[0] !== "undefined")) {

          respondSuccess(req, res, true);

        } else {

          respondSuccess(req, res, false);

        }

      }

    });

  }

};

 

exports.startActivity = function (req, res) {

  var onErr = function (err) {

    respondError(req, res, "Unable to launch the app: " + err);

  };

 

  // 8/21/14: currently not supported on all devices

  if (!req.device.startApp) {

    onErr(new NotImplementedError());

  } else {

    req.device.startApp(req.body, function (err) {

      if (err) return onErr(err);

      respondSuccess(req, res, "Successfully launched the app.");

    });

  }

};

 

exports.launchApp = function (req, res) {

  var onErr = function (err) {

    respondError(req, res, "Unable to launch the app: " + err);

  };

  req.device.start(function (err) {

    if (err) return onErr(err);

    respondSuccess(req, res, "Successfully launched the app.");

  }, function () {

    onErr(new Error("UiAutomator died"));

  });

};

 

exports.closeApp = function (req, res) {

  req.device.stop(function () {

    respondSuccess(req, res, "Successfully closed the [" + req.device.args.app + "] app.");

  }, function () {

    respondError(req, res, "Something went wrong whilst closing the [" + req.device.args.app + "] app.");

  });

};

 

exports.createSession = function (req, res) {

  if (typeof req.body === 'string') {

    req.body = JSON.parse(req.body);

  }

 

  logger.info('Client User-Agent string:', req.headers['user-agent']);

 

  var next = function (reqHost, sessionId) {

    safely(req, function () {

      res.set('Location', "http://" + reqHost + "/wd/hub/session/" + sessionId);

      res.status(303).send("Appium session started with sessionId " + sessionId);

    });

  };

  if (req.appium.preLaunched && req.appium.sessionId) {

    req.appium.preLaunched = false;

    next(req.headers.host, req.appium.sessionId, req.appium.device, true);

  } else {

    req.appium.start(req.body.desiredCapabilities, function (err, instance) {

      if (err) {

        logger.error("Failed to start an Appium session, err was: " + err);

        logger.debug(err.stack);

        delete err.stack;

        respondError(req, res, status.codes.SessionNotCreatedException, err);

      } else {

        logger.debug("Appium session started with sessionId " + req.appium.sessionId);

        next(req.headers.host, req.appium.sessionId, instance);

      }

    });

  }

};

 

exports.getSession = function (req, res) {

  // Return a static JSON object to the client

  respondSuccess(req, res, req.device.capabilities);

};

 

exports.getSessions = function (req, res) {

  var sessions = [];

  if (req.appium.sessionId !== null) {

    sessions.push({

      id: req.appium.sessionId

    , capabilities: req.device.capabilities

    });

  }

 

  respondSuccess(req, res, sessions);

};

 

exports.reset = function (req, res) {

  req.appium.reset(getResponseHandler(req, res));

};

 

exports.lock = function (req, res) {

  var seconds = req.body.seconds;

  if (checkMissingParams(req, res, {seconds: seconds})) {

    req.device.lock(seconds, getResponseHandler(req, res));

  }

};

 

exports.unlock = function (req, res) {

  if (req.device.unlock) {

    req.device.unlock(getResponseHandler(req, res));

  } else {

    notYetImplemented(req, res);

  }

};

 

exports.isLocked = function (req, res) {

  req.device.isLocked(getResponseHandler(req, res));

};

 

exports.background = function (req, res) {

  var seconds = req.body.seconds;

  if (checkMissingParams(req, res, {seconds: seconds})) {

    req.device.background(seconds, getResponseHandler(req, res));

  }

};

 

exports.deleteSession = function (req, res) {

  req.appium.stop(getResponseHandler(req, res));

};

 

exports.equalsElement = function (req, res) {

  var element = req.params.elementId

    , other = req.params.otherId;

 

  req.device.equalsWebElement(element, other, getResponseHandler(req, res));

};

 

exports.findElements = function (req, res) {

  var strategy = req.body.using

    , selector = req.body.value;

 

  if (checkMissingParams(req, res, {strategy: strategy, selector: selector}, true)) {

    req.device.findElements(strategy, selector, getResponseHandler(req, res));

  }

};

 

exports.findElement = function (req, res) {

  var strategy = req.body.using

    , selector = req.body.value;

 

  if (checkMissingParams(req, res, {strategy: strategy, selector: selector}, true)) {

    req.device.findElement(strategy, selector, getResponseHandler(req, res));

  }

};

 

exports.findElementFromElement = function (req, res) {

  var element = req.params.elementId

    , strategy = req.body.using

    , selector = req.body.value;

 

  req.device.findElementFromElement(element, strategy, selector, getResponseHandler(req, res));

};

 

exports.findElementsFromElement = function (req, res) {

  var element = req.params.elementId

    , strategy = req.body.using

    , selector = req.body.value;

 

  req.device.findElementsFromElement(element, strategy, selector, getResponseHandler(req, res));

};

 

exports.setValue = function (req, res) {

  var elementId = req.params.elementId

      // spec says value attribute is an array of strings;

      // let's turn it into one string

    , value = req.body.value.join('');

 

  req.device.setValue(elementId, value, getResponseHandler(req, res));

};

 

exports.replaceValue = function (req, res) {

  var elementId = req.params.elementId

      // spec says value attribute is an array of strings;

      // let's turn it into one string

    , value = req.body.value.join('');

 

  req.device.replaceValue(elementId, value, getResponseHandler(req, res));

};

 

exports.performTouch = function (req, res) {

  // touch actions do not work in webview

  if (req.device.isWebContext()) {

    return notYetImplemented(req, res);

  }

 

  // first, assume that we are getting and array of gestures

  var gestures = req.body;

 

  // some clients, like Python, send an object in which there is an `actions`

  // property that is the array of actions

  // get the gestures from there if we don't already have an array

  if (gestures && !Array.isArray(gestures)) {

    gestures = gestures.actions;

  }

 

  // press-wait-moveTo-release is `swipe`, so use native method

  if (gestures.length === 4 &&

      gestures[0].action === 'press' &&

      gestures[1].action === 'wait' &&

      gestures[2].action === 'moveTo' &&

      gestures[3].action === 'release') {

      return exports.mobileSwipe(req, res, gestures);

  }

 

  req.device.performTouch(gestures, getResponseHandler(req, res));

};

 

exports.performMultiAction = function (req, res) {

  // touch actions do not work in webview

  if (req.device.isWebContext()) {

    return notYetImplemented(req, res);

  }

 

  var elementId = req.body.elementId;

  var actions = req.body.actions;

 

  if (actions.length === 0) {

    return respondError(req, res, status.codes.UnknownError.code,

      new Error("Unable to perform Multi Pointer Gesture with no actions."));

  }

 

  req.device.performMultiAction(elementId, actions, getResponseHandler(req, res));

};

 

exports.doClick = function (req, res) {

  var elementId = req.params.elementId || req.body.element;

  req.device.click(elementId, getResponseHandler(req, res));

};

 

exports.touchLongClick = function (req, res) {

  var element = req.body.element;

  var x = req.body.x;

  var y = req.body.y;

  var duration = req.body.duration;

 

  if (element && checkMissingParams(req, res, {element: element}, true)) {

    req.device.touchLongClick(element, x, y, duration, getResponseHandler(req, res));

  } else if (checkMissingParams(req, res, {x: x, y: y}, true)) {

    req.device.touchLongClick(element, x, y, duration, getResponseHandler(req, res));

  }

};

 

exports.touchDown = function (req, res) {

  var element = req.body.element;

  var x = req.body.x;

  var y = req.body.y;

 

  if (element && checkMissingParams(req, res, {element: element}, true)) {

    req.device.touchDown(element, x, y, getResponseHandler(req, res));

  } else if (checkMissingParams(req, res, {x: x, y: y}, true)) {

    req.device.touchDown(element, x, y, getResponseHandler(req, res));

  }

};

 

exports.touchUp = function (req, res) {

  var element = req.body.element;

  var x = req.body.x;

  var y = req.body.y;

 

  if (element && checkMissingParams(req, res, {element: element}, true)) {

    req.device.touchUp(element, x, y, getResponseHandler(req, res));

  } else if (checkMissingParams(req, res, {x: x, y: y}, true)) {

    req.device.touchUp(element, x, y, getResponseHandler(req, res));

  }

};

 

exports.touchMove = function (req, res) {

  var element = req.body.element;

  var x = req.body.x;

  var y = req.body.y;

 

  if (element && checkMissingParams(req, res, {element: element}, true)) {

    req.device.touchMove(element, x, y, getResponseHandler(req, res));

  } else if (checkMissingParams(req, res, {x: x, y: y}, true)) {

    req.device.touchMove(element, x, y, getResponseHandler(req, res));

  }

};

 

exports.mobileTap = function (req, res) {

  req.body = _.defaults(req.body, {

    tapCount: 1

  , touchCount: 1

  , duration: 0.1

  , x: 0.5

  , y: 0.5

  , element: null

  });

  var tapCount = req.body.tapCount

    , touchCount = req.body.touchCount

    , duration = req.body.duration

    , element = req.body.element

    , x = req.body.x

    , y = req.body.y;

 

  req.device.complexTap(tapCount, touchCount, duration, x, y, element, getResponseHandler(req, res));

};

 

exports.mobileFlick = function (req, res) {

  req.body = _.defaults(req.body, {

    touchCount: 1

  , startX: 0.5

  , startY: 0.5

  , endX: 0.5

  , endY: 0.5

  , element: null

  });

  var touchCount = req.body.touchCount

    , element = req.body.element

    , startX = req.body.startX

    , startY = req.body.startY

    , endX = req.body.endX

    , endY = req.body.endY;

 

  req.device.flick(startX, startY, endX, endY, touchCount, element, getResponseHandler(req, res));

};

 

exports.mobileDrag = function (req, res) {

  req.body = _.defaults(req.body, {

    startX: 0.5

  , startY: 0.5

  , endX: 0.5

  , endY: 0.5

  , duration: 1

  , touchCount: 1

  , element: null

  , destEl: null

  });

  var touchCount = req.body.touchCount

    , element = req.body.element

    , destEl = req.body.destEl

    , duration = req.body.duration

    , startX = req.body.startX

    , startY = req.body.startY

    , endX = req.body.endX

    , endY = req.body.endY;

 

  req.device.drag(startX, startY, endX, endY, duration, touchCount, element, destEl, getResponseHandler(req, res));

};

 

var _getSwipeTouchDuration = function (waitGesture) {

  // the touch action api uses ms, we want seconds

  // 0.8 is the default time for the operation

  var duration = 0.8;

 

  if (typeof waitGesture.options.ms !== 'undefined' && waitGesture.options.ms) {

    duration = waitGesture.options.ms / 1000;

    if (duration === 0) {

      // set to a very low number, since they wanted it fast

      // but below 0.1 becomes 0 steps, which causes errors

      duration = 0.1;

    }

  }

  return duration;

};

 

exports.mobileSwipe = function (req, res, gestures) {

 

  var getCoordDefault = function (val) {

    // going the long way and checking for undefined and null since

    // we can't be assured `elId` is a string and not an int. Same

    // thing with destElement below.

    return hasValue(val) ? val : 0.5;

  };

 

  var touchCount = req.body.touchCount || 1

    , startX =  getCoordDefault(gestures[0].options.x)

    , startY = getCoordDefault(gestures[0].options.y)

    , endX = getCoordDefault(gestures[2].options.x)

    , endY = getCoordDefault(gestures[2].options.y)

    , duration = _getSwipeTouchDuration(gestures[1])

    , element = gestures[0].options.element

    , destElement = gestures[2].options.element || gestures[0].options.element;

 

  // there's no destination element handling in bootstrap and since it applies to all platforms, we handle it here

  if (hasValue(destElement)) {

    req.device.getLocation(destElement, function (err, locResult) {

      if (err) {

        respondError(req, res, err);

      } else {

        req.device.getSize(destElement, function (er, sizeResult) {

          if (er) {

            respondError(req, res, er);

          } else {

 

            var offsetX = (Math.abs(endX) < 1 && Math.abs(endX) > 0) ?

                      sizeResult.value.width * endX :

                      endX;

            var offsetY = (Math.abs(endY) < 1 && Math.abs(endY) > 0) ?

                      sizeResult.value.height * endY :

                      endY;

 

            var destX = locResult.value.x + offsetX;

            var destY = locResult.value.y + offsetY;

 

            // if the target element was provided, the coordinates for the destination need to be relative to it.

            if (hasValue(element)) {

              req.device.getLocation(element, function (e, firstElLocation) {

                if (e) {

                  respondError(req, res, e);

                } else {

                  destX -= firstElLocation.value.x;

                  destY -= firstElLocation.value.y;

 

                  req.device.swipe(startX, startY, destX, destY, duration, touchCount, element, getResponseHandler(req, res));

                }

              });

            } else {

              req.device.swipe(startX, startY, destX, destY, duration, touchCount, element, getResponseHandler(req, res));

            }

          }

        });

      }

    });

  } else {

    req.device.swipe(startX, startY, endX, endY, duration, touchCount, element, getResponseHandler(req, res));

  }

};