Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/apps/system/js/hardware_buttons.js
blob: 3363463e1b6c59f1f878aa2a594c18c0235ec6c5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
// hardware_buttons.js:
//
// Gecko code in b2g/chrome/content/shell.js sends mozChromeEvents
// when the user presses or releases a hardware button such as Home, Sleep,
// and Volume Up and Down.
//
// This module listens for those low-level mozChromeEvents, processes them
// and generates higher-level events to handle autorepeat on the volume keys
// long presses on Home and Sleep, and the Home+Sleep key combination.
//
// Other system app modules should listen for the high-level button events
// generated by this module.
//
// The low-level input events processed by this module have type set
// to "mozChromeEvent" and detail.type set to one of:
//
//   home-button-press
//   home-button-release
//   sleep-button-press
//   sleep-button-release
//   volume-up-button-press
//   volume-up-button-release
//   volume-down-button-press
//   volume-down-button-release
//
// The high-level events generated by this module are simple Event objects
// that are not cancelable and do not bubble.  The are dispatched at the
// window object.  The type property is set to one of these:
//
// Event Type    Meaning
// --------------------------------------------------------------
//   home        short press and release of home button
//   holdhome    long press and hold of home button
//   sleep       short press and release of sleep button
//   wake        sleep or home pressed while sleeping
//   holdsleep   long press and hold of sleep button
//   volumeup    volume up pressed and released or autorepeated
//   volumedown  volume down pressed and released or autorepeated
//   home+sleep  home and sleep pressed at same time (used for screenshots)
//   home+volume home and either volume key at the same time (view source)
//
// Because these events are fired at the window object, they cannot be
// captured.  Many modules listen for the home event. Those that want
// to respond to it and prevent others from responding should call
// stopImmediatePropagation(). Overlays that want to prevent the window
// manager from showing the homescreen on the home event should call that
// method.  Note, however, that this only works for scripts that run and
// register their event handlers before window_manager.js does.
//
'use strict';

(function() {
  var HOLD_INTERVAL = 750;   // How long for press and hold Home or Sleep
  var REPEAT_DELAY = 700;     // How long before volume autorepeat begins
  var REPEAT_INTERVAL = 100;  // How fast the autorepeat is.

  // Dispatch a high-level event of the specified type
  function fire(type) {
    window.dispatchEvent(new Event(type));
  }

  // We process events with a finite state machine.
  // Each state object has a process() method for handling events.
  // And optionally has enter() and exit() methods called when the FSM
  // enters and exits that state
  var state;

  // This function transitions to a new state
  function setState(s, type) {
    // Exit the current state()
    if (state && state.exit)
      state.exit(type);
    state = s;
    // Enter the new state
    if (state && state.enter)
      state.enter(type);
  }

  // This event handler listens for hardware button events and passes the
  // event type to the process() method of the current state for processing
  window.addEventListener('mozChromeEvent', function(e) {
    var type = e.detail.type;
    switch (type) {
      case 'home-button-press':
      case 'home-button-release':
      case 'sleep-button-press':
      case 'sleep-button-release':
      case 'volume-up-button-press':
      case 'volume-up-button-release':
      case 'volume-down-button-press':
      case 'volume-down-button-release':
        state.process(type);
        break;
    }
  });

  // The base state is the default, when no hardware buttons are pressed
  var baseState = {
    process: function(type) {
      switch (type) {
      case 'home-button-press':
        // If the phone is sleeping, then pressing Home wakes it
        // (on press, not release)
        if (!ScreenManager.screenEnabled) {
          fire('wake');
          setState(wakeState, type);
        } else {
          setState(homeState, type);
        }
        return;
      case 'sleep-button-press':
        // If the phone is sleeping, then pressing Sleep wakes it
        // (on press, not release)
        if (!ScreenManager.screenEnabled) {
          fire('wake');
          setState(wakeState, type);
        } else {
          setState(sleepState, type);
        }
        return;
      case 'volume-up-button-press':
      case 'volume-down-button-press':
        setState(volumeState, type);
        return;
      case 'home-button-release':
      case 'sleep-button-release':
      case 'volume-up-button-release':
      case 'volume-down-button-release':
        // Ignore button releases that occur in this state.
        // These can happen after home+sleep and home+volume.
        return;
      }
      console.error('Unexpected hardware key: ', type);
    }
  };

  // We enter the home state when the user presses the Home button
  // We can fire home, holdhome, or homesleep events from this state
  var homeState = {
    timer: null,
    enter: function() {
      this.timer = setTimeout(function() {
        fire('holdhome');
        navigator.vibrate(50);
        setState(baseState);
      }, HOLD_INTERVAL);
    },
    exit: function() {
      if (this.timer) {
        clearTimeout(this.timer);
        this.timer = null;
      }
    },
    process: function(type) {
      switch (type) {
      case 'home-button-release':
        fire('home');
        navigator.vibrate(50);
        setState(baseState, type);
        return;
      case 'sleep-button-press':
        fire('home+sleep');
        setState(baseState, type);
        return;
      case 'volume-up-button-press':
      case 'volume-down-button-press':
        fire('home+volume');
        setState(baseState, type);
        return;
      }
      console.error('Unexpected hardware key: ', type);
      setState(baseState, type);
    }
  };

  // We enter the sleep state when the user presses the Sleep button
  // We can fire sleep, holdsleep, or homesleep events from this state
  var sleepState = {
    timer: null,
    enter: function() {
      this.timer = setTimeout(function() {
        fire('holdsleep');
        setState(baseState);
      }, HOLD_INTERVAL);
    },
    exit: function() {
      if (this.timer) {
        clearTimeout(this.timer);
        this.timer = null;
      }
    },
    process: function(type) {
      switch (type) {
      case 'sleep-button-release':
        fire('sleep');
        setState(baseState, type);
        return;
      case 'home-button-press':
        fire('home+sleep');
        setState(baseState, type);
        return;
      case 'volume-up-button-press':
      case 'volume-down-button-press':
        setState(volumeState, type);
        return;
      }
      console.error('Unexpected hardware key: ', type);
      setState(baseState, type);
    }
  };

  // We enter the volume state when the user presses the volume up or
  // volume down buttons.
  // We can fire volumeup and volumedown events from this state
  var volumeState = {
    direction: null,
    timer: null,
    repeating: false,
    repeat: function() {
      this.repeating = true;
      if (this.direction === 'volume-up-button-press')
        fire('volumeup');
      else
        fire('volumedown');
      this.timer = setTimeout(this.repeat.bind(this), REPEAT_INTERVAL);
    },
    enter: function(type) {
      var self = this;
      this.direction = type;  // Is volume going up or down?
      this.repeating = false;
      this.timer = setTimeout(this.repeat.bind(this), REPEAT_DELAY);
    },
    exit: function() {
      if (this.timer) {
        clearTimeout(this.timer);
        this.timer = null;
      }
    },
    process: function(type) {
      switch (type) {
      case 'home-button-press':
        fire('home+volume');
        setState(baseState, type);
        return;
      case 'sleep-button-press':
        setState(sleepState, type);
        return;
      case 'volume-up-button-release':
        if (this.direction === 'volume-up-button-press') {
          if (!this.repeating)
            fire('volumeup');
          setState(baseState, type);
          return;
        }
        break;
      case 'volume-down-button-release':
        if (this.direction === 'volume-down-button-press') {
          if (!this.repeating)
            fire('volumedown');
          setState(baseState, type);
          return;
        }
        break;
      default:
        // Ignore anything else (such as sleep button release)
        return;
      }
      console.error('Unexpected hardware key: ', type);
      setState(baseState, type);
    }
  };

  // We enter this state when the user presses Home or Sleep on a sleeping
  // phone.  We give immediate feedback by waking the phone up on the press
  // rather than waiting for the release, but this means we need a special
  // state so that we don't actually send a home or sleep event on the
  // key release.  Note, however, that this state does set a timer so that
  // it can send holdhome or holdsleep events.  (This means that pressing and
  // holding sleep will bring up the power menu, even on a sleeping phone.)
  var wakeState = {
    timer: null,
    delegateState: null,
    enter: function(type) {
      if (type === 'home-button-press')
        this.delegateState = homeState;
      else
        this.delegateState = sleepState;
      this.timer = setTimeout(function() {
        if (type === 'home-button-press') {
          fire('holdhome');
        } else if (type === 'sleep-button-press') {
          fire('holdsleep');
        }
        setState(baseState, type);
      }, HOLD_INTERVAL);
    },
    exit: function() {
      if (this.timer) {
        clearTimeout(this.timer);
        this.timer = null;
      }
    },
    process: function(type) {
      switch (type) {
      case 'home-button-release':
      case 'sleep-button-release':
        setState(baseState, type);
        return;
      default:
        this.delegateState.process(type);
        return;
      }
    }
  };

  // Kick off the FSM in the base state
  setState(baseState);
}());