Remove the old signature of NotificationManager::closePersistent().
[chromium-blink-merge.git] / chrome / browser / resources / chromeos / login / screen_supervised_user_creation.js
blobd3a7f897489ee97aa9a904b322f6253fcca7daa7
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 /**
6  * @fileoverview Legacy supervised user creation flow screen.
7  */
9 login.createScreen('SupervisedUserCreationScreen',
10                    'supervised-user-creation', function() {
11   var MAX_NAME_LENGTH = 50;
12   var UserImagesGrid = options.UserImagesGrid;
13   var ButtonImages = UserImagesGrid.ButtonImages;
15   var ManagerPod = cr.ui.define(function() {
16     var node = $('supervised-user-creation-manager-template').cloneNode(true);
17     node.removeAttribute('id');
18     node.removeAttribute('hidden');
19     return node;
20   });
22   ManagerPod.userImageSalt_ = {};
24   /**
25    * UI element for displaying single account in list of possible managers for
26    * new supervised user.
27    * @type {Object}
28    */
29   ManagerPod.prototype = {
30     __proto__: HTMLDivElement.prototype,
32     /** @override */
33     decorate: function() {
34       // Mousedown has to be used instead of click to be able to prevent 'focus'
35       // event later.
36       this.addEventListener('mousedown',
37                             this.handleMouseDown_.bind(this));
38       var screen = $('supervised-user-creation');
39       var managerPod = this;
40       var managerPodList = screen.managerList_;
41       var hideManagerPasswordError = function(element) {
42         managerPod.passwordElement.classList.remove('password-error');
43         $('bubble').hide();
44       };
46       screen.configureTextInput(
47           this.passwordElement,
48           screen.updateNextButtonForManager_.bind(screen),
49           screen.validIfNotEmpty_.bind(screen),
50           function(element) {
51             screen.getScreenButton('next').focus();
52           },
53           hideManagerPasswordError);
55       this.passwordElement.addEventListener('keydown', function(e) {
56         switch (e.keyIdentifier) {
57           case 'Up':
58             managerPodList.selectNextPod(-1);
59             e.stopPropagation();
60             break;
61           case 'Down':
62             managerPodList.selectNextPod(+1);
63             e.stopPropagation();
64             break;
65         }
66       });
67     },
69     /**
70      * Updates UI elements from user data.
71      */
72     update: function() {
73       this.imageElement.src = 'chrome://userimage/' + this.user.username +
74           '?id=' + ManagerPod.userImageSalt_[this.user.username];
76       this.nameElement.textContent = this.user.displayName;
77       this.emailElement.textContent = this.user.emailAddress;
78     },
80     showPasswordError: function() {
81       this.passwordElement.classList.add('password-error');
82       $('bubble').showTextForElement(
83           this.passwordElement,
84           loadTimeData.getString(
85               'createSupervisedUserWrongManagerPasswordText'),
86           cr.ui.Bubble.Attachment.BOTTOM,
87           24, 4);
88     },
90     /**
91      * Brings focus to password field.
92      */
93     focusInput: function() {
94       this.passwordElement.focus();
95     },
97     /**
98      * Gets image element.
99      * @type {!HTMLImageElement}
100      */
101     get imageElement() {
102       return this.querySelector('.supervised-user-creation-manager-image');
103     },
105     /**
106      * Gets name element.
107      * @type {!HTMLDivElement}
108      */
109     get nameElement() {
110       return this.querySelector('.supervised-user-creation-manager-name');
111     },
113     /**
114      * Gets e-mail element.
115      * @type {!HTMLDivElement}
116      */
117     get emailElement() {
118       return this.querySelector('.supervised-user-creation-manager-email');
119     },
121     /**
122      * Gets password element.
123      * @type {!HTMLDivElement}
124      */
125     get passwordElement() {
126       return this.querySelector('.supervised-user-creation-manager-password');
127     },
129     /**
130      * Gets password enclosing block.
131      * @type {!HTMLDivElement}
132      */
133     get passwordBlock() {
134       return this.querySelector('.password-block');
135     },
137     /** @override */
138     handleMouseDown_: function(e) {
139       this.parentNode.selectPod(this);
140       // Prevent default so that we don't trigger 'focus' event.
141       e.preventDefault();
142     },
144     /**
145      * The user that this pod represents.
146      * @type {!Object}
147      */
148     user_: undefined,
149     get user() {
150       return this.user_;
151     },
152     set user(userDict) {
153       this.user_ = userDict;
154       this.update();
155     },
156   };
158   var ManagerPodList = cr.ui.define('div');
160   /**
161    * UI element for selecting manager account for new supervised user.
162    * @type {Object}
163    */
164   ManagerPodList.prototype = {
165     __proto__: HTMLDivElement.prototype,
167     selectedPod_: null,
169     /** @override */
170     decorate: function() {
171     },
173     /**
174      * Returns all the pods in this pod list.
175      * @type {NodeList}
176      */
177     get pods() {
178       return this.children;
179     },
181     addPod: function(manager) {
182       var managerPod = new ManagerPod({user: manager});
183       this.appendChild(managerPod);
184       managerPod.update();
185     },
187     clearPods: function() {
188       this.innerHTML = '';
189       this.selectedPod_ = null;
190     },
192     selectPod: function(podToSelect) {
193       if ((this.selectedPod_ == podToSelect) && !!podToSelect) {
194         podToSelect.focusInput();
195         return;
196       }
197       this.selectedPod_ = podToSelect;
198       for (var i = 0, pod; pod = this.pods[i]; ++i) {
199         if (pod != podToSelect) {
200           pod.classList.remove('focused');
201           pod.passwordElement.value = '';
202           pod.passwordBlock.hidden = true;
203         }
204       }
205       if (!podToSelect)
206         return;
207       podToSelect.classList.add('focused');
208       podToSelect.passwordBlock.hidden = false;
209       podToSelect.passwordElement.value = '';
210       podToSelect.focusInput();
211       chrome.send('managerSelectedOnSupervisedUserCreationFlow',
212           [podToSelect.user.username]);
213     },
215     /**
216      * Select pod next to currently selected one in given |direction|.
217      * @param {integer} direction - +1 for selecting pod below current, -1 for
218      *     selecting pod above current.
219      * @type {boolean} returns if selected pod has changed.
220      */
221     selectNextPod: function(direction) {
222       if (!this.selectedPod_)
223         return false;
224       var index = -1;
225       for (var i = 0, pod; pod = this.pods[i]; ++i) {
226         if (pod == this.selectedPod_) {
227           index = i;
228           break;
229         }
230       }
231       if (-1 == index)
232         return false;
233       index = index + direction;
234       if (index < 0 || index >= this.pods.length)
235         return false;
236       this.selectPod(this.pods[index]);
237       return true;
238     }
239   };
241   var ImportPod = cr.ui.define(function() {
242     var node = $('supervised-user-creation-import-template').cloneNode(true);
243     node.removeAttribute('id');
244     node.removeAttribute('hidden');
245     return node;
246   });
248   /**
249    * UI element for displaying single supervised user in list of possible users
250    * for importing existing users.
251    * @type {Object}
252    */
253   ImportPod.prototype = {
254     __proto__: HTMLDivElement.prototype,
256     /** @override */
257     decorate: function() {
258       // Mousedown has to be used instead of click to be able to prevent 'focus'
259       // event later.
260       this.addEventListener('mousedown', this.handleMouseDown_.bind(this));
261       var screen = $('supervised-user-creation');
262       var importList = screen.importList_;
263     },
265     /**
266      * Updates UI elements from user data.
267      */
268     update: function() {
269       this.imageElement.src = this.user.avatarurl;
270       this.nameElement.textContent = this.user.name;
271       if (this.user.exists) {
272         if (this.user.conflict == 'imported') {
273           this.nameElement.textContent =
274               loadTimeData.getStringF('importUserExists', this.user.name);
275         } else {
276           this.nameElement.textContent =
277               loadTimeData.getStringF('importUsernameExists', this.user.name);
278         }
279       }
280       this.classList.toggle('imported', this.user.exists);
281     },
283     /**
284      * Gets image element.
285      * @type {!HTMLImageElement}
286      */
287     get imageElement() {
288       return this.querySelector('.import-pod-image');
289     },
291     /**
292      * Gets name element.
293      * @type {!HTMLDivElement}
294      */
295     get nameElement() {
296       return this.querySelector('.import-pod-name');
297     },
299     /** @override */
300     handleMouseDown_: function(e) {
301       this.parentNode.selectPod(this);
302       // Prevent default so that we don't trigger 'focus' event.
303       e.preventDefault();
304     },
306     /**
307      * The user that this pod represents.
308      * @type {Object}
309      */
310     user_: undefined,
312     get user() {
313       return this.user_;
314     },
316     set user(userDict) {
317       this.user_ = userDict;
318       this.update();
319     },
320   };
322   var ImportPodList = cr.ui.define('div');
324   /**
325    * UI element for selecting existing supervised user for import.
326    * @type {Object}
327    */
328   ImportPodList.prototype = {
329     __proto__: HTMLDivElement.prototype,
331     selectedPod_: null,
333     /** @override */
334     decorate: function() {
335       this.setAttribute('tabIndex', 0);
336       this.classList.add('nofocus');
337       var importList = this;
338       var screen = $('supervised-user-creation');
340       this.addEventListener('focus', function(e) {
341         if (importList.selectedPod_ == null) {
342           if (importList.pods.length > 0)
343             importList.selectPod(importList.pods[0]);
344         }
345       });
347       this.addEventListener('keydown', function(e) {
348         switch (e.keyIdentifier) {
349           case 'Up':
350             importList.selectNextPod(-1);
351             e.stopPropagation();
352             break;
353           case 'Enter':
354             if (importList.selectedPod_ != null)
355               screen.importSupervisedUser_();
356             e.stopPropagation();
357             break;
358           case 'Down':
359             importList.selectNextPod(+1);
360             e.stopPropagation();
361             break;
362         }
363       });
364     },
366     /**
367      * Returns all the pods in this pod list.
368      * @type {NodeList}
369      */
370     get pods() {
371       return this.children;
372     },
374     /**
375      * Returns selected pod.
376      * @type {Node}
377      */
378     get selectedPod() {
379       return this.selectedPod_;
380     },
382     addPod: function(user) {
383       var importPod = new ImportPod({user: user});
384       this.appendChild(importPod);
385       importPod.update();
386     },
388     clearPods: function() {
389       this.innerHTML = '';
390       this.selectedPod_ = null;
391     },
393     scrollIntoView: function(pod) {
394       scroller = this.parentNode;
395       var itemHeight = pod.getBoundingClientRect().height;
396       var scrollTop = scroller.scrollTop;
397       var top = pod.offsetTop - scroller.offsetTop;
398       var clientHeight = scroller.clientHeight;
400       var self = scroller;
402       // Function to adjust the tops of viewport and row.
403       function scrollToAdjustTop() {
404         self.scrollTop = top;
405         return true;
406       };
407       // Function to adjust the bottoms of viewport and row.
408       function scrollToAdjustBottom() {
409         var cs = getComputedStyle(self);
410         var paddingY = parseInt(cs.paddingTop, 10) +
411                        parseInt(cs.paddingBottom, 10);
413         if (top + itemHeight > scrollTop + clientHeight - paddingY) {
414           self.scrollTop = top + itemHeight - clientHeight + paddingY;
415           return true;
416         }
417         return false;
418       };
420       // Check if the entire of given indexed row can be shown in the viewport.
421       if (itemHeight <= clientHeight) {
422         if (top < scrollTop)
423           return scrollToAdjustTop();
424         if (scrollTop + clientHeight < top + itemHeight)
425           return scrollToAdjustBottom();
426       } else {
427         if (scrollTop < top)
428           return scrollToAdjustTop();
429         if (top + itemHeight < scrollTop + clientHeight)
430           return scrollToAdjustBottom();
431       }
432       return false;
433     },
435     /**
436      * @param {Element} podToSelect - pod to select, can be null.
437      */
438     selectPod: function(podToSelect) {
439       if ((this.selectedPod_ == podToSelect) && !!podToSelect) {
440         return;
441       }
442       this.selectedPod_ = podToSelect;
443       for (var i = 0; i < this.pods.length; i++) {
444         var pod = this.pods[i];
445         if (pod != podToSelect)
446           pod.classList.remove('focused');
447       }
448       if (!podToSelect)
449         return;
450       podToSelect.classList.add('focused');
451       podToSelect.focus();
452       var screen = $('supervised-user-creation');
453       if (!this.selectedPod_) {
454         screen.getScreenButton('import').disabled = true;
455       } else {
456         screen.getScreenButton('import').disabled =
457             this.selectedPod_.user.exists;
458         if (!this.selectedPod_.user.exists) {
459           chrome.send('userSelectedForImportInSupervisedUserCreationFlow',
460                       [podToSelect.user.id]);
461         }
462       }
463     },
465     selectNextPod: function(direction) {
466       if (!this.selectedPod_)
467         return false;
468       var index = -1;
469       for (var i = 0, pod; pod = this.pods[i]; ++i) {
470         if (pod == this.selectedPod_) {
471           index = i;
472           break;
473         }
474       }
475       if (-1 == index)
476         return false;
477       index = index + direction;
478       if (index < 0 || index >= this.pods.length)
479         return false;
480       this.selectPod(this.pods[index]);
481       return true;
482     },
484     selectUser: function(user_id) {
485       for (var i = 0, pod; pod = this.pods[i]; ++i) {
486         if (pod.user.id == user_id) {
487           this.selectPod(pod);
488           this.scrollIntoView(pod);
489           break;
490         }
491       }
492     },
493   };
495   return {
496     EXTERNAL_API: [
497       'loadManagers',
498       'setCameraPresent',
499       'setDefaultImages',
500       'setExistingSupervisedUsers',
501       'showErrorPage',
502       'showIntroPage',
503       'showManagerPage',
504       'showManagerPasswordError',
505       'showPage',
506       'showPasswordError',
507       'showProgress',
508       'showStatusError',
509       'showTutorialPage',
510       'showUsernamePage',
511       'supervisedUserNameError',
512       'supervisedUserNameOk',
513       'supervisedUserSuggestImport',
514     ],
516     lastVerifiedName_: null,
517     lastIncorrectUserName_: null,
518     managerList_: null,
519     importList_: null,
521     currentPage_: null,
522     imagesRequested_: false,
524     // Contains data that can be auto-shared with handler.
525     context_: {},
527     /** @override */
528     decorate: function() {
529       this.managerList_ = new ManagerPodList();
530       $('supervised-user-creation-managers-pane').appendChild(
531           this.managerList_);
533       this.importList_ = new ImportPodList();
534       $('supervised-user-creation-import-pane').appendChild(this.importList_);
536       var userNameField = $('supervised-user-creation-name');
537       var passwordField = $('supervised-user-creation-password');
538       var password2Field = $('supervised-user-creation-password-confirm');
540       var creationScreen = this;
542       var hideUserPasswordError = function(element) {
543         $('bubble').hide();
544         $('supervised-user-creation-password').classList.remove(
545             'password-error');
546       };
548       this.configureTextInput(userNameField,
549                               this.checkUserName_.bind(this),
550                               this.validIfNotEmpty_.bind(this),
551                               function(element) {
552                                 passwordField.focus();
553                               },
554                               this.clearUserNameError_.bind(this));
555       this.configureTextInput(passwordField,
556                               this.updateNextButtonForUser_.bind(this),
557                               this.validIfNotEmpty_.bind(this),
558                               function(element) {
559                                 password2Field.focus();
560                               },
561                               hideUserPasswordError);
562       this.configureTextInput(password2Field,
563                               this.updateNextButtonForUser_.bind(this),
564                               this.validIfNotEmpty_.bind(this),
565                               function(element) {
566                                 creationScreen.getScreenButton('next').focus();
567                               },
568                               hideUserPasswordError);
570       this.getScreenButton('error').addEventListener('click', function(e) {
571         creationScreen.handleErrorButtonPressed_();
572         e.stopPropagation();
573       });
575       /*
576       TODO(antrim) : this is an explicit code duplications with UserImageScreen.
577       It should be removed by issue 251179.
578       */
579       var imageGrid = this.getScreenElement('image-grid');
580       UserImagesGrid.decorate(imageGrid);
582       // Preview image will track the selected item's URL.
583       var previewElement = this.getScreenElement('image-preview');
584       previewElement.oncontextmenu = function(e) { e.preventDefault(); };
586       imageGrid.previewElement = previewElement;
587       imageGrid.selectionType = 'default';
588       imageGrid.flipPhotoElement = this.getScreenElement('flip-photo');
590       imageGrid.addEventListener('activate',
591                                  this.handleActivate_.bind(this));
592       imageGrid.addEventListener('select',
593                                  this.handleSelect_.bind(this));
594       imageGrid.addEventListener('phototaken',
595                                  this.handlePhotoTaken_.bind(this));
596       imageGrid.addEventListener('photoupdated',
597                                  this.handlePhotoUpdated_.bind(this));
598       // Set the title for camera item in the grid.
599       imageGrid.setCameraTitles(
600           loadTimeData.getString('takePhoto'),
601           loadTimeData.getString('photoFromCamera'));
603       this.getScreenElement('take-photo').addEventListener(
604           'click', this.handleTakePhoto_.bind(this));
605       this.getScreenElement('discard-photo').addEventListener(
606           'click', this.handleDiscardPhoto_.bind(this));
608       // Toggle 'animation' class for the duration of WebKit transition.
609       this.getScreenElement('flip-photo').addEventListener(
610           'click', this.handleFlipPhoto_.bind(this));
611       this.getScreenElement('image-stream-crop').addEventListener(
612           'webkitTransitionEnd', function(e) {
613             previewElement.classList.remove('animation');
614           });
615       this.getScreenElement('image-preview-img').addEventListener(
616           'webkitTransitionEnd', function(e) {
617             previewElement.classList.remove('animation');
618           });
619     },
621     buttonIds: [],
623     /**
624      * Creates button for adding to controls.
625      * @param {string} buttonId -- id for button, have to be unique within
626      *   screen. Actual id will be prefixed with screen name and appended with
627      *   '-button'. Use getScreenButton(buttonId) to find it later.
628      * @param {string} i18nPrefix -- screen prefix for i18n values.
629      * @param {function} callback -- will be called on button press with
630      *   buttonId parameter.
631      * @param {array} pages -- list of pages where this button should be
632      *   displayed.
633      * @param {array} classes -- list of additional CSS classes for button.
634      */
635     makeButton: function(buttonId, i18nPrefix, callback, pages, classes) {
636       var capitalizedId = buttonId.charAt(0).toUpperCase() + buttonId.slice(1);
637       this.buttonIds.push(buttonId);
638       var result = this.ownerDocument.createElement('button');
639       result.id = this.name() + '-' + buttonId + '-button';
640       result.classList.add('screen-control-button');
641       for (var i = 0; i < classes.length; i++) {
642         result.classList.add(classes[i]);
643       }
644       result.textContent = loadTimeData.
645           getString(i18nPrefix + capitalizedId + 'ButtonTitle');
646       result.addEventListener('click', function(e) {
647         callback(buttonId);
648         e.stopPropagation();
649       });
650       result.pages = pages;
651       return result;
652     },
654     /**
655      * Simple validator for |configureTextInput|.
656      * Element is considered valid if it has any text.
657      * @param {Element} element - element to be validated.
658      * @return {boolean} - true, if element has any text.
659      */
660     validIfNotEmpty_: function(element) {
661       return (element.value.length > 0);
662     },
664     /**
665      * Configure text-input |element|.
666      * @param {Element} element - element to be configured.
667      * @param {function(element)} inputChangeListener - function that will be
668      *    called upon any button press/release.
669      * @param {function(element)} validator - function that will be called when
670      *    Enter is pressed. If it returns |true| then advance to next element.
671      * @param {function(element)} moveFocus - function that will determine next
672      *    element and move focus to it.
673      * @param {function(element)} errorHider - function that is called upon
674      *    every button press, so that any associated error can be hidden.
675      */
676     configureTextInput: function(element,
677                                  inputChangeListener,
678                                  validator,
679                                  moveFocus,
680                                  errorHider) {
681       element.addEventListener('keydown', function(e) {
682         if (e.keyIdentifier == 'Enter') {
683           var dataValid = true;
684           if (validator)
685             dataValid = validator(element);
686           if (!dataValid) {
687             element.focus();
688           } else {
689             if (moveFocus)
690               moveFocus(element);
691           }
692           e.stopPropagation();
693           return;
694         }
695         if (errorHider)
696           errorHider(element);
697         if (inputChangeListener)
698           inputChangeListener(element);
699       });
700       element.addEventListener('keyup', function(e) {
701         if (inputChangeListener)
702           inputChangeListener(element);
703       });
704     },
706     /**
707      * Makes element from template.
708      * @param {string} templateId -- template will be looked up within screen
709      * by class with name "template-<templateId>".
710      * @param {string} elementId -- id for result, uinque within screen. Actual
711      *   id will be prefixed with screen name. Use getScreenElement(id) to find
712      *   it later.
713      */
714     makeFromTemplate: function(templateId, elementId) {
715       var templateClassName = 'template-' + templateId;
716       var templateNode = this.querySelector('.' + templateClassName);
717       var screenPrefix = this.name() + '-';
718       var result = templateNode.cloneNode(true);
719       result.classList.remove(templateClassName);
720       result.id = screenPrefix + elementId;
721       return result;
722     },
724     /**
725      * @param {string} buttonId -- id of button to be found,
726      * @return {Element} button created by makeButton with given buttonId.
727      */
728     getScreenButton: function(buttonId) {
729       var fullId = this.name() + '-' + buttonId + '-button';
730       return this.getScreenElement(buttonId + '-button');
731     },
733     /**
734      * @param {string} elementId -- id of element to be found,
735      * @return {Element} button created by makeFromTemplate with elementId.
736      */
737     getScreenElement: function(elementId) {
738       var fullId = this.name() + '-' + elementId;
739       return $(fullId);
740     },
742     /**
743      * Screen controls.
744      * @type {!Array} Array of Buttons.
745      */
746     get buttons() {
747       var links = this.ownerDocument.createElement('div');
748       var buttons = this.ownerDocument.createElement('div');
749       links.classList.add('controls-links');
750       buttons.classList.add('controls-buttons');
752       var importLink = this.makeFromTemplate('import-supervised-user-link',
753                                              'import-link');
754       importLink.hidden = true;
755       links.appendChild(importLink);
757       var linkElement = importLink.querySelector('.signin-link');
758       linkElement.addEventListener('click',
759           this.importLinkPressed_.bind(this));
761       var createLink = this.makeFromTemplate('create-supervised-user-link',
762                                              'create-link');
763       createLink.hidden = true;
764       links.appendChild(createLink);
766       var status = this.makeFromTemplate('status-container', 'status');
767       buttons.appendChild(status);
769       linkElement = createLink.querySelector('.signin-link');
770       linkElement.addEventListener('click',
771           this.createLinkPressed_.bind(this));
773       buttons.appendChild(this.makeButton(
774           'start',
775           'supervisedUserCreationFlow',
776           this.startButtonPressed_.bind(this),
777           ['intro'],
778           ['custom-appearance', 'button-fancy', 'button-blue']));
780       buttons.appendChild(this.makeButton(
781           'prev',
782           'supervisedUserCreationFlow',
783           this.prevButtonPressed_.bind(this),
784           ['manager'],
785           []));
787       buttons.appendChild(this.makeButton(
788           'next',
789           'supervisedUserCreationFlow',
790           this.nextButtonPressed_.bind(this),
791           ['manager', 'username'],
792           []));
794       buttons.appendChild(this.makeButton(
795           'import',
796           'supervisedUserCreationFlow',
797           this.importButtonPressed_.bind(this),
798           ['import', 'import-password'],
799           []));
801       buttons.appendChild(this.makeButton(
802           'gotit',
803           'supervisedUserCreationFlow',
804           this.gotItButtonPressed_.bind(this),
805           ['created'],
806           ['custom-appearance', 'button-fancy', 'button-blue']));
807       return [links, buttons];
808     },
810     /**
811      * Does sanity check and calls backend with current user name/password pair
812      * to authenticate manager. May result in showManagerPasswordError.
813      * @private
814      */
815     validateAndLogInAsManager_: function() {
816       var selectedPod = this.managerList_.selectedPod_;
817       if (null == selectedPod)
818         return;
820       var managerId = selectedPod.user.username;
821       var managerDisplayId = selectedPod.user.emailAddress;
822       var managerPassword = selectedPod.passwordElement.value;
823       if (managerPassword.length == 0)
824         return;
825       if (this.disabled)
826         return;
827       this.disabled = true;
828       this.context_.managerId = managerId;
829       this.context_.managerDisplayId = managerDisplayId;
830       this.context_.managerName = selectedPod.user.displayName;
831       chrome.send('authenticateManagerInSupervisedUserCreationFlow',
832           [managerId, managerPassword]);
833     },
835     /**
836      * Does sanity check and calls backend with user display name/password pair
837      * to create a user.
838      * @private
839      */
840     validateAndCreateSupervisedUser_: function() {
841       var firstPassword = $('supervised-user-creation-password').value;
842       var secondPassword =
843           $('supervised-user-creation-password-confirm').value;
844       var userName = $('supervised-user-creation-name').value;
845       if (firstPassword != secondPassword) {
846         this.showPasswordError(loadTimeData.getString(
847             'createSupervisedUserPasswordMismatchError'));
848         return;
849       }
850       if (this.disabled)
851         return;
852       this.disabled = true;
854       this.context_.supervisedName = userName;
855       chrome.send('specifySupervisedUserCreationFlowUserData',
856           [userName, firstPassword]);
857     },
859     /**
860      * Does sanity check and calls backend with selected existing supervised
861      * user id to import user.
862      * @private
863      */
864     importSupervisedUser_: function() {
865       if (this.disabled)
866         return;
867       if (this.currentPage_ == 'import-password') {
868         var firstPassword = this.getScreenElement('password').value;
869         var secondPassword = this.getScreenElement('password-confirm').value;
870         if (firstPassword != secondPassword) {
871           this.showPasswordError(loadTimeData.getString(
872               'createSupervisedUserPasswordMismatchError'));
873           return;
874         }
875         var userId = this.context_.importUserId;
876         this.disabled = true;
877         chrome.send('importSupervisedUserWithPassword',
878             [userId, firstPassword]);
879         return;
880       } else {
881         var selectedPod = this.importList_.selectedPod_;
882         if (!selectedPod)
883           return;
884         var user = selectedPod.user;
885         var userId = user.id;
887         this.context_.importUserId = userId;
888         this.context_.supervisedName = user.name;
889         this.context_.selectedImageUrl = user.avatarurl;
890         if (!user.needPassword) {
891           this.disabled = true;
892           chrome.send('importSupervisedUser', [userId]);
893         } else {
894           this.setVisiblePage_('import-password');
895         }
896       }
897     },
899     /**
900      * Calls backend part to check if current user name is valid/not taken.
901      * Results in a call to either supervisedUserNameOk or
902      * supervisedUserNameError.
903      * @private
904      */
905     checkUserName_: function() {
906       var userName = this.getScreenElement('name').value;
908       // Avoid flickering
909       if (userName == this.lastIncorrectUserName_ ||
910           userName == this.lastVerifiedName_) {
911         return;
912       }
913       if (userName.length > 0) {
914         chrome.send('checkSupervisedUserName', [userName]);
915       } else {
916         this.nameErrorVisible = false;
917         this.lastVerifiedName_ = null;
918         this.lastIncorrectUserName_ = null;
919         this.updateNextButtonForUser_();
920       }
921     },
923     /**
924      * Called by backend part in case of successful name validation.
925      * @param {string} name - name that was validated.
926      */
927     supervisedUserNameOk: function(name) {
928       this.lastVerifiedName_ = name;
929       this.lastIncorrectUserName_ = null;
930       if ($('supervised-user-creation-name').value == name)
931         this.clearUserNameError_();
932       this.updateNextButtonForUser_();
933     },
935     /**
936      * Called by backend part in case of name validation failure.
937      * @param {string} name - name that was validated.
938      * @param {string} errorText - reason why this name is invalid.
939      */
940     supervisedUserNameError: function(name, errorText) {
941       this.disabled = false;
942       this.lastIncorrectUserName_ = name;
943       this.lastVerifiedName_ = null;
945       var userNameField = $('supervised-user-creation-name');
946       if (userNameField.value == this.lastIncorrectUserName_) {
947         this.nameErrorVisible = true;
948         $('bubble').showTextForElement(
949             $('supervised-user-creation-name'),
950             errorText,
951             cr.ui.Bubble.Attachment.RIGHT,
952             12, 4);
953         this.setButtonDisabledStatus('next', true);
954       }
955     },
957     supervisedUserSuggestImport: function(name, user_id) {
958       this.disabled = false;
959       this.lastIncorrectUserName_ = name;
960       this.lastVerifiedName_ = null;
962       var userNameField = $('supervised-user-creation-name');
963       var creationScreen = this;
965       if (userNameField.value == this.lastIncorrectUserName_) {
966         this.nameErrorVisible = true;
967         var link = this.ownerDocument.createElement('div');
968         link.innerHTML = loadTimeData.getStringF(
969             'importBubbleText',
970             '<a class="signin-link" href="#">',
971             name,
972             '</a>');
973         link.querySelector('.signin-link').addEventListener('click',
974             function(e) {
975               creationScreen.handleSuggestImport_(user_id);
976               e.stopPropagation();
977             });
978         $('bubble').showContentForElement(
979             $('supervised-user-creation-name'),
980             cr.ui.Bubble.Attachment.RIGHT,
981             link,
982             12, 4);
983         this.setButtonDisabledStatus('next', true);
984       }
985     },
987     /**
988      * Clears user name error, if name is no more guaranteed to be invalid.
989      * @private
990      */
991     clearUserNameError_: function() {
992       // Avoid flickering
993       if ($('supervised-user-creation-name').value ==
994               this.lastIncorrectUserName_) {
995         return;
996       }
997       this.nameErrorVisible = false;
998     },
1000     /**
1001      * Called by backend part in case of password validation failure.
1002      * @param {string} errorText - reason why this password is invalid.
1003      */
1004     showPasswordError: function(errorText) {
1005       $('bubble').showTextForElement(
1006           $('supervised-user-creation-password'),
1007           errorText,
1008           cr.ui.Bubble.Attachment.RIGHT,
1009           12, 4);
1010       $('supervised-user-creation-password').classList.add('password-error');
1011       $('supervised-user-creation-password').focus();
1012       this.disabled = false;
1013       this.setButtonDisabledStatus('next', true);
1014     },
1016     /**
1017      * True if user name error should be displayed.
1018      * @type {boolean}
1019      */
1020     set nameErrorVisible(value) {
1021       $('supervised-user-creation-name').
1022           classList.toggle('duplicate-name', value);
1023       if (!value)
1024         $('bubble').hide();
1025     },
1027     /**
1028      * Updates state of Continue button after minimal checks.
1029      * @return {boolean} true, if form seems to be valid.
1030      * @private
1031      */
1032     updateNextButtonForManager_: function() {
1033       var selectedPod = this.managerList_.selectedPod_;
1034       canProceed = null != selectedPod &&
1035                    selectedPod.passwordElement.value.length > 0;
1037       this.setButtonDisabledStatus('next', !canProceed);
1038       return canProceed;
1039     },
1041     /**
1042      * Updates state of Continue button after minimal checks.
1043      * @return {boolean} true, if form seems to be valid.
1044      * @private
1045      */
1046     updateNextButtonForUser_: function() {
1047       var firstPassword = this.getScreenElement('password').value;
1048       var secondPassword = this.getScreenElement('password-confirm').value;
1049       var userName = this.getScreenElement('name').value;
1051       var passwordOk = (firstPassword.length > 0) &&
1052           (firstPassword.length == secondPassword.length);
1054       if (this.currentPage_ == 'import-password') {
1055         this.setButtonDisabledStatus('import', !passwordOk);
1056         return passwordOk;
1057       }
1058       var imageGrid = this.getScreenElement('image-grid');
1059       var imageChosen = !(imageGrid.selectionType == 'camera' &&
1060                           imageGrid.cameraLive);
1061       var canProceed =
1062           passwordOk &&
1063           (userName.length > 0) &&
1064           this.lastVerifiedName_ &&
1065           (userName == this.lastVerifiedName_) &&
1066           imageChosen;
1068       this.setButtonDisabledStatus('next', !canProceed);
1069       return canProceed;
1070     },
1072     showSelectedManagerPasswordError_: function() {
1073       var selectedPod = this.managerList_.selectedPod_;
1074       selectedPod.showPasswordError();
1075       selectedPod.passwordElement.value = '';
1076       selectedPod.focusInput();
1077       this.updateNextButtonForManager_();
1078     },
1080     /**
1081      * Enables one particular subpage and hides the rest.
1082      * @param {string} visiblePage - name of subpage.
1083      * @private
1084      */
1085     setVisiblePage_: function(visiblePage) {
1086       this.disabled = false;
1087       this.updateText_();
1088       $('bubble').hide();
1089       if (!this.imagesRequested_) {
1090         chrome.send('supervisedUserGetImages');
1091         this.imagesRequested_ = true;
1092       }
1093       var pageNames = ['intro',
1094                        'manager',
1095                        'username',
1096                        'import',
1097                        'error',
1098                        'created'];
1099       var pageButtons = {'intro' : 'start',
1100                          'error' : 'error',
1101                          'import' : 'import',
1102                          'import-password' : 'import',
1103                          'created' : 'gotit'};
1104       this.hideStatus_();
1105       var pageToDisplay = visiblePage;
1106       if (visiblePage == 'import-password')
1107         pageToDisplay = 'username';
1109       for (i in pageNames) {
1110         var pageName = pageNames[i];
1111         var page = $('supervised-user-creation-' + pageName);
1112         page.hidden = (pageName != pageToDisplay);
1113         if (pageName == pageToDisplay)
1114           $('step-logo').hidden = page.classList.contains('step-no-logo');
1115       }
1117       for (i in this.buttonIds) {
1118         var button = this.getScreenButton(this.buttonIds[i]);
1119         button.hidden = button.pages.indexOf(visiblePage) < 0;
1120         button.disabled = false;
1121       }
1123       var pagesWithCancel = ['intro', 'manager', 'username', 'import-password',
1124           'error', 'import'];
1125       $('login-header-bar').allowCancel =
1126           pagesWithCancel.indexOf(visiblePage) > 0;
1127       $('cancel-add-user-button').disabled = false;
1129       this.getScreenElement('import-link').hidden = true;
1130       this.getScreenElement('create-link').hidden = true;
1132       if (pageButtons[visiblePage])
1133         this.getScreenButton(pageButtons[visiblePage]).focus();
1135       this.currentPage_ = visiblePage;
1137       if (visiblePage == 'manager' || visiblePage == 'intro') {
1138         $('supervised-user-creation-password').classList.remove(
1139             'password-error');
1140         if (this.managerList_.pods.length > 0)
1141           this.managerList_.selectPod(this.managerList_.pods[0]);
1142       }
1144       if (visiblePage == 'username' || visiblePage == 'import-password') {
1145         var elements = this.getScreenElement(pageToDisplay).
1146             querySelectorAll('.hide-on-import');
1147         for (var i = 0; i < elements.length; i++) {
1148           elements[i].classList.toggle('hidden-on-import',
1149               visiblePage == 'import-password');
1150         }
1151       }
1152       if (visiblePage == 'username') {
1153         var imageGrid = this.getScreenElement('image-grid');
1154         // select some image.
1155         var selected = this.imagesData_[
1156             Math.floor(Math.random() * this.imagesData_.length)];
1157         this.context_.selectedImageUrl = selected.url;
1158         imageGrid.selectedItemUrl = selected.url;
1159         chrome.send('supervisedUserSelectImage',
1160                     [selected.url, 'default']);
1161         this.getScreenElement('image-grid').redraw();
1162         this.checkUserName_();
1163         this.updateNextButtonForUser_();
1164         this.getScreenElement('name').focus();
1165         this.getScreenElement('import-link').hidden =
1166             this.importList_.pods.length == 0;
1167       } else if (visiblePage == 'import-password') {
1168         var imageGrid = this.getScreenElement('image-grid');
1169         var selected;
1170         if ('selectedImageUrl' in this.context_) {
1171           selected = this.context_.selectedImageUrl;
1172         } else {
1173           // select some image.
1174           selected = this.imagesData_[
1175               Math.floor(Math.random() * this.imagesData_.length)].url;
1176           chrome.send('supervisedUserSelectImage',
1177                       [selected, 'default']);
1178         }
1179         imageGrid.selectedItemUrl = selected;
1180         this.getScreenElement('image-grid').redraw();
1182         this.updateNextButtonForUser_();
1184         this.getScreenElement('password').focus();
1185         this.getScreenElement('import-link').hidden = true;
1186       } else {
1187         this.getScreenElement('image-grid').stopCamera();
1188       }
1189       if (visiblePage == 'import') {
1190         this.getScreenElement('create-link').hidden = false;
1191         this.getScreenButton('import').disabled =
1192             !this.importList_.selectedPod_ ||
1193             this.importList_.selectedPod_.user.exists;
1194       }
1195       chrome.send('currentSupervisedUserPage', [this.currentPage_]);
1196     },
1198     setButtonDisabledStatus: function(buttonName, status) {
1199       var button = $('supervised-user-creation-' + buttonName + '-button');
1200       button.disabled = status;
1201     },
1203     gotItButtonPressed_: function() {
1204       chrome.send('finishLocalSupervisedUserCreation');
1205     },
1207     handleErrorButtonPressed_: function() {
1208       chrome.send('abortLocalSupervisedUserCreation');
1209     },
1211     startButtonPressed_: function() {
1212       this.setVisiblePage_('manager');
1213       this.setButtonDisabledStatus('next', true);
1214     },
1216     nextButtonPressed_: function() {
1217       if (this.currentPage_ == 'manager') {
1218         this.validateAndLogInAsManager_();
1219         return;
1220       }
1221       if (this.currentPage_ == 'username') {
1222         this.validateAndCreateSupervisedUser_();
1223       }
1224     },
1226     importButtonPressed_: function() {
1227       this.importSupervisedUser_();
1228     },
1230     importLinkPressed_: function() {
1231       this.setVisiblePage_('import');
1232     },
1234     handleSuggestImport_: function(user_id) {
1235       this.setVisiblePage_('import');
1236       this.importList_.selectUser(user_id);
1237     },
1239     createLinkPressed_: function() {
1240       this.setVisiblePage_('username');
1241       this.lastIncorrectUserName_ = null;
1242       this.lastVerifiedName_ = null;
1243       this.checkUserName_();
1244     },
1246     prevButtonPressed_: function() {
1247       this.setVisiblePage_('intro');
1248     },
1250     showProgress: function(text) {
1251       var status = this.getScreenElement('status');
1252       var statusText = status.querySelector('.id-text');
1253       statusText.textContent = text;
1254       statusText.classList.remove('error');
1255       status.querySelector('.id-spinner').hidden = false;
1256       status.hidden = false;
1257       this.getScreenElement('import-link').hidden = true;
1258       this.getScreenElement('create-link').hidden = true;
1259     },
1261     showStatusError: function(text) {
1262       var status = this.getScreenElement('status');
1263       var statusText = status.querySelector('.id-text');
1264       statusText.textContent = text;
1265       statusText.classList.add('error');
1266       status.querySelector('.id-spinner').hidden = true;
1267       status.hidden = false;
1268       this.getScreenElement('import-link').hidden = true;
1269       this.getScreenElement('create-link').hidden = true;
1270     },
1272     hideStatus_: function() {
1273       var status = this.getScreenElement('status');
1274       status.hidden = true;
1275     },
1277     /**
1278      * Updates state of login header so that necessary buttons are displayed.
1279      **/
1280     onBeforeShow: function(data) {
1281       $('login-header-bar').signinUIState =
1282           SIGNIN_UI_STATE.SUPERVISED_USER_CREATION_FLOW;
1283       if (data['managers']) {
1284         this.loadManagers(data['managers']);
1285       }
1286       var imageGrid = this.getScreenElement('image-grid');
1287       imageGrid.updateAndFocus();
1288     },
1290     /**
1291      * Update state of login header so that necessary buttons are displayed.
1292      */
1293     onBeforeHide: function() {
1294       $('login-header-bar').signinUIState = SIGNIN_UI_STATE.HIDDEN;
1295       this.getScreenElement('image-grid').stopCamera();
1296     },
1298     /**
1299      * Returns a control which should receive an initial focus.
1300      */
1301     get defaultControl() {
1302       return $('supervised-user-creation-name');
1303     },
1305     /**
1306      * True if the the screen is disabled (handles no user interaction).
1307      * @type {boolean}
1308      */
1309     disabled_: false,
1311     get disabled() {
1312       return this.disabled_;
1313     },
1315     set disabled(value) {
1316       this.disabled_ = value;
1317       var controls = this.querySelectorAll('button,input');
1318       for (var i = 0, control; control = controls[i]; ++i) {
1319         control.disabled = value;
1320       }
1321       $('login-header-bar').disabled = value;
1322       $('cancel-add-user-button').disabled = false;
1323     },
1325     /**
1326      * Called by backend part to propagate list of possible managers.
1327      * @param {Array} userList - list of users that can be managers.
1328      */
1329     loadManagers: function(userList) {
1330       $('supervised-user-creation-managers-block').hidden = false;
1331       this.managerList_.clearPods();
1332       for (var i = 0; i < userList.length; ++i)
1333         this.managerList_.addPod(userList[i]);
1334       if (userList.length > 0)
1335         this.managerList_.selectPod(this.managerList_.pods[0]);
1336     },
1338     /**
1339      * Cancels user creation and drops to user screen (either sign).
1340      */
1341     cancel: function() {
1342       var notSignedInPages = ['intro', 'manager'];
1343       var postCreationPages = ['created'];
1344       if (notSignedInPages.indexOf(this.currentPage_) >= 0) {
1345         // Make sure no manager password is kept:
1346         this.managerList_.clearPods();
1348         $('pod-row').loadLastWallpaper();
1350         Oobe.showScreen({id: SCREEN_ACCOUNT_PICKER});
1351         Oobe.resetSigninUI(true);
1352         return;
1353       }
1354       if (postCreationPages.indexOf(this.currentPage_) >= 0) {
1355         chrome.send('finishLocalSupervisedUserCreation');
1356         return;
1357       }
1358       chrome.send('abortLocalSupervisedUserCreation');
1359     },
1361     updateText_: function() {
1362       var managerDisplayId = this.context_.managerDisplayId;
1363       this.updateElementText_('intro-alternate-text',
1364                               'createSupervisedUserIntroAlternateText');
1365       this.updateElementText_('created-text-1',
1366                               'createSupervisedUserCreatedText1',
1367                               this.context_.supervisedName);
1368       // TODO(antrim): Move wrapping with strong in grd file, and eliminate this
1369       //call.
1370       this.updateElementText_('created-text-2',
1371                               'createSupervisedUserCreatedText2',
1372                               this.wrapStrong(
1373                                   loadTimeData.getString('managementURL')),
1374                                   this.context_.supervisedName);
1375       this.updateElementText_('created-text-3',
1376                               'createSupervisedUserCreatedText3',
1377                               managerDisplayId);
1378       this.updateElementText_('name-explanation',
1379                               'createSupervisedUserNameExplanation',
1380                               managerDisplayId);
1381     },
1383     wrapStrong: function(original) {
1384       if (original == undefined)
1385         return original;
1386       return '<strong>' + original + '</strong>';
1387     },
1389     updateElementText_: function(localId, templateName) {
1390       var args = Array.prototype.slice.call(arguments);
1391       args.shift();
1392       this.getScreenElement(localId).innerHTML =
1393           loadTimeData.getStringF.apply(loadTimeData, args);
1394     },
1396     showIntroPage: function() {
1397       $('supervised-user-creation-password').value = '';
1398       $('supervised-user-creation-password-confirm').value = '';
1399       $('supervised-user-creation-name').value = '';
1401       this.lastVerifiedName_ = null;
1402       this.lastIncorrectUserName_ = null;
1403       this.passwordErrorVisible = false;
1404       $('supervised-user-creation-password').classList.remove('password-error');
1405       this.nameErrorVisible = false;
1407       this.setVisiblePage_('intro');
1408     },
1410     showManagerPage: function() {
1411       this.setVisiblePage_('manager');
1412     },
1414     showUsernamePage: function() {
1415       this.setVisiblePage_('username');
1416     },
1418     showTutorialPage: function() {
1419       this.setVisiblePage_('created');
1420     },
1422     showPage: function(page) {
1423       this.setVisiblePage_(page);
1424     },
1426     showErrorPage: function(errorTitle, errorText, errorButtonText) {
1427       this.disabled = false;
1428       $('supervised-user-creation-error-title').innerHTML = errorTitle;
1429       $('supervised-user-creation-error-text').innerHTML = errorText;
1430       $('supervised-user-creation-error-button').textContent = errorButtonText;
1431       this.setVisiblePage_('error');
1432     },
1434     showManagerPasswordError: function() {
1435       this.disabled = false;
1436       this.showSelectedManagerPasswordError_();
1437     },
1439     /*
1440     TODO(antrim) : this is an explicit code duplications with UserImageScreen.
1441     It should be removed by issue 251179.
1442     */
1443     /**
1444      * Currently selected user image index (take photo button is with zero
1445      * index).
1446      * @type {number}
1447      */
1448     selectedUserImage_: -1,
1449     imagesData: [],
1451     setDefaultImages: function(imagesData) {
1452       var imageGrid = this.getScreenElement('image-grid');
1453       imageGrid.setDefaultImages(imagesData);
1454       this.imagesData_ = imagesData;
1455     },
1458     handleActivate_: function() {
1459       var imageGrid = this.getScreenElement('image-grid');
1460       if (imageGrid.selectedItemUrl == ButtonImages.TAKE_PHOTO) {
1461         this.handleTakePhoto_();
1462         return;
1463       }
1464       this.nextButtonPressed_();
1465     },
1467     /**
1468      * Handles selection change.
1469      * @param {Event} e Selection change event.
1470      * @private
1471      */
1472     handleSelect_: function(e) {
1473       var imageGrid = this.getScreenElement('image-grid');
1474       this.updateNextButtonForUser_();
1476       $('supervised-user-creation-flip-photo').tabIndex =
1477           (imageGrid.selectionType == 'camera') ? 0 : -1;
1478       if (imageGrid.cameraLive || imageGrid.selectionType != 'camera')
1479         imageGrid.previewElement.classList.remove('phototaken');
1480       else
1481         imageGrid.previewElement.classList.add('phototaken');
1483       if (!imageGrid.cameraLive || imageGrid.selectionType != 'camera') {
1484         this.context_.selectedImageUrl = imageGrid.selectedItemUrl;
1485         chrome.send('supervisedUserSelectImage',
1486                     [imageGrid.selectedItemUrl, imageGrid.selectionType]);
1487       }
1488       // Start/stop camera on (de)selection.
1489       if (!imageGrid.inProgramSelection &&
1490           imageGrid.selectionType != e.oldSelectionType) {
1491         if (imageGrid.selectionType == 'camera') {
1492           // Programmatic selection of camera item is done in
1493           // startCamera callback where streaming is started by itself.
1494           imageGrid.startCamera(
1495               function() {
1496                 // Start capture if camera is still the selected item.
1497                 $('supervised-user-creation-image-preview-img').classList.
1498                     toggle('animated-transform', true);
1499                 return imageGrid.selectedItem == imageGrid.cameraImage;
1500               });
1501         } else {
1502           $('supervised-user-creation-image-preview-img').classList.toggle(
1503               'animated-transform', false);
1504           imageGrid.stopCamera();
1505         }
1506       }
1507     },
1509     /**
1510      * Handle camera-photo flip.
1511      */
1512     handleFlipPhoto_: function() {
1513       var imageGrid = this.getScreenElement('image-grid');
1514       imageGrid.previewElement.classList.add('animation');
1515       imageGrid.flipPhoto = !imageGrid.flipPhoto;
1516       var flipMessageId = imageGrid.flipPhoto ?
1517          'photoFlippedAccessibleText' : 'photoFlippedBackAccessibleText';
1518       announceAccessibleMessage(loadTimeData.getString(flipMessageId));
1519     },
1521     /**
1522      * Handle photo capture from the live camera stream.
1523      */
1524     handleTakePhoto_: function(e) {
1525       this.getScreenElement('image-grid').takePhoto();
1526       chrome.send('supervisedUserTakePhoto');
1527     },
1529     handlePhotoTaken_: function(e) {
1530       chrome.send('supervisedUserPhotoTaken', [e.dataURL]);
1531       announceAccessibleMessage(
1532           loadTimeData.getString('photoCaptureAccessibleText'));
1533     },
1535     /**
1536      * Handle photo updated event.
1537      * @param {Event} e Event with 'dataURL' property containing a data URL.
1538      */
1539     handlePhotoUpdated_: function(e) {
1540       chrome.send('supervisedUserPhotoTaken', [e.dataURL]);
1541     },
1543     /**
1544      * Handle discarding the captured photo.
1545      */
1546     handleDiscardPhoto_: function(e) {
1547       var imageGrid = this.getScreenElement('image-grid');
1548       imageGrid.discardPhoto();
1549       chrome.send('supervisedUserDiscardPhoto');
1550       announceAccessibleMessage(
1551           loadTimeData.getString('photoDiscardAccessibleText'));
1552     },
1554     setCameraPresent: function(present) {
1555       this.getScreenElement('image-grid').cameraPresent = present;
1556     },
1558     setExistingSupervisedUsers: function(users) {
1559       var selectedUser = null;
1560       // Store selected user
1561       if (this.importList_.selectedPod)
1562         selectedUser = this.importList_.selectedPod.user.id;
1564       var userList = users;
1565       userList.sort(function(a, b) {
1566         // Put existing users last.
1567         if (a.exists != b.exists)
1568           return a.exists ? 1 : -1;
1569         // Sort rest by name.
1570         return a.name.localeCompare(b.name, [], {sensitivity: 'base'});
1571       });
1573       this.importList_.clearPods();
1574       var selectedIndex = -1;
1575       for (var i = 0; i < userList.length; ++i) {
1576         this.importList_.addPod(userList[i]);
1577         if (selectedUser == userList[i].id)
1578           selectedIndex = i;
1579       }
1581       if (userList.length == 1)
1582         this.importList_.selectPod(this.importList_.pods[0]);
1584       if (selectedIndex >= 0)
1585         this.importList_.selectPod(this.importList_.pods[selectedIndex]);
1587       if (this.currentPage_ == 'username')
1588         this.getScreenElement('import-link').hidden = (userList.length == 0);
1589     },
1590   };