Source: device.js

/**
 * Mobizen - Mobizen/Device
 *
 * IMPORTANT NOTE: This file is licensed only for use in providing the RSUPPORT services,
 *
 * @copyright 2015 RSUPPORT CO., LTD. All rights reserved.
 * @license Dedicated to the public domain.
 * @author Joon Kyoung <firejune@gmail.com>
 *
 */

(function($, exports, undefined) {
  "use strict";

  /**
   * @typedef {Mobizen}
   */
  var Mobizen = exports.Mobizen || (exports.Mobizen = {})
  /**
   * @typedef {Mobizen.i18n|function}
   */
    , i18n = Mobizen.i18n || function() {};

  /**
   * Mobizen/Device API Provider
   * @constructs Device
   * @memberof Mobizen
   * @param {Mobizen.<modeName>} mobizen - parent object
   *
   * @requires {@linkplain Mobizen.Device.Screen}
   * @requires {@linkplain Mobizen.Device.Skin}
   * @requires {@linkplain Mobizen.Device.Whiteboard}
   * @requires {@linkplain Mobizen.Device.FullScreen}
   * @requires {@linkplain Mobizen.Device.InputText}
   */
  function Device(mobizen) {
    console.info('Mobizen.Device');

    /**
     * @typedef {Mobizen.<modeName>}
     * @private
     */
    this.m = mobizen;
    /**
     * @typedef {Mobizen.Apps}
     * @private
     */
    this.a = mobizen.apps;
    /**
     * A provider of Mobizen/Socket API.
     * @typedef {Mobizen.Socket}
     * @private
     */
    this.s = mobizen.socket;
    /**
     * @typedef {Mobizen.Tools}
     * @private
     */
    this.t = mobizen.tools; //=> undefined

    // open request to screen channel
    this.s.data.open('screen');

    /**
     * @typedef {Mobizen.UI~Overlay}
     */
    this.overlay = mobizen.ui.overlay;
    /**
     * @typedef {Mobizen.UI~Shortcut}
     */
    this.shortcut = mobizen.ui.shortcut;

    /**
     * @typedef {Mobizen.Socket#specific}
     */
    this.agent = mobizen.socket.specific;

    /**
     * @type {string}
     * @private
     */
    this._state = 'idle';
    /**
     * @type {number}
     * @private
     */
    this._rotate = 0;
    /**
     * @type {boolean}
     * @private
     */
    this._activate = false;
    /**
     * @type {number}
     */
    this.callState = 0;

    /**
     * Link to 'device' property of {@link Mobizen#$}
     * @type {jQueryObject}
     */
    this.$device = mobizen.$.device;
    /**
     * Link to 'rswp' property of {@link Mobizen#$}
     * @type {jQueryObject}
     */
    this.$rswp = mobizen.$.rswp;
    /**
     * @type {jQueryObject}
     */
    this.$widget = $('#widget, #remote, #devicetool');

    /**
     * @typedef {Mobizen.Device.Screen}
     */
    this.screen = new Device.Screen(this.s, this.s.specific);

    /**
     * @typedef {Mobizen.Device.Skin}
     */
    this.skin = new Device.Skin(this);

    this.$rswp.bind('screenRotated', function(event, data) {
      //console.log('trigger.screenRotated', event, data);
      this.rotate(data.degree);
    }.bind(this)).bind('screenConnected', function(event, data) {
      this.skin.adjust(data);
    }.bind(this));

    /*
    this.$paste = $.paste().appendTo(this.$device)
      .on('pasteImage', function(ev, data){
        $('<p>image(' + data.width + 'x' + data.height + '): <img src="' + data.dataURL + '" />.</p>').appendTo(el);
      })
      .on('pasteText', function(ev, data) {
        this.s.data.request('input.paste', {text: data.text});
      }.bind(this));
    */

    this.m.$.modules.find('.blocker,.popout')
      .add(this.m.$.viewer.find('.blocker,.popout')).click(function(event) {
        if ($(event.target).parents('#device').length) {
          return;
        }
        this.popdown();
      }.bind(this));

    this.$rswp.find('.offscreen').on('mouseup mousedown', function(event) {
      // FIXED #17685
      if (this.$rswp.hasClass('off')) {
        if (event.type == 'mousedown') this.s.data.send('wakeup');
      } else {
        this.$rswp.trigger('MONKEY_POWER', event.type);
      }
      return false;
    }.bind(this));

    this._monkey();
    this._setup();
  }

  Device.prototype = /** @lends Mobizen.Device# */ {
    /**
     * @method
     * @private
     */
    _monkey: function() {
      var that = this
        // FIXED #38907
        , tempAccess;

      exports.testKey = function(val) {
        var key = that.screen.sendKey(val, 'down');

        if (key) {
          setTimeout(function() {
            that.screen.sendKey(val, 'up');
          }, 1);
        }

        return key;
      };

      $.each([
        'BACK', 'HOME', 'APP_SWITCH', 'MENU', 'SEARCH',
        'POWER', 'VOLUME_UP', 'VOLUME_DOWN', 'VOLUME_MUTE'
      ], function(idx, val) {
        var lastType = null;


        that.$rswp.bind('MONKEY_' + val, function(event, type) {
          type = type.replace('mouse', '');

          if (type == 'leave') {
            if (lastType == 'down') {
              type = 'up';
            } else {
              return false;
            }
          }

          if (val == 'POWER' && type == 'down') {
            tempAccess = that.screen.active;

            // FIXED #39556
            // 아래 조건문 주석하면 안됨 앱 실행시 settings.powersave가 작동안함
            if (!that.screen.active) {
              that.screen.active = true;
            }

            that._powerDelayPended = true;
          }

          that.screen.sendKey(val, lastType = type);

          if (val == 'POWER' && type == 'up' && !tempAccess) {
            that.screen.active = tempAccess;
          }
        });
      });

      $('#remote, #device')
        .on('mouseup mousedown mouseleave', '[data-role="DEVICE_BUTTON_POWER"]', function(event) {
          // FIXED #17685
          if (that.$rswp.hasClass('off')) {
            if (event.type == 'mousedown') {
              that.s.data.send('wakeup');
            }
          } else {
            that.$rswp.trigger('MONKEY_POWER', event.type);
          }
        })
        .on('mouseup mousedown mouseleave', '[data-role="DEVICE_BUTTON_VOLUME_UP"]', function(event) {
          that.$rswp.trigger('MONKEY_VOLUME_UP', event.type);
        })
        .on('mouseup mousedown mouseleave', '[data-role="DEVICE_BUTTON_VOLUME_DOWN"]', function(event) {
          that.$rswp.trigger('MONKEY_VOLUME_DOWN', event.type);
        })
        .on('mouseup mousedown mouseleave', '[data-role="DEVICE_BUTTON_BACK"]', function(event) {
          that.$rswp.trigger('MONKEY_BACK', event.type);
        })
        .on('mouseup mousedown mouseleave', '[data-role="DEVICE_BUTTON_HOME"]', function(event) {
          that.$rswp.trigger('MONKEY_HOME', event.type);
        })
        .on('mouseup mousedown mouseleave', '[data-role="DEVICE_BUTTON_RECENTS"]', function(event) {
          that.$rswp.trigger('MONKEY_APP_SWITCH', event.type);
        })
        .on('mouseup mousedown mouseleave', '[data-role="DEVICE_BUTTON_MENU"]', function(event) {
          that.$rswp.trigger('MONKEY_MENU', event.type);
        })
        .on('mouseup mousedown mouseleave', '[data-role="DEVICE_BUTTON_SEARCH"]', function(event) {
          that.$rswp.trigger('MONKEY_SEARCH', event.type);
        });

      that.$device.on('mouseenter touchstart', function() {
        if (!that._activate && !that.$rswp.hasClass('off')) {
          that.activate(true);
        }
      });

      that.m.$.win.bind('mousedown blur', function(e) {
        var $target = $(e.target)
          , fullscreen = that.act('fullscreen')
          , rswp = $target.parents('#rswp').length
          , remote = $target.parents('#remote').length
          , device = $target.parents('#device').length;

        if (that._activate && !fullscreen && !(rswp || device || remote)) {
          that.activate(false);
        }

        if (!that._activate && (fullscreen || rswp || device || remote)) {
          that.activate(true);
        }
      });
    },

    /**
     * 제어 도구 설정
     * @method
     * @private
     */
    _setup: function() {
      var that = this
        , $widget = this.$widget
        , resizeTimer = null;

      $widget.on('click', '.widget-command', function() {
        var $el = $(this);

        if ( $el.hasClass('disable')
          // FIXED #21685
          || $el.hasClass('dim') && !that._recording
          || !that.screen.started
          // FIXED #33472
          || that._powerDelayPended) return;

        var className = $el.attr('class').match(/record|snapshot|whiteboard|fullscreen|keyboard|hd|close/)[0];
        $el = $widget.find('.' + className);

        switch(className) {
          case 'record':
            if ($el.hasClass('_proc')) {
              return;
            }

            $el.addClass('_proc');
            that._recording = !that._recording;

            // FIXED #34014
            if (that._recording && that.act('whiteboard')) {
              $widget.find('.whiteboard').first().click();
            }

            setTimeout(function() {
              that.s.data.request('record', {
                command: that._recording ? 0 : 1,
                option: {
                  useWaterMark: true,
                  useGPU: true,
                  ratio: 0.7,
                  filePath: "/sdcard/mobizen",
                  fileName: "mobizen.mp4",
                  bitrate: 102400,
                  useMicAudioRecord: true
                }
              }).on('end', that.record.bind(that));
            }, 10);
            break;

          case 'snapshot':
            that.$rswp.fadeOut(100).fadeIn(100);
            $el.toggleClass('off', false);
            setTimeout(function() {
              that.screen.send('capture');
              $el.toggleClass('off', true);
              $el.filter('[data-fn]').removeClass('off');
            }, 100);
            break;

          case 'whiteboard':
            $el.toggleClass('off check');
            $el.filter('[data-fn]').removeClass('off');
            if (that.act('whiteboard')) {
              that.whiteboard.close();
            } else {
              // create a instance of whiteboard
              if (!that.whiteboard) {
                /**
                 * @typedef {Mobizen.Device.Whiteboard} Mobizen.Device#whiteboard
                 */
                that.whiteboard = new Device.Whiteboard(that);
              }

              that.whiteboard.open();
            }
            break;

          case 'hd':
            if (that.screen.encodeType != 'D') {
              that.screen.params.useHighDefinition = !that.screen.params.useHighDefinition;
              that.screen.viewport(true);
              $el.toggleClass('off', !that.screen.hd);
            }
            break;

          case 'fullscreen':
            // create a instance of fullscreen
            if (!that.fullscreen) {
              /**
               * @typedef {Mobizen.Device.FullScreen} Mobizen.Device#fullscreen
               */
              that.fullscreen = new Device.FullScreen(that);
            }
            /* falls through */
          case 'close':
            that.fullscreen[that.act('fullscreen') && 'close' || 'open']();
            break;

          case 'keyboard':
            // create a instance of inputtext
            if (!that.inputtext) {
              /**
               * @typedef {Mobizen.Device.InputText} Mobizen.Device#inputtext
               */
              that.inputtext = new Device.InputText(that);
            }

            that.inputtext[that.inputtext.enabled && 'close' || 'open']();
            break;
        }
      });

      // third cancel
      that.shortcut.addEvent('esc', function() {
        if (that.a.running) {
          return;
        }

        if (that._recording) {
          $widget.find('.record').first().click();
          return true;
        }

        if (that.act('whiteboard')) {
          $widget.find('.whiteboard').first().click();
          return true;
        }

        if (that.act('fullscreen')) {
          $widget.find('.close').click();
          return true;
        }

        if (that.act('screen')) {
          that.activate(false);
          return true;
        }
      }, 'device.widget: record, whiteboard, fullscreen, devicefocus');

      this.m.$.win.resize(function() {
        clearTimeout(resizeTimer);
        resizeTimer = setTimeout(function() {
          this.fullscreen && this.fullscreen.update();
          this.t && this.t.notification.resize();
        }.bind(this), 400);

        if (!this.act('fullscreen')) {
          this._state = 'resize';
        }
      }.bind(this));

      this.$device.on('transitionend webkitTransitionEnd', function _TRANSITIONEND(evt) {
        var propertyName = evt.originalEvent.propertyName || '';

        // remove prefix keyword
        if (propertyName.match('transform')) propertyName = 'transform';

        // a case of rescale
        if (propertyName == 'transform' && (this._state == 'resize' || this._state == 'rotate')) {

          // NOTE: be careful order
          !this._recording && this.screen.viewport();
          this.whiteboard && this.whiteboard.update();

          this._state = 'idle';
          //console.debug('$device.transitionend', propertyName, this._state);
        // a case of popup
        } else if (propertyName == 'left' && this._state == 'popup') {
          //var viewScale = this.$device.transform();
          this.$panel.addClass('popout');
          this.$popout.append(this.$device);
          //this.$popout.transform(viewScale).append(this.$device);

          setTimeout(function() {
            this.$device.removeClass('slide-out');
            this.resume();
          }.bind(this), 0);

          this._state = 'idle';
          console.debug('$device.transitionend', propertyName, this._state);
        }
      }.bind(this));

      // Set the name of the hidden property and the change event for visibility
      var hidden, visibilityChange;
      if (typeof document.hidden !== "undefined") { // Opera 12.10 and Firefox 18 and later support
        hidden = "hidden";
        visibilityChange = "visibilitychange";
      } else if (typeof document.mozHidden !== "undefined") {
        hidden = "mozHidden";
        visibilityChange = "mozvisibilitychange";
      } else if (typeof document.msHidden !== "undefined") {
        hidden = "msHidden";
        visibilityChange = "msvisibilitychange";
      } else if (typeof document.webkitHidden !== "undefined") {
        hidden = "webkitHidden";
        visibilityChange = "webkitvisibilitychange";
      }

      document.addEventListener(visibilityChange, function() {
        // FIXED #28202
        this[document[hidden] ? 'pause' : 'resume']();
        /** @type {boolean} */
        this.hidden = hidden;
      }.bind(this), false);
    },

    /**
     * @method
     */
    unrecorded: function() {
      this.$widget.find('.record').addClass('disable');
    },

    /**
     * @method
     */
    uncaptured: function() {
      this.$widget.find('.snapshot').addClass('disable');
    },

    /**
     * @method
     */
    uncontrolled: function(init) {
      if (init === true) {
        console.debug('uncontrolled.init', this.screen);
        //this.screen.sendKey = this.uncontrolled.bind(this);
        //this.screen.sendWheel = this.uncontrolled.bind(this);
        this.screen.sendMouse = this.uncontrolled.bind(this);
        // FIXED #38206
        this.screen.mouse.sendMouse = this.uncontrolled.bind(this);
      } else if (!this.$rswp.hasClass('uncontroll')) {
        this.$rswp.addClass('unsupport uncontroll').removeClass('next');
        this.$rswp.find('label.uncontroll input.button').off().on('click', function(e) {
          var $button = $(e.target);
          if ($button.hasClass('install')) {
            this.$rswp.addClass('next');
            this.s.data.send('device.update');
          } else if ($button.is(':visible')) {
            this.$rswp.removeClass('unsupport uncontroll');
          }
        }.bind(this));
      }
    },

    /**
     * @param {Error} err - error
     */
    unsupported: function(err) {
      console.log('Device.unsupported', err);

      this.agent.unsupported = true;
      this.screen.disabled = true;

      this.$widget.addClass('disable')
        .find('.snapshot, .record, .whiteboard, .fullscreen, .keyboard')
        .addClass('disable');

      this.$rswp.removeClass('loading').addClass('unsupport');
      this.$device.addClass('disable');
      this.pause();

      if (err) {
        this.$rswp.addClass('error')
          .find('label.error span').html(err.code)
          .end().find('label.error p').html(err.text);
      }
    },

    /**
     * @listens Mobizen.Socket.Channel.Data#record․end
     * @param {object} data - record result
     */
    record: function(data) {
      console.debug('Device.record', data);

      this.$widget.find('.record').removeClass('_proc');

      // FIXED #20851, #27487
      if (data.result != 200 && !data.command || data.command) {
        this._recording = false;
        if (!this.a.running || this._popped) {
          this.resume();
        }

        if (!data.command) return;
      }

      var $el = this.$widget.find('.record')
        , _recording = this._recording = !data.command;

      if (_recording) {
        this.activate(false);
      }

      $el.toggleClass('off', !_recording)
        .find('i')
        .toggleClass('icon-pause', _recording)
        .toggleClass('icon-facetime-video', !_recording);

      $el.filter('[data-fn]')
        .removeClass('off')
        .toggleClass('check', _recording);

      this.$widget
        .find('.snapshot, .whiteboard, .fullscreen, .keyboard')
        .toggleClass('disable', _recording);

      this.$rswp.toggleClass('recording', _recording);
      this.$device.toggleClass('disable', _recording);

      if (!_recording) {
        $el.find('label').html('<span>'+ i18n('device.record')+ '</span>');
        this.recordInterval && clearInterval(this.recordInterval);

      } else {

        var $label = $el.find('label').html('<span>00:00</span>')
          , timestamp = new Date();

        this.recordInterval = setInterval(function() {
          var time = new Date(new Date() - timestamp)
            , min = time.getMinutes()
            , sec = time.getSeconds();

          $label.html('<span>'+ ((min < 10 && '0' || '') + min + ':' + (sec < 10 && '0' || '') + sec) + '</span>');
        }, 1000);
      }
    },

    /**
     * @return {boolean} on/off status
     */
    on: function() {
      if (this.$rswp.hasClass('off')) {
        console.log('Device.on');

        // FIXED #17685
        /*
        this.$rswp.trigger('MONKEY_POWER', 'down');
        this.$rswp.trigger('MONKEY_POWER', 'up');
        */
        this.s.data.send('wakeup');
        return true;
      }
      return false;
    },

    /**
     * @return {boolean} on/off status
     */
    off: function() {
      if (!this.$rswp.hasClass('off')) {
        this.$rswp.trigger('MONKEY_POWER', 'down');
        this.$rswp.trigger('MONKEY_POWER', 'up');

        console.log('Device.off');
        return true;
      }
      return false;
    },

    /**
     * @param {string} name - method name
     * @return {boolean} on/off status
     */
    act: function(name) {
      return this[name] && this[name].active;
    },

    /**
     * @param {boolean} started - on/off state
     */
    state: function(started) {
      //console.log('Device.state', started);

      if (this.a.running
        && (this.a.meeting && !this.a.meeting._started)
        && !this._popped
        && this._paused
        && started) {
        return this.pause();
      }

      this.activate(started);

      // FIXED #28028
      this._paused = this.screen.paused; // screen state sync
    },

    /**
     * @method
     */
    pause: function() {
      //console.log('Device.pause', new Error().stack);

      this.screen.pause();
    },

    /**
     * @method
     */
    resume: function() {
      //console.log('Device.resume', this._recording, this.a.running, this._popped);

      if ( this._recording
        || this.a.running && !this._popped) {
        return;
      }

      this.screen.resume();
    },

    /**
     * @listens Mobizen.Socket.Channel.Data#share․contents․end
     */
    popup: function() {
      if (!this.a.running || !this.$device.find('#rswp').length) {
        console.warn('DEVICE.POPUP.FAILURE');
        return;
      }

      var ui = this.m.ui
        , panel = ui.panel.deep == 2 && !ui.dialog.opened ? 'viewer' : 'modules';

      ui.dialog.opened && ui.dialog.close(true);

      this.$device.addClass('slide-out');
      this.$widget.removeClass('dim');

      this.$panel = this.m.$[panel];
      this.$popout = this.$panel.find('.popout');

      if (this.m.$.contents.hasClass('tab')) {
        this.$popout.addClass('tab');
      }

      if (this.m.$.contents.hasClass('landscape')) {
        this.$popout.addClass('landscape');
      }

      this.rotate();

      this._popped = true;
      this._state = 'popup';
    },

    /**
     * @method
     */
    popdown: function() {
      if (!this.a.running || !this._popped) return;

      this.$device.addClass('slide-out');
      this.$widget.addClass('dim');

      setTimeout(function() {
        this.$panel.removeClass('popout');
        this.screen.view.$wrapper.append(this.$device);

        setTimeout(function() {
          this.$device.removeClass('slide-out');
          this.a.running && this.pause();
          this.$popout = null;
          this.activate(false);
        }.bind(this), 10);
      }.bind(this), 400);

      this._popped = false;
    },

    // 기기의 기울기 정보에 따른 화면 갱신
    /**
     * @param {number} [rotate] - degree numger
     */
    rotate: function(rotate) {
      console.log('Device.rotate', rotate);

      if (rotate !== undefined) {
        this._rotate = rotate;
      }

      if (!this.act('fullscreen')) {
        this._state = 'rotate';
      }

      this.whiteboard && this.whiteboard.clear();

      if (this.act('inputtext')) {
        this.inputtext.update();
      }

      if (this.act('fullscreen')) {
        this.fullscreen.$el.removeClass('deg0 deg90 deg180 deg270');
        this.fullscreen.$el.addClass('deg' + this._rotate);
        return this.fullscreen.update();
      }

      this.m.$.contents.removeClass('deg0 deg90 deg180 deg270');
      this.m.$.contents.addClass('deg' + this._rotate);

      if (this.$popout) {
        this.$popout.removeClass('deg0 deg90 deg180 deg270');
        this.$popout.addClass('deg' + this._rotate);
      }
    },

    /**
     * @param {boolean} state - on/off state
     */
    power: function(state) {
      console.log('Device.power', state);

      this.$rswp.toggleClass('off', !state);

      // 앱 실행상태에서 메뉴 > 모바일 디바이스 비활성
      this.$widget
        .find('.record, .snapshot, .whiteboard, .fullscreen, .keyboard')
        .toggleClass('dim', !state);

      this.$widget.toggleClass('off', !state);

      if (this.act('whiteboard') && !state) {
        this.$widget.find('.whiteboard').addClass('off');
        this.whiteboard.close();
      }

      this._powerDelayPended = false;
    },

    /**
     * @param {boolean} active - on/off state
     * @return {boolean} on/off state
     */
    activate: function(active) {
      if (!this.screen.started
        // FIXED #34954
        ||  active === this._activate && active === this.screen.active
        ||  active && this.t && this.t.search.focused
        ||  active && this._recording
        || !active && this.screen.paused
        || !active && this.$rswp.hasClass('off')
      ) {
        /*
        console.warn('Device.activate.failed', {
          activate: active,
          trunoff: this.$rswp.hasClass('off'),
          deviceRecording: this._recording,
          screenActive: this.screen.active,
          screenPause: this.screen.paused
        });
        */
        return false;
      }

      this.screen[(this._activate = !!active) ? 'activate' : 'deactivate']();
      this.$device.toggleClass('active', this._activate);

      // screen state sync
      if (this._activate !== this.screen.active) {
        this._activate = this.screen.active;
        console.warn('Device.activate.synced', this.screen.active);
      }

      if (!this._activate) {
        this.screen.mask.toggle(false);
        //this.$paste.blur();
      } else {
        //this.$paste.focus();
      }

      return true;
    },

    /**
     * @method
     */
    update: function() {
      if (this.act('inputtext')) {
        this.inputtext.show();
      }

      if (!this._recording) {
        this.screen.viewport();

        var params = this.screen.params
          , viewport = this.screen.view
          , dimension = this.screen.device
          , optimized = {
            width: Math.round(dimension.width / params.pixelRatio),
            height: Math.round(dimension.height / params.pixelRatio)
          };

        if (this.screen.encodeType != 'D') {
          this.$widget.find('.hd')
            .toggleClass('disable', optimized.width > viewport.width || optimized.height > viewport.height)
            .toggleClass('off', !params.useHighDefinition);
        }
      } else {
        // FIXED #28098
        this.screen.viewport(false);
      }

      this.screen.resume();

      if (this.t && this.t.search.focused) {
        this.screen.deactivate();
      }

      if (this.whiteboard) {
        this.whiteboard.update(); // clear
      }
    }
  };


  /*
   * Exporting modules
   */

  Mobizen.Device = Device;


})(jQuery, this);
comments powered by Disqus