docs.js revision 52dd206715e9d8e80a22c7106ec783324d51e747
1var classesNav;
2var devdocNav;
3var sidenav;
4var cookie_namespace = 'android_developer';
5var NAV_PREF_TREE = "tree";
6var NAV_PREF_PANELS = "panels";
7var nav_pref;
8var isMobile = false; // true if mobile, so we can adjust some layout
9var mPagePath; // initialized in ready() function
10
11var basePath = getBaseUri(location.pathname);
12var SITE_ROOT = toRoot + basePath.substring(1,basePath.indexOf("/",1));
13var GOOGLE_DATA; // combined data for google service apis, used for search suggest
14
15// Ensure that all ajax getScript() requests allow caching
16$.ajaxSetup({
17  cache: true
18});
19
20/******  ON LOAD SET UP STUFF *********/
21
22var navBarIsFixed = false;
23$(document).ready(function() {
24
25  // load json file for JD doc search suggestions
26  $.getScript(toRoot + 'reference/jd_lists.js');
27  // load json file for Android API search suggestions
28  $.getScript(toRoot + 'reference/lists.js');
29  // load json files for Google services API suggestions
30  $.getScript(toRoot + 'reference/gcm_lists.js', function(data, textStatus, jqxhr) {
31      // once the GCM json (GCM_DATA) is loaded, load the GMS json (GMS_DATA) and merge the data
32      if(jqxhr.status === 200) {
33          $.getScript(toRoot + 'reference/gms_lists.js', function(data, textStatus, jqxhr) {
34              if(jqxhr.status === 200) {
35                  // combine GCM and GMS data
36                  GOOGLE_DATA = GMS_DATA;
37                  var start = GOOGLE_DATA.length;
38                  for (var i=0; i<GCM_DATA.length; i++) {
39                      GOOGLE_DATA.push({id:start+i, label:GCM_DATA[i].label,
40                              link:GCM_DATA[i].link, type:GCM_DATA[i].type});
41                  }
42              }
43          });
44      }
45  });
46
47  // setup keyboard listener for search shortcut
48  $('body').keyup(function(event) {
49    if (event.which == 191) {
50      $('#search_autocomplete').focus();
51    }
52  });
53
54  // init the fullscreen toggle click event
55  $('#nav-swap .fullscreen').click(function(){
56    if ($(this).hasClass('disabled')) {
57      toggleFullscreen(true);
58    } else {
59      toggleFullscreen(false);
60    }
61  });
62
63  // initialize the divs with custom scrollbars
64  $('.scroll-pane').jScrollPane( {verticalGutter:0} );
65
66  // add HRs below all H2s (except for a few other h2 variants)
67  $('h2').not('#qv h2').not('#tb h2').not('.sidebox h2').not('#devdoc-nav h2').not('h2.norule').css({marginBottom:0}).after('<hr/>');
68
69  // set up the search close button
70  $('.search .close').click(function() {
71    $searchInput = $('#search_autocomplete');
72    $searchInput.attr('value', '');
73    $(this).addClass("hide");
74    $("#search-container").removeClass('active');
75    $("#search_autocomplete").blur();
76    search_focus_changed($searchInput.get(), false);
77    hideResults();
78  });
79
80  // Set up quicknav
81  var quicknav_open = false;
82  $("#btn-quicknav").click(function() {
83    if (quicknav_open) {
84      $(this).removeClass('active');
85      quicknav_open = false;
86      collapse();
87    } else {
88      $(this).addClass('active');
89      quicknav_open = true;
90      expand();
91    }
92  })
93
94  var expand = function() {
95   $('#header-wrap').addClass('quicknav');
96   $('#quicknav').stop().show().animate({opacity:'1'});
97  }
98
99  var collapse = function() {
100    $('#quicknav').stop().animate({opacity:'0'}, 100, function() {
101      $(this).hide();
102      $('#header-wrap').removeClass('quicknav');
103    });
104  }
105
106
107  //Set up search
108  $("#search_autocomplete").focus(function() {
109    $("#search-container").addClass('active');
110  })
111  $("#search-container").mouseover(function() {
112    $("#search-container").addClass('active');
113    $("#search_autocomplete").focus();
114  })
115  $("#search-container").mouseout(function() {
116    if ($("#search_autocomplete").is(":focus")) return;
117    if ($("#search_autocomplete").val() == '') {
118      setTimeout(function(){
119        $("#search-container").removeClass('active');
120        $("#search_autocomplete").blur();
121      },250);
122    }
123  })
124  $("#search_autocomplete").blur(function() {
125    if ($("#search_autocomplete").val() == '') {
126      $("#search-container").removeClass('active');
127    }
128  })
129
130
131  // prep nav expandos
132  var pagePath = document.location.pathname;
133  // account for intl docs by removing the intl/*/ path
134  if (pagePath.indexOf("/intl/") == 0) {
135    pagePath = pagePath.substr(pagePath.indexOf("/",6)); // start after intl/ to get last /
136  }
137
138  if (pagePath.indexOf(SITE_ROOT) == 0) {
139    if (pagePath == '' || pagePath.charAt(pagePath.length - 1) == '/') {
140      pagePath += 'index.html';
141    }
142  }
143
144  // Need a copy of the pagePath before it gets changed in the next block;
145  // it's needed to perform proper tab highlighting in offline docs (see rootDir below)
146  var pagePathOriginal = pagePath;
147  if (SITE_ROOT.match(/\.\.\//) || SITE_ROOT == '') {
148    // If running locally, SITE_ROOT will be a relative path, so account for that by
149    // finding the relative URL to this page. This will allow us to find links on the page
150    // leading back to this page.
151    var pathParts = pagePath.split('/');
152    var relativePagePathParts = [];
153    var upDirs = (SITE_ROOT.match(/(\.\.\/)+/) || [''])[0].length / 3;
154    for (var i = 0; i < upDirs; i++) {
155      relativePagePathParts.push('..');
156    }
157    for (var i = 0; i < upDirs; i++) {
158      relativePagePathParts.push(pathParts[pathParts.length - (upDirs - i) - 1]);
159    }
160    relativePagePathParts.push(pathParts[pathParts.length - 1]);
161    pagePath = relativePagePathParts.join('/');
162  } else {
163    // Otherwise the page path is already an absolute URL
164  }
165
166  // Highlight the header tabs...
167  // highlight Design tab
168  if ($("body").hasClass("design")) {
169    $("#header li.design a").addClass("selected");
170
171  // highlight Develop tab
172  } else if ($("body").hasClass("develop") || $("body").hasClass("google")) {
173    $("#header li.develop a").addClass("selected");
174    // In Develop docs, also highlight appropriate sub-tab
175    var rootDir = pagePathOriginal.substring(1,pagePathOriginal.indexOf('/', 1));
176    if (rootDir == "training") {
177      $("#nav-x li.training a").addClass("selected");
178    } else if (rootDir == "guide") {
179      $("#nav-x li.guide a").addClass("selected");
180    } else if (rootDir == "reference") {
181      // If the root is reference, but page is also part of Google Services, select Google
182      if ($("body").hasClass("google")) {
183        $("#nav-x li.google a").addClass("selected");
184      } else {
185        $("#nav-x li.reference a").addClass("selected");
186      }
187    } else if ((rootDir == "tools") || (rootDir == "sdk")) {
188      $("#nav-x li.tools a").addClass("selected");
189    } else if ($("body").hasClass("google")) {
190      $("#nav-x li.google a").addClass("selected");
191    }
192
193  // highlight Distribute tab
194  } else if ($("body").hasClass("distribute")) {
195    $("#header li.distribute a").addClass("selected");
196  }
197
198  // set global variable so we can highlight the sidenav a bit later (such as for google reference)
199  // and highlight the sidenav
200  mPagePath = pagePath;
201  highlightSidenav();
202
203  // set up prev/next links if they exist
204  var $selNavLink = $('#nav').find('a[href="' + pagePath + '"]');
205  var $selListItem;
206  if ($selNavLink.length) {
207    $selListItem = $selNavLink.closest('li');
208
209    // set up prev links
210    var $prevLink = [];
211    var $prevListItem = $selListItem.prev('li');
212
213    var crossBoundaries = ($("body.design").length > 0) || ($("body.guide").length > 0) ? true :
214false; // navigate across topic boundaries only in design docs
215    if ($prevListItem.length) {
216      if ($prevListItem.hasClass('nav-section')) {
217        // jump to last topic of previous section
218        $prevLink = $prevListItem.find('a:last');
219      } else if (!$selListItem.hasClass('nav-section')) {
220        // jump to previous topic in this section
221        $prevLink = $prevListItem.find('a:eq(0)');
222      }
223    } else {
224      // jump to this section's index page (if it exists)
225      var $parentListItem = $selListItem.parents('li');
226      $prevLink = $selListItem.parents('li').find('a');
227
228      // except if cross boundaries aren't allowed, and we're at the top of a section already
229      // (and there's another parent)
230      if (!crossBoundaries && $parentListItem.hasClass('nav-section')
231                           && $selListItem.hasClass('nav-section')) {
232        $prevLink = [];
233      }
234    }
235
236    // set up next links
237    var $nextLink = [];
238    var startClass = false;
239    var training = $(".next-class-link").length; // decides whether to provide "next class" link
240    var isCrossingBoundary = false;
241
242    if ($selListItem.hasClass('nav-section')) {
243      // we're on an index page, jump to the first topic
244      $nextLink = $selListItem.find('ul:eq(0)').find('a:eq(0)');
245
246      // if there aren't any children, go to the next section (required for About pages)
247      if($nextLink.length == 0) {
248        $nextLink = $selListItem.next('li').find('a');
249      } else if ($('.topic-start-link').length) {
250        // as long as there's a child link and there is a "topic start link" (we're on a landing)
251        // then set the landing page "start link" text to be the first doc title
252        $('.topic-start-link').text($nextLink.text().toUpperCase());
253      }
254
255      // If the selected page has a description, then it's a class or article homepage
256      if ($selListItem.find('a[description]').length) {
257        // this means we're on a class landing page
258        startClass = true;
259      }
260    } else {
261      // jump to the next topic in this section (if it exists)
262      $nextLink = $selListItem.next('li').find('a:eq(0)');
263      if (!$nextLink.length) {
264        isCrossingBoundary = true;
265        // no more topics in this section, jump to the first topic in the next section
266        $nextLink = $selListItem.parents('li:eq(0)').next('li.nav-section').find('a:eq(0)');
267        if (!$nextLink.length) {  // Go up another layer to look for next page (lesson > class > course)
268          $nextLink = $selListItem.parents('li:eq(1)').next('li.nav-section').find('a:eq(0)');
269        }
270      }
271    }
272
273    if (startClass) {
274      $('.start-class-link').attr('href', $nextLink.attr('href')).removeClass("hide");
275
276      // if there's no training bar (below the start button),
277      // then we need to add a bottom border to button
278      if (!$("#tb").length) {
279        $('.start-class-link').css({'border-bottom':'1px solid #DADADA'});
280      }
281    } else if (isCrossingBoundary && !$('body.design').length) {  // Design always crosses boundaries
282      $('.content-footer.next-class').show();
283      $('.next-page-link').attr('href','')
284                          .removeClass("hide").addClass("disabled")
285                          .click(function() { return false; });
286
287      $('.next-class-link').attr('href',$nextLink.attr('href'))
288                          .removeClass("hide").append($nextLink.html());
289      $('.next-class-link').find('.new').empty();
290    } else {
291      $('.next-page-link').attr('href', $nextLink.attr('href')).removeClass("hide");
292    }
293
294    if (!startClass && $prevLink.length) {
295      var prevHref = $prevLink.attr('href');
296      if (prevHref == SITE_ROOT + 'index.html') {
297        // Don't show Previous when it leads to the homepage
298      } else {
299        $('.prev-page-link').attr('href', $prevLink.attr('href')).removeClass("hide");
300      }
301    }
302
303    // If this is a training 'article', there should be no prev/next nav
304    // ... if the grandparent is the "nav" ... and it has no child list items...
305    if (training && $selListItem.parents('ul').eq(1).is('[id="nav"]') &&
306        !$selListItem.find('li').length) {
307      $('.next-page-link,.prev-page-link').attr('href','').addClass("disabled")
308                          .click(function() { return false; });
309    }
310
311  }
312
313
314
315  // Set up the course landing pages for Training with class names and descriptions
316  if ($('body.trainingcourse').length) {
317    var $classLinks = $selListItem.find('ul li a').not('#nav .nav-section .nav-section ul a');
318    var $classDescriptions = $classLinks.attr('description');
319
320    var $olClasses  = $('<ol class="class-list"></ol>');
321    var $liClass;
322    var $imgIcon;
323    var $h2Title;
324    var $pSummary;
325    var $olLessons;
326    var $liLesson;
327    $classLinks.each(function(index) {
328      $liClass  = $('<li></li>');
329      $h2Title  = $('<a class="title" href="'+$(this).attr('href')+'"><h2>' + $(this).html()+'</h2><span></span></a>');
330      $pSummary = $('<p class="description">' + $(this).attr('description') + '</p>');
331
332      $olLessons  = $('<ol class="lesson-list"></ol>');
333
334      $lessons = $(this).closest('li').find('ul li a');
335
336      if ($lessons.length) {
337        $imgIcon = $('<img src="'+toRoot+'assets/images/resource-tutorial.png" '
338            + ' width="64" height="64" alt=""/>');
339        $lessons.each(function(index) {
340          $olLessons.append('<li><a href="'+$(this).attr('href')+'">' + $(this).html()+'</a></li>');
341        });
342      } else {
343        $imgIcon = $('<img src="'+toRoot+'assets/images/resource-article.png" '
344            + ' width="64" height="64" alt=""/>');
345        $pSummary.addClass('article');
346      }
347
348      $liClass.append($h2Title).append($imgIcon).append($pSummary).append($olLessons);
349      $olClasses.append($liClass);
350    });
351    $('.jd-descr').append($olClasses);
352  }
353
354
355
356
357  // Set up expand/collapse behavior
358  $('#nav li.nav-section .nav-section-header').click(function() {
359    var section = $(this).closest('li.nav-section');
360    if (section.hasClass('expanded')) {
361    /* hide me */
362    //  if (section.hasClass('selected') || section.find('li').hasClass('selected')) {
363   //   /* but not if myself or my descendents are selected */
364   //     return;
365    //  }
366      section.children('ul').slideUp(250, function() {
367        section.closest('li').removeClass('expanded');
368        resizeNav();
369      });
370    } else {
371    /* show me */
372      // first hide all other siblings
373      var $others = $('li.nav-section.expanded', $(this).closest('ul'));
374      $others.removeClass('expanded').children('ul').slideUp(250);
375
376      // now expand me
377      section.closest('li').addClass('expanded');
378      section.children('ul').slideDown(250, function() {
379        resizeNav();
380      });
381    }
382  });
383
384  $(".scroll-pane").scroll(function(event) {
385      event.preventDefault();
386      return false;
387  });
388
389  /* Resize nav height when window height changes */
390  $(window).resize(function() {
391    if ($('#side-nav').length == 0) return;
392    var stylesheet = $('link[rel="stylesheet"][class="fullscreen"]');
393    setNavBarLeftPos(); // do this even if sidenav isn't fixed because it could become fixed
394    // make sidenav behave when resizing the window and side-scolling is a concern
395    if (navBarIsFixed) {
396      if ((stylesheet.attr("disabled") == "disabled") || stylesheet.length == 0) {
397        updateSideNavPosition();
398      } else {
399        updateSidenavFullscreenWidth();
400      }
401    }
402    resizeNav();
403  });
404
405
406  // Set up fixed navbar
407  var prevScrollLeft = 0; // used to compare current position to previous position of horiz scroll
408  $(window).scroll(function(event) {
409    if ($('#side-nav').length == 0) return;
410    if (event.target.nodeName == "DIV") {
411      // Dump scroll event if the target is a DIV, because that means the event is coming
412      // from a scrollable div and so there's no need to make adjustments to our layout
413      return;
414    }
415    var scrollTop = $(window).scrollTop();
416    var headerHeight = $('#header').outerHeight();
417    var subheaderHeight = $('#nav-x').outerHeight();
418    var searchResultHeight = $('#searchResults').is(":visible") ?
419                             $('#searchResults').outerHeight() : 0;
420    var totalHeaderHeight = headerHeight + subheaderHeight + searchResultHeight;
421    // we set the navbar fixed when the scroll position is beyond the height of the site header...
422    var navBarShouldBeFixed = scrollTop > totalHeaderHeight;
423    // ... except if the document content is shorter than the sidenav height.
424    // (this is necessary to avoid crazy behavior on OSX Lion due to overscroll bouncing)
425    if ($("#doc-col").height() < $("#side-nav").height()) {
426      navBarShouldBeFixed = false;
427    }
428
429    var scrollLeft = $(window).scrollLeft();
430    // When the sidenav is fixed and user scrolls horizontally, reposition the sidenav to match
431    if (navBarIsFixed && (scrollLeft != prevScrollLeft)) {
432      updateSideNavPosition();
433      prevScrollLeft = scrollLeft;
434    }
435
436    // Don't continue if the header is sufficently far away
437    // (to avoid intensive resizing that slows scrolling)
438    if (navBarIsFixed && navBarShouldBeFixed) {
439      return;
440    }
441
442    if (navBarIsFixed != navBarShouldBeFixed) {
443      if (navBarShouldBeFixed) {
444        // make it fixed
445        var width = $('#devdoc-nav').width();
446        $('#devdoc-nav')
447            .addClass('fixed')
448            .css({'width':width+'px'})
449            .prependTo('#body-content');
450        // add neato "back to top" button
451        $('#devdoc-nav a.totop').css({'display':'block','width':$("#nav").innerWidth()+'px'});
452
453        // update the sidenaav position for side scrolling
454        updateSideNavPosition();
455      } else {
456        // make it static again
457        $('#devdoc-nav')
458            .removeClass('fixed')
459            .css({'width':'auto','margin':''})
460            .prependTo('#side-nav');
461        $('#devdoc-nav a.totop').hide();
462      }
463      navBarIsFixed = navBarShouldBeFixed;
464    }
465
466    resizeNav(250); // pass true in order to delay the scrollbar re-initialization for performance
467  });
468
469
470  var navBarLeftPos;
471  if ($('#devdoc-nav').length) {
472    setNavBarLeftPos();
473  }
474
475
476  // Stop expand/collapse behavior when clicking on nav section links (since we're navigating away
477  // from the page)
478  $('.nav-section-header').find('a:eq(0)').click(function(evt) {
479    window.location.href = $(this).attr('href');
480    return false;
481  });
482
483  // Set up play-on-hover <video> tags.
484  $('video.play-on-hover').bind('click', function(){
485    $(this).get(0).load(); // in case the video isn't seekable
486    $(this).get(0).play();
487  });
488
489  // Set up tooltips
490  var TOOLTIP_MARGIN = 10;
491  $('acronym,.tooltip-link').each(function() {
492    var $target = $(this);
493    var $tooltip = $('<div>')
494        .addClass('tooltip-box')
495        .append($target.attr('title'))
496        .hide()
497        .appendTo('body');
498    $target.removeAttr('title');
499
500    $target.hover(function() {
501      // in
502      var targetRect = $target.offset();
503      targetRect.width = $target.width();
504      targetRect.height = $target.height();
505
506      $tooltip.css({
507        left: targetRect.left,
508        top: targetRect.top + targetRect.height + TOOLTIP_MARGIN
509      });
510      $tooltip.addClass('below');
511      $tooltip.show();
512    }, function() {
513      // out
514      $tooltip.hide();
515    });
516  });
517
518  // Set up <h2> deeplinks
519  $('h2').click(function() {
520    var id = $(this).attr('id');
521    if (id) {
522      document.location.hash = id;
523    }
524  });
525
526  //Loads the +1 button
527  var po = document.createElement('script'); po.type = 'text/javascript'; po.async = true;
528  po.src = 'https://apis.google.com/js/plusone.js';
529  var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(po, s);
530
531
532  // Revise the sidenav widths to make room for the scrollbar
533  // which avoids the visible width from changing each time the bar appears
534  var $sidenav = $("#side-nav");
535  var sidenav_width = parseInt($sidenav.innerWidth());
536
537  $("#devdoc-nav  #nav").css("width", sidenav_width - 4 + "px"); // 4px is scrollbar width
538
539
540  $(".scroll-pane").removeAttr("tabindex"); // get rid of tabindex added by jscroller
541
542  if ($(".scroll-pane").length > 1) {
543    // Check if there's a user preference for the panel heights
544    var cookieHeight = readCookie("reference_height");
545    if (cookieHeight) {
546      restoreHeight(cookieHeight);
547    }
548  }
549
550  resizeNav();
551
552  /* init the language selector based on user cookie for lang */
553  loadLangPref();
554  changeNavLang(getLangPref());
555
556  /* setup event handlers to ensure the overflow menu is visible while picking lang */
557  $("#language select")
558      .mousedown(function() {
559        $("div.morehover").addClass("hover"); })
560      .blur(function() {
561        $("div.morehover").removeClass("hover"); });
562
563  /* some global variable setup */
564  resizePackagesNav = $("#resize-packages-nav");
565  classesNav = $("#classes-nav");
566  devdocNav = $("#devdoc-nav");
567
568  var cookiePath = "";
569  if (location.href.indexOf("/reference/") != -1) {
570    cookiePath = "reference_";
571  } else if (location.href.indexOf("/guide/") != -1) {
572    cookiePath = "guide_";
573  } else if (location.href.indexOf("/tools/") != -1) {
574    cookiePath = "tools_";
575  } else if (location.href.indexOf("/training/") != -1) {
576    cookiePath = "training_";
577  } else if (location.href.indexOf("/design/") != -1) {
578    cookiePath = "design_";
579  } else if (location.href.indexOf("/distribute/") != -1) {
580    cookiePath = "distribute_";
581  }
582
583});
584// END of the onload event
585
586
587function highlightSidenav() {
588  // select current page in sidenav and header, and set up prev/next links if they exist
589  var $selNavLink = $('#nav').find('a[href="' + mPagePath + '"]');
590  var $selListItem;
591  if ($selNavLink.length) {
592
593    // Find this page's <li> in sidenav and set selected
594    $selListItem = $selNavLink.closest('li');
595    $selListItem.addClass('selected');
596
597    // Traverse up the tree and expand all parent nav-sections
598    $selNavLink.parents('li.nav-section').each(function() {
599      $(this).addClass('expanded');
600      $(this).children('ul').show();
601    });
602  }
603}
604
605
606function toggleFullscreen(enable) {
607  var delay = 20;
608  var enabled = true;
609  var stylesheet = $('link[rel="stylesheet"][class="fullscreen"]');
610  if (enable) {
611    // Currently NOT USING fullscreen; enable fullscreen
612    stylesheet.removeAttr('disabled');
613    $('#nav-swap .fullscreen').removeClass('disabled');
614    $('#devdoc-nav').css({left:''});
615    setTimeout(updateSidenavFullscreenWidth,delay); // need to wait a moment for css to switch
616    enabled = true;
617  } else {
618    // Currently USING fullscreen; disable fullscreen
619    stylesheet.attr('disabled', 'disabled');
620    $('#nav-swap .fullscreen').addClass('disabled');
621    setTimeout(updateSidenavFixedWidth,delay); // need to wait a moment for css to switch
622    enabled = false;
623  }
624  writeCookie("fullscreen", enabled, null, null);
625  setNavBarLeftPos();
626  resizeNav(delay);
627  updateSideNavPosition();
628  setTimeout(initSidenavHeightResize,delay);
629}
630
631
632function setNavBarLeftPos() {
633  navBarLeftPos = $('#body-content').offset().left;
634}
635
636
637function updateSideNavPosition() {
638  var newLeft = $(window).scrollLeft() - navBarLeftPos;
639  $('#devdoc-nav').css({left: -newLeft});
640  $('#devdoc-nav .totop').css({left: -(newLeft - parseInt($('#side-nav').css('margin-left')))});
641}
642
643
644
645
646
647
648
649
650// TODO: use $(document).ready instead
651function addLoadEvent(newfun) {
652  var current = window.onload;
653  if (typeof window.onload != 'function') {
654    window.onload = newfun;
655  } else {
656    window.onload = function() {
657      current();
658      newfun();
659    }
660  }
661}
662
663var agent = navigator['userAgent'].toLowerCase();
664// If a mobile phone, set flag and do mobile setup
665if ((agent.indexOf("mobile") != -1) ||      // android, iphone, ipod
666    (agent.indexOf("blackberry") != -1) ||
667    (agent.indexOf("webos") != -1) ||
668    (agent.indexOf("mini") != -1)) {        // opera mini browsers
669  isMobile = true;
670}
671
672
673addLoadEvent( function() {
674  $("pre:not(.no-pretty-print)").addClass("prettyprint");
675  prettyPrint();
676} );
677
678
679
680
681/* ######### RESIZE THE SIDENAV HEIGHT ########## */
682
683function resizeNav(delay) {
684  var $nav = $("#devdoc-nav");
685  var $window = $(window);
686  var navHeight;
687
688  // Get the height of entire window and the total header height.
689  // Then figure out based on scroll position whether the header is visible
690  var windowHeight = $window.height();
691  var scrollTop = $window.scrollTop();
692  var headerHeight = $('#header').outerHeight();
693  var subheaderHeight = $('#nav-x').outerHeight();
694  var headerVisible = (scrollTop < (headerHeight + subheaderHeight));
695
696  // get the height of space between nav and top of window.
697  // Could be either margin or top position, depending on whether the nav is fixed.
698  var topMargin = (parseInt($nav.css('margin-top')) || parseInt($nav.css('top'))) + 1;
699  // add 1 for the #side-nav bottom margin
700
701  // Depending on whether the header is visible, set the side nav's height.
702  if (headerVisible) {
703    // The sidenav height grows as the header goes off screen
704    navHeight = windowHeight - (headerHeight + subheaderHeight - scrollTop) - topMargin;
705  } else {
706    // Once header is off screen, the nav height is almost full window height
707    navHeight = windowHeight - topMargin;
708  }
709
710
711
712  $scrollPanes = $(".scroll-pane");
713  if ($scrollPanes.length > 1) {
714    // subtract the height of the api level widget and nav swapper from the available nav height
715    navHeight -= ($('#api-nav-header').outerHeight(true) + $('#nav-swap').outerHeight(true));
716
717    $("#swapper").css({height:navHeight + "px"});
718    if ($("#nav-tree").is(":visible")) {
719      $("#nav-tree").css({height:navHeight});
720    }
721
722    var classesHeight = navHeight - parseInt($("#resize-packages-nav").css("height")) - 10 + "px";
723    //subtract 10px to account for drag bar
724
725    // if the window becomes small enough to make the class panel height 0,
726    // then the package panel should begin to shrink
727    if (parseInt(classesHeight) <= 0) {
728      $("#resize-packages-nav").css({height:navHeight - 10}); //subtract 10px for drag bar
729      $("#packages-nav").css({height:navHeight - 10});
730    }
731
732    $("#classes-nav").css({'height':classesHeight, 'margin-top':'10px'});
733    $("#classes-nav .jspContainer").css({height:classesHeight});
734
735
736  } else {
737    $nav.height(navHeight);
738  }
739
740  if (delay) {
741    updateFromResize = true;
742    delayedReInitScrollbars(delay);
743  } else {
744    reInitScrollbars();
745  }
746
747}
748
749var updateScrollbars = false;
750var updateFromResize = false;
751
752/* Re-initialize the scrollbars to account for changed nav size.
753 * This method postpones the actual update by a 1/4 second in order to optimize the
754 * scroll performance while the header is still visible, because re-initializing the
755 * scroll panes is an intensive process.
756 */
757function delayedReInitScrollbars(delay) {
758  // If we're scheduled for an update, but have received another resize request
759  // before the scheduled resize has occured, just ignore the new request
760  // (and wait for the scheduled one).
761  if (updateScrollbars && updateFromResize) {
762    updateFromResize = false;
763    return;
764  }
765
766  // We're scheduled for an update and the update request came from this method's setTimeout
767  if (updateScrollbars && !updateFromResize) {
768    reInitScrollbars();
769    updateScrollbars = false;
770  } else {
771    updateScrollbars = true;
772    updateFromResize = false;
773    setTimeout('delayedReInitScrollbars()',delay);
774  }
775}
776
777/* Re-initialize the scrollbars to account for changed nav size. */
778function reInitScrollbars() {
779  var pane = $(".scroll-pane").each(function(){
780    var api = $(this).data('jsp');
781    if (!api) { setTimeout(reInitScrollbars,300); return;}
782    api.reinitialise( {verticalGutter:0} );
783  });
784  $(".scroll-pane").removeAttr("tabindex"); // get rid of tabindex added by jscroller
785}
786
787
788/* Resize the height of the nav panels in the reference,
789 * and save the new size to a cookie */
790function saveNavPanels() {
791  var basePath = getBaseUri(location.pathname);
792  var section = basePath.substring(1,basePath.indexOf("/",1));
793  writeCookie("height", resizePackagesNav.css("height"), section, null);
794}
795
796
797
798function restoreHeight(packageHeight) {
799    $("#resize-packages-nav").height(packageHeight);
800    $("#packages-nav").height(packageHeight);
801  //  var classesHeight = navHeight - packageHeight;
802 //   $("#classes-nav").css({height:classesHeight});
803  //  $("#classes-nav .jspContainer").css({height:classesHeight});
804}
805
806
807
808/* ######### END RESIZE THE SIDENAV HEIGHT ########## */
809
810
811
812
813
814/** Scroll the jScrollPane to make the currently selected item visible
815    This is called when the page finished loading. */
816function scrollIntoView(nav) {
817  var $nav = $("#"+nav);
818  var element = $nav.jScrollPane({/* ...settings... */});
819  var api = element.data('jsp');
820
821  if ($nav.is(':visible')) {
822    var $selected = $(".selected", $nav);
823    if ($selected.length == 0) {
824      // If no selected item found, exit
825      return;
826    }
827    // get the selected item's offset from its container nav by measuring the item's offset
828    // relative to the document then subtract the container nav's offset relative to the document
829    var selectedOffset = $selected.offset().top - $nav.offset().top;
830    if (selectedOffset > $nav.height() * .8) { // multiply nav height by .8 so we move up the item
831                                               // if it's more than 80% down the nav
832      // scroll the item up by an amount equal to 80% the container nav's height
833      api.scrollTo(0, selectedOffset - ($nav.height() * .8), false);
834    }
835  }
836}
837
838
839
840
841
842
843/* Show popup dialogs */
844function showDialog(id) {
845  $dialog = $("#"+id);
846  $dialog.prepend('<div class="box-border"><div class="top"> <div class="left"></div> <div class="right"></div></div><div class="bottom"> <div class="left"></div> <div class="right"></div> </div> </div>');
847  $dialog.wrapInner('<div/>');
848  $dialog.removeClass("hide");
849}
850
851
852
853
854
855/* #########    COOKIES!     ########## */
856
857function readCookie(cookie) {
858  var myCookie = cookie_namespace+"_"+cookie+"=";
859  if (document.cookie) {
860    var index = document.cookie.indexOf(myCookie);
861    if (index != -1) {
862      var valStart = index + myCookie.length;
863      var valEnd = document.cookie.indexOf(";", valStart);
864      if (valEnd == -1) {
865        valEnd = document.cookie.length;
866      }
867      var val = document.cookie.substring(valStart, valEnd);
868      return val;
869    }
870  }
871  return 0;
872}
873
874function writeCookie(cookie, val, section, expiration) {
875  if (val==undefined) return;
876  section = section == null ? "_" : "_"+section+"_";
877  if (expiration == null) {
878    var date = new Date();
879    date.setTime(date.getTime()+(10*365*24*60*60*1000)); // default expiration is one week
880    expiration = date.toGMTString();
881  }
882  var cookieValue = cookie_namespace + section + cookie + "=" + val
883                    + "; expires=" + expiration+"; path=/";
884  document.cookie = cookieValue;
885}
886
887/* #########     END COOKIES!     ########## */
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907/*      MISC LIBRARY FUNCTIONS     */
908
909
910
911
912
913function toggle(obj, slide) {
914  var ul = $("ul:first", obj);
915  var li = ul.parent();
916  if (li.hasClass("closed")) {
917    if (slide) {
918      ul.slideDown("fast");
919    } else {
920      ul.show();
921    }
922    li.removeClass("closed");
923    li.addClass("open");
924    $(".toggle-img", li).attr("title", "hide pages");
925  } else {
926    ul.slideUp("fast");
927    li.removeClass("open");
928    li.addClass("closed");
929    $(".toggle-img", li).attr("title", "show pages");
930  }
931}
932
933
934function buildToggleLists() {
935  $(".toggle-list").each(
936    function(i) {
937      $("div:first", this).append("<a class='toggle-img' href='#' title='show pages' onClick='toggle(this.parentNode.parentNode, true); return false;'></a>");
938      $(this).addClass("closed");
939    });
940}
941
942
943
944function hideNestedItems(list, toggle) {
945  $list = $(list);
946  // hide nested lists
947  if($list.hasClass('showing')) {
948    $("li ol", $list).hide('fast');
949    $list.removeClass('showing');
950  // show nested lists
951  } else {
952    $("li ol", $list).show('fast');
953    $list.addClass('showing');
954  }
955  $(".more,.less",$(toggle)).toggle();
956}
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985/*      REFERENCE NAV SWAP     */
986
987
988function getNavPref() {
989  var v = readCookie('reference_nav');
990  if (v != NAV_PREF_TREE) {
991    v = NAV_PREF_PANELS;
992  }
993  return v;
994}
995
996function chooseDefaultNav() {
997  nav_pref = getNavPref();
998  if (nav_pref == NAV_PREF_TREE) {
999    $("#nav-panels").toggle();
1000    $("#panel-link").toggle();
1001    $("#nav-tree").toggle();
1002    $("#tree-link").toggle();
1003  }
1004}
1005
1006function swapNav() {
1007  if (nav_pref == NAV_PREF_TREE) {
1008    nav_pref = NAV_PREF_PANELS;
1009  } else {
1010    nav_pref = NAV_PREF_TREE;
1011    init_default_navtree(toRoot);
1012  }
1013  var date = new Date();
1014  date.setTime(date.getTime()+(10*365*24*60*60*1000)); // keep this for 10 years
1015  writeCookie("nav", nav_pref, "reference", date.toGMTString());
1016
1017  $("#nav-panels").toggle();
1018  $("#panel-link").toggle();
1019  $("#nav-tree").toggle();
1020  $("#tree-link").toggle();
1021
1022  resizeNav();
1023
1024  // Gross nasty hack to make tree view show up upon first swap by setting height manually
1025  $("#nav-tree .jspContainer:visible")
1026      .css({'height':$("#nav-tree .jspContainer .jspPane").height() +'px'});
1027  // Another nasty hack to make the scrollbar appear now that we have height
1028  resizeNav();
1029
1030  if ($("#nav-tree").is(':visible')) {
1031    scrollIntoView("nav-tree");
1032  } else {
1033    scrollIntoView("packages-nav");
1034    scrollIntoView("classes-nav");
1035  }
1036}
1037
1038
1039
1040/* ############################################ */
1041/* ##########     LOCALIZATION     ############ */
1042/* ############################################ */
1043
1044function getBaseUri(uri) {
1045  var intlUrl = (uri.substring(0,6) == "/intl/");
1046  if (intlUrl) {
1047    base = uri.substring(uri.indexOf('intl/')+5,uri.length);
1048    base = base.substring(base.indexOf('/')+1, base.length);
1049      //alert("intl, returning base url: /" + base);
1050    return ("/" + base);
1051  } else {
1052      //alert("not intl, returning uri as found.");
1053    return uri;
1054  }
1055}
1056
1057function requestAppendHL(uri) {
1058//append "?hl=<lang> to an outgoing request (such as to blog)
1059  var lang = getLangPref();
1060  if (lang) {
1061    var q = 'hl=' + lang;
1062    uri += '?' + q;
1063    window.location = uri;
1064    return false;
1065  } else {
1066    return true;
1067  }
1068}
1069
1070
1071function changeNavLang(lang) {
1072  var $links = $("#devdoc-nav,#header,#nav-x,.training-nav-top,.content-footer").find("a["+lang+"-lang]");
1073  $links.each(function(i){ // for each link with a translation
1074    var $link = $(this);
1075    if (lang != "en") { // No need to worry about English, because a language change invokes new request
1076      // put the desired language from the attribute as the text
1077      $link.text($link.attr(lang+"-lang"))
1078    }
1079  });
1080}
1081
1082function changeLangPref(lang, submit) {
1083  var date = new Date();
1084  expires = date.toGMTString(date.setTime(date.getTime()+(10*365*24*60*60*1000)));
1085  // keep this for 50 years
1086  //alert("expires: " + expires)
1087  writeCookie("pref_lang", lang, null, expires);
1088
1089  //  #######  TODO:  Remove this condition once we're stable on devsite #######
1090  //  This condition is only needed if we still need to support legacy GAE server
1091  if (devsite) {
1092    // Switch language when on Devsite server
1093    if (submit) {
1094      $("#setlang").submit();
1095    }
1096  } else {
1097    // Switch language when on legacy GAE server
1098    if (submit) {
1099      window.location = getBaseUri(location.pathname);
1100    }
1101  }
1102}
1103
1104function loadLangPref() {
1105  var lang = readCookie("pref_lang");
1106  if (lang != 0) {
1107    $("#language").find("option[value='"+lang+"']").attr("selected",true);
1108  }
1109}
1110
1111function getLangPref() {
1112  var lang = $("#language").find(":selected").attr("value");
1113  if (!lang) {
1114    lang = readCookie("pref_lang");
1115  }
1116  return (lang != 0) ? lang : 'en';
1117}
1118
1119/* ##########     END LOCALIZATION     ############ */
1120
1121
1122
1123
1124
1125
1126/* Used to hide and reveal supplemental content, such as long code samples.
1127   See the companion CSS in android-developer-docs.css */
1128function toggleContent(obj) {
1129  var div = $(obj.parentNode.parentNode);
1130  var toggleMe = $(".toggle-content-toggleme",div);
1131  if (div.hasClass("closed")) { // if it's closed, open it
1132    toggleMe.slideDown();
1133    $(".toggle-content-text", obj).toggle();
1134    div.removeClass("closed").addClass("open");
1135    $(".toggle-content-img", div).attr("title", "hide").attr("src", toRoot
1136                  + "assets/images/triangle-opened.png");
1137  } else { // if it's open, close it
1138    toggleMe.slideUp('fast', function() {  // Wait until the animation is done before closing arrow
1139      $(".toggle-content-text", obj).toggle();
1140      div.removeClass("open").addClass("closed");
1141      $(".toggle-content-img", div).attr("title", "show").attr("src", toRoot
1142                  + "assets/images/triangle-closed.png");
1143    });
1144  }
1145  return false;
1146}
1147
1148
1149/* New version of expandable content */
1150function toggleExpandable(link,id) {
1151  if($(id).is(':visible')) {
1152    $(id).slideUp();
1153    $(link).removeClass('expanded');
1154  } else {
1155    $(id).slideDown();
1156    $(link).addClass('expanded');
1157  }
1158}
1159
1160function hideExpandable(ids) {
1161  $(ids).slideUp();
1162  $(ids).prev('h4').find('a.expandable').removeClass('expanded');
1163}
1164
1165
1166
1167
1168
1169/*
1170 *  Slideshow 1.0
1171 *  Used on /index.html and /develop/index.html for carousel
1172 *
1173 *  Sample usage:
1174 *  HTML -
1175 *  <div class="slideshow-container">
1176 *   <a href="" class="slideshow-prev">Prev</a>
1177 *   <a href="" class="slideshow-next">Next</a>
1178 *   <ul>
1179 *       <li class="item"><img src="images/marquee1.jpg"></li>
1180 *       <li class="item"><img src="images/marquee2.jpg"></li>
1181 *       <li class="item"><img src="images/marquee3.jpg"></li>
1182 *       <li class="item"><img src="images/marquee4.jpg"></li>
1183 *   </ul>
1184 *  </div>
1185 *
1186 *   <script type="text/javascript">
1187 *   $('.slideshow-container').dacSlideshow({
1188 *       auto: true,
1189 *       btnPrev: '.slideshow-prev',
1190 *       btnNext: '.slideshow-next'
1191 *   });
1192 *   </script>
1193 *
1194 *  Options:
1195 *  btnPrev:    optional identifier for previous button
1196 *  btnNext:    optional identifier for next button
1197 *  btnPause:   optional identifier for pause button
1198 *  auto:       whether or not to auto-proceed
1199 *  speed:      animation speed
1200 *  autoTime:   time between auto-rotation
1201 *  easing:     easing function for transition
1202 *  start:      item to select by default
1203 *  scroll:     direction to scroll in
1204 *  pagination: whether or not to include dotted pagination
1205 *
1206 */
1207
1208 (function($) {
1209 $.fn.dacSlideshow = function(o) {
1210
1211     //Options - see above
1212     o = $.extend({
1213         btnPrev:   null,
1214         btnNext:   null,
1215         btnPause:  null,
1216         auto:      true,
1217         speed:     500,
1218         autoTime:  12000,
1219         easing:    null,
1220         start:     0,
1221         scroll:    1,
1222         pagination: true
1223
1224     }, o || {});
1225
1226     //Set up a carousel for each
1227     return this.each(function() {
1228
1229         var running = false;
1230         var animCss = o.vertical ? "top" : "left";
1231         var sizeCss = o.vertical ? "height" : "width";
1232         var div = $(this);
1233         var ul = $("ul", div);
1234         var tLi = $("li", ul);
1235         var tl = tLi.size();
1236         var timer = null;
1237
1238         var li = $("li", ul);
1239         var itemLength = li.size();
1240         var curr = o.start;
1241
1242         li.css({float: o.vertical ? "none" : "left"});
1243         ul.css({margin: "0", padding: "0", position: "relative", "list-style-type": "none", "z-index": "1"});
1244         div.css({position: "relative", "z-index": "2", left: "0px"});
1245
1246         var liSize = o.vertical ? height(li) : width(li);
1247         var ulSize = liSize * itemLength;
1248         var divSize = liSize;
1249
1250         li.css({width: li.width(), height: li.height()});
1251         ul.css(sizeCss, ulSize+"px").css(animCss, -(curr*liSize));
1252
1253         div.css(sizeCss, divSize+"px");
1254
1255         //Pagination
1256         if (o.pagination) {
1257             var pagination = $("<div class='pagination'></div>");
1258             var pag_ul = $("<ul></ul>");
1259             if (tl > 1) {
1260               for (var i=0;i<tl;i++) {
1261                    var li = $("<li>"+i+"</li>");
1262                    pag_ul.append(li);
1263                    if (i==o.start) li.addClass('active');
1264                        li.click(function() {
1265                        go(parseInt($(this).text()));
1266                    })
1267                }
1268                pagination.append(pag_ul);
1269                div.append(pagination);
1270             }
1271         }
1272
1273         //Previous button
1274         if(o.btnPrev)
1275             $(o.btnPrev).click(function(e) {
1276                 e.preventDefault();
1277                 return go(curr-o.scroll);
1278             });
1279
1280         //Next button
1281         if(o.btnNext)
1282             $(o.btnNext).click(function(e) {
1283                 e.preventDefault();
1284                 return go(curr+o.scroll);
1285             });
1286
1287         //Pause button
1288         if(o.btnPause)
1289             $(o.btnPause).click(function(e) {
1290                 e.preventDefault();
1291                 if ($(this).hasClass('paused')) {
1292                     startRotateTimer();
1293                 } else {
1294                     pauseRotateTimer();
1295                 }
1296             });
1297
1298         //Auto rotation
1299         if(o.auto) startRotateTimer();
1300
1301         function startRotateTimer() {
1302             clearInterval(timer);
1303             timer = setInterval(function() {
1304                  if (curr == tl-1) {
1305                    go(0);
1306                  } else {
1307                    go(curr+o.scroll);
1308                  }
1309              }, o.autoTime);
1310             $(o.btnPause).removeClass('paused');
1311         }
1312
1313         function pauseRotateTimer() {
1314             clearInterval(timer);
1315             $(o.btnPause).addClass('paused');
1316         }
1317
1318         //Go to an item
1319         function go(to) {
1320             if(!running) {
1321
1322                 if(to<0) {
1323                    to = itemLength-1;
1324                 } else if (to>itemLength-1) {
1325                    to = 0;
1326                 }
1327                 curr = to;
1328
1329                 running = true;
1330
1331                 ul.animate(
1332                     animCss == "left" ? { left: -(curr*liSize) } : { top: -(curr*liSize) } , o.speed, o.easing,
1333                     function() {
1334                         running = false;
1335                     }
1336                 );
1337
1338                 $(o.btnPrev + "," + o.btnNext).removeClass("disabled");
1339                 $( (curr-o.scroll<0 && o.btnPrev)
1340                     ||
1341                    (curr+o.scroll > itemLength && o.btnNext)
1342                     ||
1343                    []
1344                  ).addClass("disabled");
1345
1346
1347                 var nav_items = $('li', pagination);
1348                 nav_items.removeClass('active');
1349                 nav_items.eq(to).addClass('active');
1350
1351
1352             }
1353             if(o.auto) startRotateTimer();
1354             return false;
1355         };
1356     });
1357 };
1358
1359 function css(el, prop) {
1360     return parseInt($.css(el[0], prop)) || 0;
1361 };
1362 function width(el) {
1363     return  el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight');
1364 };
1365 function height(el) {
1366     return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom');
1367 };
1368
1369 })(jQuery);
1370
1371
1372/*
1373 *  dacSlideshow 1.0
1374 *  Used on develop/index.html for side-sliding tabs
1375 *
1376 *  Sample usage:
1377 *  HTML -
1378 *  <div class="slideshow-container">
1379 *   <a href="" class="slideshow-prev">Prev</a>
1380 *   <a href="" class="slideshow-next">Next</a>
1381 *   <ul>
1382 *       <li class="item"><img src="images/marquee1.jpg"></li>
1383 *       <li class="item"><img src="images/marquee2.jpg"></li>
1384 *       <li class="item"><img src="images/marquee3.jpg"></li>
1385 *       <li class="item"><img src="images/marquee4.jpg"></li>
1386 *   </ul>
1387 *  </div>
1388 *
1389 *   <script type="text/javascript">
1390 *   $('.slideshow-container').dacSlideshow({
1391 *       auto: true,
1392 *       btnPrev: '.slideshow-prev',
1393 *       btnNext: '.slideshow-next'
1394 *   });
1395 *   </script>
1396 *
1397 *  Options:
1398 *  btnPrev:    optional identifier for previous button
1399 *  btnNext:    optional identifier for next button
1400 *  auto:       whether or not to auto-proceed
1401 *  speed:      animation speed
1402 *  autoTime:   time between auto-rotation
1403 *  easing:     easing function for transition
1404 *  start:      item to select by default
1405 *  scroll:     direction to scroll in
1406 *  pagination: whether or not to include dotted pagination
1407 *
1408 */
1409 (function($) {
1410 $.fn.dacTabbedList = function(o) {
1411
1412     //Options - see above
1413     o = $.extend({
1414         speed : 250,
1415         easing: null,
1416         nav_id: null,
1417         frame_id: null
1418     }, o || {});
1419
1420     //Set up a carousel for each
1421     return this.each(function() {
1422
1423         var curr = 0;
1424         var running = false;
1425         var animCss = "margin-left";
1426         var sizeCss = "width";
1427         var div = $(this);
1428
1429         var nav = $(o.nav_id, div);
1430         var nav_li = $("li", nav);
1431         var nav_size = nav_li.size();
1432         var frame = div.find(o.frame_id);
1433         var content_width = $(frame).find('ul').width();
1434         //Buttons
1435         $(nav_li).click(function(e) {
1436           go($(nav_li).index($(this)));
1437         })
1438
1439         //Go to an item
1440         function go(to) {
1441             if(!running) {
1442                 curr = to;
1443                 running = true;
1444
1445                 frame.animate({ 'margin-left' : -(curr*content_width) }, o.speed, o.easing,
1446                     function() {
1447                         running = false;
1448                     }
1449                 );
1450
1451
1452                 nav_li.removeClass('active');
1453                 nav_li.eq(to).addClass('active');
1454
1455
1456             }
1457             return false;
1458         };
1459     });
1460 };
1461
1462 function css(el, prop) {
1463     return parseInt($.css(el[0], prop)) || 0;
1464 };
1465 function width(el) {
1466     return  el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight');
1467 };
1468 function height(el) {
1469     return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom');
1470 };
1471
1472 })(jQuery);
1473
1474
1475
1476
1477
1478/* ######################################################## */
1479/* ################  SEARCH SUGGESTIONS  ################## */
1480/* ######################################################## */
1481
1482
1483
1484var gSelectedIndex = -1;  // the index position of currently highlighted suggestion
1485var gSelectedColumn = -1;  // which column of suggestion lists is currently focused
1486
1487var gMatches = new Array();
1488var gLastText = "";
1489var gInitialized = false;
1490var ROW_COUNT_FRAMEWORK = 20;       // max number of results in list
1491var gListLength = 0;
1492
1493
1494var gGoogleMatches = new Array();
1495var ROW_COUNT_GOOGLE = 15;          // max number of results in list
1496var gGoogleListLength = 0;
1497
1498var gDocsMatches = new Array();
1499var ROW_COUNT_DOCS = 100;          // max number of results in list
1500var gDocsListLength = 0;
1501
1502function onSuggestionClick(link) {
1503  // When user clicks a suggested document, track it
1504  _gaq.push(['_trackEvent', 'Suggestion Click', 'clicked: ' + $(link).text(),
1505            'from: ' + $("#search_autocomplete").val()]);
1506}
1507
1508function set_item_selected($li, selected)
1509{
1510    if (selected) {
1511        $li.attr('class','jd-autocomplete jd-selected');
1512    } else {
1513        $li.attr('class','jd-autocomplete');
1514    }
1515}
1516
1517function set_item_values(toroot, $li, match)
1518{
1519    var $link = $('a',$li);
1520    $link.html(match.__hilabel || match.label);
1521    $link.attr('href',toroot + match.link);
1522}
1523
1524function new_suggestion($list) {
1525    var $li = $("<li class='jd-autocomplete'></li>");
1526    $list.append($li);
1527
1528    $li.mousedown(function() {
1529        window.location = this.firstChild.getAttribute("href");
1530    });
1531    $li.mouseover(function() {
1532        $('.search_filtered_wrapper li').removeClass('jd-selected');
1533        $(this).addClass('jd-selected');
1534        gSelectedColumn = $(".search_filtered:visible").index($(this).closest('.search_filtered'));
1535        gSelectedIndex = $("li", $(".search_filtered:visible")[gSelectedColumn]).index(this);
1536    });
1537    $li.append("<a onclick='onSuggestionClick(this)'></a>");
1538    $li.attr('class','show-item');
1539    return $li;
1540}
1541
1542function sync_selection_table(toroot)
1543{
1544    var $li; //list item jquery object
1545    var i; //list item iterator
1546
1547    // if there are NO results at all, hide all columns
1548    if (!(gMatches.length > 0) && !(gGoogleMatches.length > 0) && !(gDocsMatches.length > 0)) {
1549        $('.suggest-card').hide(300);
1550        return;
1551    }
1552
1553    // if there are api results
1554    if ((gMatches.length > 0) || (gGoogleMatches.length > 0)) {
1555      // reveal suggestion list
1556      $('.suggest-card.dummy').show();
1557      $('.suggest-card.reference').show();
1558      var listIndex = 0; // list index position
1559
1560      // reset the lists
1561      $(".search_filtered_wrapper.reference li").remove();
1562
1563      // ########### ANDROID RESULTS #############
1564      if (gMatches.length > 0) {
1565
1566          // determine android results to show
1567          gListLength = gMatches.length < ROW_COUNT_FRAMEWORK ?
1568                        gMatches.length : ROW_COUNT_FRAMEWORK;
1569          for (i=0; i<gListLength; i++) {
1570              var $li = new_suggestion($(".suggest-card.reference ul"));
1571              set_item_values(toroot, $li, gMatches[i]);
1572              set_item_selected($li, i == gSelectedIndex);
1573          }
1574      }
1575
1576      // ########### GOOGLE RESULTS #############
1577      if (gGoogleMatches.length > 0) {
1578          // show header for list
1579          $(".suggest-card.reference ul").append("<li class='header'>in Google Services:</li>");
1580
1581          // determine google results to show
1582          gGoogleListLength = gGoogleMatches.length < ROW_COUNT_GOOGLE ? gGoogleMatches.length : ROW_COUNT_GOOGLE;
1583          for (i=0; i<gGoogleListLength; i++) {
1584              var $li = new_suggestion($(".suggest-card.reference ul"));
1585              set_item_values(toroot, $li, gGoogleMatches[i]);
1586              set_item_selected($li, i == gSelectedIndex);
1587          }
1588      }
1589    } else {
1590      $('.suggest-card.reference').hide();
1591      $('.suggest-card.dummy').hide();
1592    }
1593
1594    // ########### JD DOC RESULTS #############
1595    if (gDocsMatches.length > 0) {
1596        // reset the lists
1597        $(".search_filtered_wrapper.docs li").remove();
1598
1599        // determine google results to show
1600        gDocsListLength = gDocsMatches.length < ROW_COUNT_DOCS ? gDocsMatches.length : ROW_COUNT_DOCS;
1601        for (i=0; i<gDocsListLength; i++) {
1602            var sugg = gDocsMatches[i];
1603            var $li;
1604            if (sugg.type == "design") {
1605                $li = new_suggestion($(".suggest-card.design ul"));
1606            } else
1607            if (sugg.type == "distribute") {
1608                $li = new_suggestion($(".suggest-card.distribute ul"));
1609            } else
1610            if (sugg.type == "training") {
1611                $li = new_suggestion($(".suggest-card.develop .child-card.training"));
1612            } else
1613            if (sugg.type == "guide"||"google") {
1614                $li = new_suggestion($(".suggest-card.develop .child-card.guides"));
1615            } else {
1616              continue;
1617            }
1618
1619            set_item_values(toroot, $li, sugg);
1620            set_item_selected($li, i == gSelectedIndex);
1621        }
1622
1623        // add heading and show or hide card
1624        if ($(".suggest-card.design li").length > 0) {
1625          $(".suggest-card.design ul").prepend("<li class='header'>Design:</li>");
1626          $(".suggest-card.design").show(300);
1627        } else {
1628          $('.suggest-card.design').hide(300);
1629        }
1630        if ($(".suggest-card.distribute li").length > 0) {
1631          $(".suggest-card.distribute ul").prepend("<li class='header'>Distribute:</li>");
1632          $(".suggest-card.distribute").show(300);
1633        } else {
1634          $('.suggest-card.distribute').hide(300);
1635        }
1636        if ($(".child-card.guides li").length > 0) {
1637          $(".child-card.guides").prepend("<li class='header'>Guides:</li>");
1638          $(".child-card.guides li").appendTo(".suggest-card.develop ul");
1639        }
1640        if ($(".child-card.training li").length > 0) {
1641          $(".child-card.training").prepend("<li class='header'>Training:</li>");
1642          $(".child-card.training li").appendTo(".suggest-card.develop ul");
1643        }
1644
1645        if ($(".suggest-card.develop li").length > 0) {
1646          $(".suggest-card.develop").show(300);
1647        } else {
1648          $('.suggest-card.develop').hide(300);
1649        }
1650
1651    } else {
1652      $('.search_filtered_wrapper.docs .suggest-card:not(.dummy)').hide(300);
1653    }
1654}
1655
1656/** Called by the search input's onkeydown and onkeyup events.
1657  * Handles navigation with keyboard arrows, Enter key to invoke search,
1658  * otherwise invokes search suggestions on key-up event.
1659  * @param e       The JS event
1660  * @param kd      True if the event is key-down
1661  * @param toroot  A string for the site's root path
1662  * @returns       True if the event should bubble up
1663  */
1664function search_changed(e, kd, toroot)
1665{
1666    var search = document.getElementById("search_autocomplete");
1667    var text = search.value.replace(/(^ +)|( +$)/g, '');
1668    // get the ul hosting the currently selected item
1669    gSelectedColumn = gSelectedColumn >= 0 ? gSelectedColumn :  0;
1670    var $columns = $(".search_filtered_wrapper").find(".search_filtered:visible");
1671    var $selectedUl = $columns[gSelectedColumn];
1672
1673    // show/hide the close button
1674    if (text != '') {
1675        $(".search .close").removeClass("hide");
1676    } else {
1677        $(".search .close").addClass("hide");
1678    }
1679    // 27 = esc
1680    if (e.keyCode == 27) {
1681        // close all search results
1682        if (kd) $('.search .close').trigger('click');
1683        return true;
1684    }
1685    // 13 = enter
1686    else if (e.keyCode == 13) {
1687        if (gSelectedIndex < 0) {
1688            $('.suggest-card').hide();
1689            if ($("#searchResults").is(":hidden") && (search.value != "")) {
1690              // if results aren't showing (and text not empty), return true to allow search to execute
1691              return true;
1692            } else {
1693              // otherwise, results are already showing, so allow ajax to auto refresh the results
1694              // and ignore this Enter press to avoid the reload.
1695              return false;
1696            }
1697        } else if (kd && gSelectedIndex >= 0) {
1698            // click the link corresponding to selected item
1699            $("a",$("li",$selectedUl)[gSelectedIndex]).get()[0].click();
1700            return false;
1701        }
1702    }
1703    // Stop here if Google results are showing
1704    else if ($("#searchResults").is(":visible")) {
1705        return true;
1706    }
1707    // 38 UP ARROW
1708    else if (kd && (e.keyCode == 38)) {
1709        // if the next item is a header, skip it
1710        if ($($("li", $selectedUl)[gSelectedIndex-1]).hasClass("header")) {
1711            gSelectedIndex--;
1712        }
1713        if (gSelectedIndex >= 0) {
1714            $('li', $selectedUl).removeClass('jd-selected');
1715            gSelectedIndex--;
1716            $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
1717            // If user reaches top, reset selected column
1718            if (gSelectedIndex < 0) {
1719              gSelectedColumn = -1;
1720            }
1721        }
1722        return false;
1723    }
1724    // 40 DOWN ARROW
1725    else if (kd && (e.keyCode == 40)) {
1726        // if the next item is a header, skip it
1727        if ($($("li", $selectedUl)[gSelectedIndex+1]).hasClass("header")) {
1728            gSelectedIndex++;
1729        }
1730        if ((gSelectedIndex < $("li", $selectedUl).length-1) ||
1731                        ($($("li", $selectedUl)[gSelectedIndex+1]).hasClass("header"))) {
1732            $('li', $selectedUl).removeClass('jd-selected');
1733            gSelectedIndex++;
1734            $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
1735        }
1736        return false;
1737    }
1738    // Consider left/right arrow navigation
1739    // NOTE: Order of suggest columns are reverse order (index position 0 is on right)
1740    else if (kd && $columns.length > 1 && gSelectedColumn >= 0) {
1741      // 37 LEFT ARROW
1742      // go left only if current column is not left-most column (last column)
1743      if (e.keyCode == 37 && gSelectedColumn < $columns.length - 1) {
1744        $('li', $selectedUl).removeClass('jd-selected');
1745        gSelectedColumn++;
1746        $selectedUl = $columns[gSelectedColumn];
1747        // keep or reset the selected item to last item as appropriate
1748        gSelectedIndex = gSelectedIndex >
1749                $("li", $selectedUl).length-1 ?
1750                $("li", $selectedUl).length-1 : gSelectedIndex;
1751        // if the corresponding item is a header, move down
1752        if ($($("li", $selectedUl)[gSelectedIndex]).hasClass("header")) {
1753          gSelectedIndex++;
1754        }
1755        // set item selected
1756        $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
1757        return false;
1758      }
1759      // 39 RIGHT ARROW
1760      // go right only if current column is not the right-most column (first column)
1761      else if (e.keyCode == 39 && gSelectedColumn > 0) {
1762        $('li', $selectedUl).removeClass('jd-selected');
1763        gSelectedColumn--;
1764        $selectedUl = $columns[gSelectedColumn];
1765        // keep or reset the selected item to last item as appropriate
1766        gSelectedIndex = gSelectedIndex >
1767                $("li", $selectedUl).length-1 ?
1768                $("li", $selectedUl).length-1 : gSelectedIndex;
1769        // if the corresponding item is a header, move down
1770        if ($($("li", $selectedUl)[gSelectedIndex]).hasClass("header")) {
1771          gSelectedIndex++;
1772        }
1773        // set item selected
1774        $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
1775        return false;
1776      }
1777    }
1778
1779    // if key-up event and not arrow down/up,
1780    // read the search query and add suggestsions to gMatches
1781    else if (!kd && (e.keyCode != 40)
1782                 && (e.keyCode != 38)
1783                 && (e.keyCode != 37)
1784                 && (e.keyCode != 39)) {
1785        gSelectedIndex = -1;
1786        gMatches = new Array();
1787        matchedCount = 0;
1788        gGoogleMatches = new Array();
1789        matchedCountGoogle = 0;
1790        gDocsMatches = new Array();
1791        matchedCountDocs = 0;
1792
1793        // Search for Android matches
1794        for (var i=0; i<DATA.length; i++) {
1795            var s = DATA[i];
1796            if (text.length != 0 &&
1797                  s.label.toLowerCase().indexOf(text.toLowerCase()) != -1) {
1798                gMatches[matchedCount] = s;
1799                matchedCount++;
1800            }
1801        }
1802        rank_autocomplete_api_results(text, gMatches);
1803        for (var i=0; i<gMatches.length; i++) {
1804            var s = gMatches[i];
1805        }
1806
1807
1808        // Search for Google matches
1809        for (var i=0; i<GOOGLE_DATA.length; i++) {
1810            var s = GOOGLE_DATA[i];
1811            if (text.length != 0 &&
1812                  s.label.toLowerCase().indexOf(text.toLowerCase()) != -1) {
1813                gGoogleMatches[matchedCountGoogle] = s;
1814                matchedCountGoogle++;
1815            }
1816        }
1817        rank_autocomplete_api_results(text, gGoogleMatches);
1818        for (var i=0; i<gGoogleMatches.length; i++) {
1819            var s = gGoogleMatches[i];
1820        }
1821
1822        highlight_autocomplete_result_labels(text);
1823
1824
1825
1826        // Search for JD docs
1827        if (text.length >= 3) {
1828          for (var i=0; i<JD_DATA.length; i++) {
1829            // Regex to match only the beginning of a word
1830            var textRegex = new RegExp("\\b" + text.toLowerCase(), "g");
1831            // current search comparison, with counters for tag and title,
1832            // used later to improve ranking
1833            var s = JD_DATA[i];
1834            s.matched_tag = 0;
1835            s.matched_title = 0;
1836            var matched = false;
1837
1838            // Check if query matches any tags; work backwards toward 1 to assist ranking
1839            for (var j = s.tags.length - 1; j >= 0; j--) {
1840              // it matches a tag
1841              if (s.tags[j].toLowerCase().match(textRegex)) {
1842                matched = true;
1843                s.matched_tag = j + 1; // add 1 to index position
1844              }
1845            }
1846            // Don't consider doc title for lessons (only for class landing pages)
1847            // ...it is not a training lesson (or is but has matched a tag)
1848            if (!(s.type == "training" && s.link.indexOf("index.html") == -1) || matched) {
1849              // it matches the doc title
1850              if (s.label.toLowerCase().match(textRegex)) {
1851                matched = true;
1852                s.matched_title = 1;
1853              }
1854            }
1855            if (matched) {
1856              gDocsMatches[matchedCountDocs] = s;
1857              matchedCountDocs++;
1858            }
1859          }
1860          rank_autocomplete_doc_results(text, gDocsMatches);
1861        }
1862
1863        // draw the suggestions
1864        sync_selection_table(toroot);
1865        return true; // allow the event to bubble up to the search api
1866    }
1867}
1868
1869/* Order the jd doc result list based on match quality */
1870function rank_autocomplete_doc_results(query, matches) {
1871    query = query || '';
1872    if (!matches || !matches.length)
1873      return;
1874
1875    var _resultScoreFn = function(match) {
1876        var score = 1.0;
1877
1878        // if the query matched a tag
1879        if (match.matched_tag > 0) {
1880          // multiply score by factor relative to position in tags list (max of 3)
1881          score *= 3 / match.matched_tag;
1882
1883          // if it also matched the title
1884          if (match.matched_title > 0) {
1885            score *= 2;
1886          }
1887        } else if (match.matched_title > 0) {
1888          score *= 3;
1889        }
1890
1891        return score;
1892    };
1893
1894    for (var i=0; i<matches.length; i++) {
1895        matches[i].__resultScore = _resultScoreFn(matches[i]);
1896    }
1897
1898    matches.sort(function(a,b){
1899        var n = b.__resultScore - a.__resultScore;
1900        if (n == 0) // lexicographical sort if scores are the same
1901            n = (a.label < b.label) ? -1 : 1;
1902        return n;
1903    });
1904}
1905
1906/* Order the result list based on match quality */
1907function rank_autocomplete_api_results(query, matches) {
1908    query = query || '';
1909    if (!matches || !matches.length)
1910      return;
1911
1912    // helper function that gets the last occurence index of the given regex
1913    // in the given string, or -1 if not found
1914    var _lastSearch = function(s, re) {
1915      if (s == '')
1916        return -1;
1917      var l = -1;
1918      var tmp;
1919      while ((tmp = s.search(re)) >= 0) {
1920        if (l < 0) l = 0;
1921        l += tmp;
1922        s = s.substr(tmp + 1);
1923      }
1924      return l;
1925    };
1926
1927    // helper function that counts the occurrences of a given character in
1928    // a given string
1929    var _countChar = function(s, c) {
1930      var n = 0;
1931      for (var i=0; i<s.length; i++)
1932        if (s.charAt(i) == c) ++n;
1933      return n;
1934    };
1935
1936    var queryLower = query.toLowerCase();
1937    var queryAlnum = (queryLower.match(/\w+/) || [''])[0];
1938    var partPrefixAlnumRE = new RegExp('\\b' + queryAlnum);
1939    var partExactAlnumRE = new RegExp('\\b' + queryAlnum + '\\b');
1940
1941    var _resultScoreFn = function(result) {
1942        // scores are calculated based on exact and prefix matches,
1943        // and then number of path separators (dots) from the last
1944        // match (i.e. favoring classes and deep package names)
1945        var score = 1.0;
1946        var labelLower = result.label.toLowerCase();
1947        var t;
1948        t = _lastSearch(labelLower, partExactAlnumRE);
1949        if (t >= 0) {
1950            // exact part match
1951            var partsAfter = _countChar(labelLower.substr(t + 1), '.');
1952            score *= 200 / (partsAfter + 1);
1953        } else {
1954            t = _lastSearch(labelLower, partPrefixAlnumRE);
1955            if (t >= 0) {
1956                // part prefix match
1957                var partsAfter = _countChar(labelLower.substr(t + 1), '.');
1958                score *= 20 / (partsAfter + 1);
1959            }
1960        }
1961
1962        return score;
1963    };
1964
1965    for (var i=0; i<matches.length; i++) {
1966        // if the API is deprecated, default score is 0; otherwise, perform scoring
1967        if (matches[i].deprecated == "true") {
1968          matches[i].__resultScore = 0;
1969        } else {
1970          matches[i].__resultScore = _resultScoreFn(matches[i]);
1971        }
1972    }
1973
1974    matches.sort(function(a,b){
1975        var n = b.__resultScore - a.__resultScore;
1976        if (n == 0) // lexicographical sort if scores are the same
1977            n = (a.label < b.label) ? -1 : 1;
1978        return n;
1979    });
1980}
1981
1982/* Add emphasis to part of string that matches query */
1983function highlight_autocomplete_result_labels(query) {
1984    query = query || '';
1985    if ((!gMatches || !gMatches.length) && (!gGoogleMatches || !gGoogleMatches.length))
1986      return;
1987
1988    var queryLower = query.toLowerCase();
1989    var queryAlnumDot = (queryLower.match(/[\w\.]+/) || [''])[0];
1990    var queryRE = new RegExp(
1991        '(' + queryAlnumDot.replace(/\./g, '\\.') + ')', 'ig');
1992    for (var i=0; i<gMatches.length; i++) {
1993        gMatches[i].__hilabel = gMatches[i].label.replace(
1994            queryRE, '<b>$1</b>');
1995    }
1996    for (var i=0; i<gGoogleMatches.length; i++) {
1997        gGoogleMatches[i].__hilabel = gGoogleMatches[i].label.replace(
1998            queryRE, '<b>$1</b>');
1999    }
2000}
2001
2002function search_focus_changed(obj, focused)
2003{
2004    if (!focused) {
2005        if(obj.value == ""){
2006          $(".search .close").addClass("hide");
2007        }
2008        $(".suggest-card").hide();
2009    }
2010}
2011
2012function submit_search() {
2013  var query = document.getElementById('search_autocomplete').value;
2014  location.hash = 'q=' + query;
2015  loadSearchResults();
2016  $("#searchResults").slideDown('slow');
2017  return false;
2018}
2019
2020
2021function hideResults() {
2022  $("#searchResults").slideUp();
2023  $(".search .close").addClass("hide");
2024  location.hash = '';
2025
2026  $("#search_autocomplete").val("").blur();
2027
2028  // reset the ajax search callback to nothing, so results don't appear unless ENTER
2029  searchControl.setSearchStartingCallback(this, function(control, searcher, query) {});
2030
2031  // forcefully regain key-up event control (previously jacked by search api)
2032  $("#search_autocomplete").keyup(function(event) {
2033    return search_changed(event, false, toRoot);
2034  });
2035
2036  return false;
2037}
2038
2039
2040
2041/* ########################################################## */
2042/* ################  CUSTOM SEARCH ENGINE  ################## */
2043/* ########################################################## */
2044
2045var searchControl;
2046google.load('search', '1', {"callback" : function() {
2047            searchControl = new google.search.SearchControl();
2048          } });
2049
2050function loadSearchResults() {
2051  document.getElementById("search_autocomplete").style.color = "#000";
2052
2053  searchControl = new google.search.SearchControl();
2054
2055  // use our existing search form and use tabs when multiple searchers are used
2056  drawOptions = new google.search.DrawOptions();
2057  drawOptions.setDrawMode(google.search.SearchControl.DRAW_MODE_TABBED);
2058  drawOptions.setInput(document.getElementById("search_autocomplete"));
2059
2060  // configure search result options
2061  searchOptions = new google.search.SearcherOptions();
2062  searchOptions.setExpandMode(GSearchControl.EXPAND_MODE_OPEN);
2063
2064  // configure each of the searchers, for each tab
2065  devSiteSearcher = new google.search.WebSearch();
2066  devSiteSearcher.setUserDefinedLabel("All");
2067  devSiteSearcher.setSiteRestriction("001482626316274216503:zu90b7s047u");
2068
2069  designSearcher = new google.search.WebSearch();
2070  designSearcher.setUserDefinedLabel("Design");
2071  designSearcher.setSiteRestriction("http://developer.android.com/design/");
2072
2073  trainingSearcher = new google.search.WebSearch();
2074  trainingSearcher.setUserDefinedLabel("Training");
2075  trainingSearcher.setSiteRestriction("http://developer.android.com/training/");
2076
2077  guidesSearcher = new google.search.WebSearch();
2078  guidesSearcher.setUserDefinedLabel("Guides");
2079  guidesSearcher.setSiteRestriction("http://developer.android.com/guide/");
2080
2081  referenceSearcher = new google.search.WebSearch();
2082  referenceSearcher.setUserDefinedLabel("Reference");
2083  referenceSearcher.setSiteRestriction("http://developer.android.com/reference/");
2084
2085  googleSearcher = new google.search.WebSearch();
2086  googleSearcher.setUserDefinedLabel("Google Services");
2087  googleSearcher.setSiteRestriction("http://developer.android.com/google/");
2088
2089  blogSearcher = new google.search.WebSearch();
2090  blogSearcher.setUserDefinedLabel("Blog");
2091  blogSearcher.setSiteRestriction("http://android-developers.blogspot.com");
2092
2093  // add each searcher to the search control
2094  searchControl.addSearcher(devSiteSearcher, searchOptions);
2095  searchControl.addSearcher(designSearcher, searchOptions);
2096  searchControl.addSearcher(trainingSearcher, searchOptions);
2097  searchControl.addSearcher(guidesSearcher, searchOptions);
2098  searchControl.addSearcher(referenceSearcher, searchOptions);
2099  searchControl.addSearcher(googleSearcher, searchOptions);
2100  searchControl.addSearcher(blogSearcher, searchOptions);
2101
2102  // configure result options
2103  searchControl.setResultSetSize(google.search.Search.LARGE_RESULTSET);
2104  searchControl.setLinkTarget(google.search.Search.LINK_TARGET_SELF);
2105  searchControl.setTimeoutInterval(google.search.SearchControl.TIMEOUT_SHORT);
2106  searchControl.setNoResultsString(google.search.SearchControl.NO_RESULTS_DEFAULT_STRING);
2107
2108  // upon ajax search, refresh the url and search title
2109  searchControl.setSearchStartingCallback(this, function(control, searcher, query) {
2110    updateResultTitle(query);
2111    var query = document.getElementById('search_autocomplete').value;
2112    location.hash = 'q=' + query;
2113  });
2114
2115  // once search results load, set up click listeners
2116  searchControl.setSearchCompleteCallback(this, function(control, searcher, query) {
2117    addResultClickListeners();
2118  });
2119
2120  // draw the search results box
2121  searchControl.draw(document.getElementById("leftSearchControl"), drawOptions);
2122
2123  // get query and execute the search
2124  searchControl.execute(decodeURI(getQuery(location.hash)));
2125
2126  document.getElementById("search_autocomplete").focus();
2127  addTabListeners();
2128}
2129// End of loadSearchResults
2130
2131
2132google.setOnLoadCallback(function(){
2133  if (location.hash.indexOf("q=") == -1) {
2134    // if there's no query in the url, don't search and make sure results are hidden
2135    $('#searchResults').hide();
2136    return;
2137  } else {
2138    // first time loading search results for this page
2139    $('#searchResults').slideDown('slow');
2140    $(".search .close").removeClass("hide");
2141    loadSearchResults();
2142  }
2143}, true);
2144
2145// when an event on the browser history occurs (back, forward, load) requery hash and do search
2146$(window).hashchange( function(){
2147  // Exit if the hash isn't a search query or there's an error in the query
2148  if ((location.hash.indexOf("q=") == -1) || (query == "undefined")) {
2149    // If the results pane is open, close it.
2150    if (!$("#searchResults").is(":hidden")) {
2151      hideResults();
2152    }
2153    return;
2154  }
2155
2156  // Otherwise, we have a search to do
2157  var query = decodeURI(getQuery(location.hash));
2158  searchControl.execute(query);
2159  $('#searchResults').slideDown('slow');
2160  $("#search_autocomplete").focus();
2161  $(".search .close").removeClass("hide");
2162
2163  updateResultTitle(query);
2164});
2165
2166function updateResultTitle(query) {
2167  $("#searchTitle").html("Results for <em>" + escapeHTML(query) + "</em>");
2168}
2169
2170// forcefully regain key-up event control (previously jacked by search api)
2171$("#search_autocomplete").keyup(function(event) {
2172  return search_changed(event, false, toRoot);
2173});
2174
2175// add event listeners to each tab so we can track the browser history
2176function addTabListeners() {
2177  var tabHeaders = $(".gsc-tabHeader");
2178  for (var i = 0; i < tabHeaders.length; i++) {
2179    $(tabHeaders[i]).attr("id",i).click(function() {
2180    /*
2181      // make a copy of the page numbers for the search left pane
2182      setTimeout(function() {
2183        // remove any residual page numbers
2184        $('#searchResults .gsc-tabsArea .gsc-cursor-box.gs-bidi-start-align').remove();
2185        // move the page numbers to the left position; make a clone,
2186        // because the element is drawn to the DOM only once
2187        // and because we're going to remove it (previous line),
2188        // we need it to be available to move again as the user navigates
2189        $('#searchResults .gsc-webResult .gsc-cursor-box.gs-bidi-start-align:visible')
2190                        .clone().appendTo('#searchResults .gsc-tabsArea');
2191        }, 200);
2192      */
2193    });
2194  }
2195  setTimeout(function(){$(tabHeaders[0]).click()},200);
2196}
2197
2198// add analytics tracking events to each result link
2199function addResultClickListeners() {
2200  $("#searchResults a.gs-title").each(function(index, link) {
2201    // When user clicks enter for Google search results, track it
2202    $(link).click(function() {
2203      _gaq.push(['_trackEvent', 'Google Click', 'clicked: ' + $(this).text(),
2204                'from: ' + $("#search_autocomplete").val()]);
2205    });
2206  });
2207}
2208
2209
2210function getQuery(hash) {
2211  var queryParts = hash.split('=');
2212  return queryParts[1];
2213}
2214
2215/* returns the given string with all HTML brackets converted to entities
2216    TODO: move this to the site's JS library */
2217function escapeHTML(string) {
2218  return string.replace(/</g,"&lt;")
2219                .replace(/>/g,"&gt;");
2220}
2221
2222
2223
2224
2225
2226
2227
2228/* ######################################################## */
2229/* #################  JAVADOC REFERENCE ################### */
2230/* ######################################################## */
2231
2232/* Initialize some droiddoc stuff, but only if we're in the reference */
2233if (location.pathname.indexOf("/reference") == 0) {
2234  if(!(location.pathname.indexOf("/reference-gms/packages.html") == 0)
2235    && !(location.pathname.indexOf("/reference-gcm/packages.html") == 0)
2236    && !(location.pathname.indexOf("/reference/com/google") == 0)) {
2237    $(document).ready(function() {
2238      // init available apis based on user pref
2239      changeApiLevel();
2240      initSidenavHeightResize()
2241      });
2242  }
2243}
2244
2245var API_LEVEL_COOKIE = "api_level";
2246var minLevel = 1;
2247var maxLevel = 1;
2248
2249/******* SIDENAV DIMENSIONS ************/
2250
2251  function initSidenavHeightResize() {
2252    // Change the drag bar size to nicely fit the scrollbar positions
2253    var $dragBar = $(".ui-resizable-s");
2254    $dragBar.css({'width': $dragBar.parent().width() - 5 + "px"});
2255
2256    $( "#resize-packages-nav" ).resizable({
2257      containment: "#nav-panels",
2258      handles: "s",
2259      alsoResize: "#packages-nav",
2260      resize: function(event, ui) { resizeNav(); }, /* resize the nav while dragging */
2261      stop: function(event, ui) { saveNavPanels(); } /* once stopped, save the sizes to cookie  */
2262      });
2263
2264  }
2265
2266function updateSidenavFixedWidth() {
2267  if (!navBarIsFixed) return;
2268  $('#devdoc-nav').css({
2269    'width' : $('#side-nav').css('width'),
2270    'margin' : $('#side-nav').css('margin')
2271  });
2272  $('#devdoc-nav a.totop').css({'display':'block','width':$("#nav").innerWidth()+'px'});
2273
2274  initSidenavHeightResize();
2275}
2276
2277function updateSidenavFullscreenWidth() {
2278  if (!navBarIsFixed) return;
2279  $('#devdoc-nav').css({
2280    'width' : $('#side-nav').css('width'),
2281    'margin' : $('#side-nav').css('margin')
2282  });
2283  $('#devdoc-nav .totop').css({'left': 'inherit'});
2284
2285  initSidenavHeightResize();
2286}
2287
2288function buildApiLevelSelector() {
2289  maxLevel = SINCE_DATA.length;
2290  var userApiLevel = parseInt(readCookie(API_LEVEL_COOKIE));
2291  userApiLevel = userApiLevel == 0 ? maxLevel : userApiLevel; // If there's no cookie (zero), use the max by default
2292
2293  minLevel = parseInt($("#doc-api-level").attr("class"));
2294  // Handle provisional api levels; the provisional level will always be the highest possible level
2295  // Provisional api levels will also have a length; other stuff that's just missing a level won't,
2296  // so leave those kinds of entities at the default level of 1 (for example, the R.styleable class)
2297  if (isNaN(minLevel) && minLevel.length) {
2298    minLevel = maxLevel;
2299  }
2300  var select = $("#apiLevelSelector").html("").change(changeApiLevel);
2301  for (var i = maxLevel-1; i >= 0; i--) {
2302    var option = $("<option />").attr("value",""+SINCE_DATA[i]).append(""+SINCE_DATA[i]);
2303  //  if (SINCE_DATA[i] < minLevel) option.addClass("absent"); // always false for strings (codenames)
2304    select.append(option);
2305  }
2306
2307  // get the DOM element and use setAttribute cuz IE6 fails when using jquery .attr('selected',true)
2308  var selectedLevelItem = $("#apiLevelSelector option[value='"+userApiLevel+"']").get(0);
2309  selectedLevelItem.setAttribute('selected',true);
2310}
2311
2312function changeApiLevel() {
2313  maxLevel = SINCE_DATA.length;
2314  var selectedLevel = maxLevel;
2315
2316  selectedLevel = parseInt($("#apiLevelSelector option:selected").val());
2317  toggleVisisbleApis(selectedLevel, "body");
2318
2319  var date = new Date();
2320  date.setTime(date.getTime()+(10*365*24*60*60*1000)); // keep this for 10 years
2321  var expiration = date.toGMTString();
2322  writeCookie(API_LEVEL_COOKIE, selectedLevel, null, expiration);
2323
2324  if (selectedLevel < minLevel) {
2325    var thing = ($("#jd-header").html().indexOf("package") != -1) ? "package" : "class";
2326    $("#naMessage").show().html("<div><p><strong>This " + thing
2327              + " requires API level " + minLevel + " or higher.</strong></p>"
2328              + "<p>This document is hidden because your selected API level for the documentation is "
2329              + selectedLevel + ". You can change the documentation API level with the selector "
2330              + "above the left navigation.</p>"
2331              + "<p>For more information about specifying the API level your app requires, "
2332              + "read <a href='" + toRoot + "training/basics/supporting-devices/platforms.html'"
2333              + ">Supporting Different Platform Versions</a>.</p>"
2334              + "<input type='button' value='OK, make this page visible' "
2335              + "title='Change the API level to " + minLevel + "' "
2336              + "onclick='$(\"#apiLevelSelector\").val(\"" + minLevel + "\");changeApiLevel();' />"
2337              + "</div>");
2338  } else {
2339    $("#naMessage").hide();
2340  }
2341}
2342
2343function toggleVisisbleApis(selectedLevel, context) {
2344  var apis = $(".api",context);
2345  apis.each(function(i) {
2346    var obj = $(this);
2347    var className = obj.attr("class");
2348    var apiLevelIndex = className.lastIndexOf("-")+1;
2349    var apiLevelEndIndex = className.indexOf(" ", apiLevelIndex);
2350    apiLevelEndIndex = apiLevelEndIndex != -1 ? apiLevelEndIndex : className.length;
2351    var apiLevel = className.substring(apiLevelIndex, apiLevelEndIndex);
2352    if (apiLevel.length == 0) { // for odd cases when the since data is actually missing, just bail
2353      return;
2354    }
2355    apiLevel = parseInt(apiLevel);
2356
2357    // Handle provisional api levels; if this item's level is the provisional one, set it to the max
2358    var selectedLevelNum = parseInt(selectedLevel)
2359    var apiLevelNum = parseInt(apiLevel);
2360    if (isNaN(apiLevelNum)) {
2361        apiLevelNum = maxLevel;
2362    }
2363
2364    // Grey things out that aren't available and give a tooltip title
2365    if (apiLevelNum > selectedLevelNum) {
2366      obj.addClass("absent").attr("title","Requires API Level \""
2367            + apiLevel + "\" or higher");
2368    }
2369    else obj.removeClass("absent").removeAttr("title");
2370  });
2371}
2372
2373
2374
2375
2376/* #################  SIDENAV TREE VIEW ################### */
2377
2378function new_node(me, mom, text, link, children_data, api_level)
2379{
2380  var node = new Object();
2381  node.children = Array();
2382  node.children_data = children_data;
2383  node.depth = mom.depth + 1;
2384
2385  node.li = document.createElement("li");
2386  mom.get_children_ul().appendChild(node.li);
2387
2388  node.label_div = document.createElement("div");
2389  node.label_div.className = "label";
2390  if (api_level != null) {
2391    $(node.label_div).addClass("api");
2392    $(node.label_div).addClass("api-level-"+api_level);
2393  }
2394  node.li.appendChild(node.label_div);
2395
2396  if (children_data != null) {
2397    node.expand_toggle = document.createElement("a");
2398    node.expand_toggle.href = "javascript:void(0)";
2399    node.expand_toggle.onclick = function() {
2400          if (node.expanded) {
2401            $(node.get_children_ul()).slideUp("fast");
2402            node.plus_img.src = me.toroot + "assets/images/triangle-closed-small.png";
2403            node.expanded = false;
2404          } else {
2405            expand_node(me, node);
2406          }
2407       };
2408    node.label_div.appendChild(node.expand_toggle);
2409
2410    node.plus_img = document.createElement("img");
2411    node.plus_img.src = me.toroot + "assets/images/triangle-closed-small.png";
2412    node.plus_img.className = "plus";
2413    node.plus_img.width = "8";
2414    node.plus_img.border = "0";
2415    node.expand_toggle.appendChild(node.plus_img);
2416
2417    node.expanded = false;
2418  }
2419
2420  var a = document.createElement("a");
2421  node.label_div.appendChild(a);
2422  node.label = document.createTextNode(text);
2423  a.appendChild(node.label);
2424  if (link) {
2425    a.href = me.toroot + link;
2426  } else {
2427    if (children_data != null) {
2428      a.className = "nolink";
2429      a.href = "javascript:void(0)";
2430      a.onclick = node.expand_toggle.onclick;
2431      // This next line shouldn't be necessary.  I'll buy a beer for the first
2432      // person who figures out how to remove this line and have the link
2433      // toggle shut on the first try. --joeo@android.com
2434      node.expanded = false;
2435    }
2436  }
2437
2438
2439  node.children_ul = null;
2440  node.get_children_ul = function() {
2441      if (!node.children_ul) {
2442        node.children_ul = document.createElement("ul");
2443        node.children_ul.className = "children_ul";
2444        node.children_ul.style.display = "none";
2445        node.li.appendChild(node.children_ul);
2446      }
2447      return node.children_ul;
2448    };
2449
2450  return node;
2451}
2452
2453
2454
2455
2456function expand_node(me, node)
2457{
2458  if (node.children_data && !node.expanded) {
2459    if (node.children_visited) {
2460      $(node.get_children_ul()).slideDown("fast");
2461    } else {
2462      get_node(me, node);
2463      if ($(node.label_div).hasClass("absent")) {
2464        $(node.get_children_ul()).addClass("absent");
2465      }
2466      $(node.get_children_ul()).slideDown("fast");
2467    }
2468    node.plus_img.src = me.toroot + "assets/images/triangle-opened-small.png";
2469    node.expanded = true;
2470
2471    // perform api level toggling because new nodes are new to the DOM
2472    var selectedLevel = $("#apiLevelSelector option:selected").val();
2473    toggleVisisbleApis(selectedLevel, "#side-nav");
2474  }
2475}
2476
2477function get_node(me, mom)
2478{
2479  mom.children_visited = true;
2480  for (var i in mom.children_data) {
2481    var node_data = mom.children_data[i];
2482    mom.children[i] = new_node(me, mom, node_data[0], node_data[1],
2483        node_data[2], node_data[3]);
2484  }
2485}
2486
2487function this_page_relative(toroot)
2488{
2489  var full = document.location.pathname;
2490  var file = "";
2491  if (toroot.substr(0, 1) == "/") {
2492    if (full.substr(0, toroot.length) == toroot) {
2493      return full.substr(toroot.length);
2494    } else {
2495      // the file isn't under toroot.  Fail.
2496      return null;
2497    }
2498  } else {
2499    if (toroot != "./") {
2500      toroot = "./" + toroot;
2501    }
2502    do {
2503      if (toroot.substr(toroot.length-3, 3) == "../" || toroot == "./") {
2504        var pos = full.lastIndexOf("/");
2505        file = full.substr(pos) + file;
2506        full = full.substr(0, pos);
2507        toroot = toroot.substr(0, toroot.length-3);
2508      }
2509    } while (toroot != "" && toroot != "/");
2510    return file.substr(1);
2511  }
2512}
2513
2514function find_page(url, data)
2515{
2516  var nodes = data;
2517  var result = null;
2518  for (var i in nodes) {
2519    var d = nodes[i];
2520    if (d[1] == url) {
2521      return new Array(i);
2522    }
2523    else if (d[2] != null) {
2524      result = find_page(url, d[2]);
2525      if (result != null) {
2526        return (new Array(i).concat(result));
2527      }
2528    }
2529  }
2530  return null;
2531}
2532
2533function init_default_navtree(toroot) {
2534  // load json file for navtree data
2535  $.getScript(toRoot + 'navtree_data.js', function(data, textStatus, jqxhr) {
2536      // when the file is loaded, initialize the tree
2537      if(jqxhr.status === 200) {
2538          init_navtree("tree-list", toroot, NAVTREE_DATA);
2539      }
2540  });
2541
2542  // perform api level toggling because because the whole tree is new to the DOM
2543  var selectedLevel = $("#apiLevelSelector option:selected").val();
2544  toggleVisisbleApis(selectedLevel, "#side-nav");
2545}
2546
2547function init_navtree(navtree_id, toroot, root_nodes)
2548{
2549  var me = new Object();
2550  me.toroot = toroot;
2551  me.node = new Object();
2552
2553  me.node.li = document.getElementById(navtree_id);
2554  me.node.children_data = root_nodes;
2555  me.node.children = new Array();
2556  me.node.children_ul = document.createElement("ul");
2557  me.node.get_children_ul = function() { return me.node.children_ul; };
2558  //me.node.children_ul.className = "children_ul";
2559  me.node.li.appendChild(me.node.children_ul);
2560  me.node.depth = 0;
2561
2562  get_node(me, me.node);
2563
2564  me.this_page = this_page_relative(toroot);
2565  me.breadcrumbs = find_page(me.this_page, root_nodes);
2566  if (me.breadcrumbs != null && me.breadcrumbs.length != 0) {
2567    var mom = me.node;
2568    for (var i in me.breadcrumbs) {
2569      var j = me.breadcrumbs[i];
2570      mom = mom.children[j];
2571      expand_node(me, mom);
2572    }
2573    mom.label_div.className = mom.label_div.className + " selected";
2574    addLoadEvent(function() {
2575      scrollIntoView("nav-tree");
2576      });
2577  }
2578}
2579
2580/* TODO: eliminate redundancy with non-google functions */
2581function init_google_navtree(navtree_id, toroot, root_nodes)
2582{
2583  var me = new Object();
2584  me.toroot = toroot;
2585  me.node = new Object();
2586
2587  me.node.li = document.getElementById(navtree_id);
2588  me.node.children_data = root_nodes;
2589  me.node.children = new Array();
2590  me.node.children_ul = document.createElement("ul");
2591  me.node.get_children_ul = function() { return me.node.children_ul; };
2592  //me.node.children_ul.className = "children_ul";
2593  me.node.li.appendChild(me.node.children_ul);
2594  me.node.depth = 0;
2595
2596  get_google_node(me, me.node);
2597}
2598
2599function new_google_node(me, mom, text, link, children_data, api_level)
2600{
2601  var node = new Object();
2602  var child;
2603  node.children = Array();
2604  node.children_data = children_data;
2605  node.depth = mom.depth + 1;
2606  node.get_children_ul = function() {
2607      if (!node.children_ul) {
2608        node.children_ul = document.createElement("ul");
2609        node.children_ul.className = "tree-list-children";
2610        node.li.appendChild(node.children_ul);
2611      }
2612      return node.children_ul;
2613    };
2614  node.li = document.createElement("li");
2615
2616  mom.get_children_ul().appendChild(node.li);
2617
2618
2619  if(link) {
2620    child = document.createElement("a");
2621
2622  }
2623  else {
2624    child = document.createElement("span");
2625    child.className = "tree-list-subtitle";
2626
2627  }
2628  if (children_data != null) {
2629    node.li.className="nav-section";
2630    node.label_div = document.createElement("div");
2631    node.label_div.className = "nav-section-header-ref";
2632    node.li.appendChild(node.label_div);
2633    get_google_node(me, node);
2634    node.label_div.appendChild(child);
2635  }
2636  else {
2637    node.li.appendChild(child);
2638  }
2639  if(link) {
2640    child.href = me.toroot + link;
2641  }
2642  node.label = document.createTextNode(text);
2643  child.appendChild(node.label);
2644
2645  node.children_ul = null;
2646
2647  return node;
2648}
2649
2650function get_google_node(me, mom)
2651{
2652  mom.children_visited = true;
2653  var linkText;
2654  for (var i in mom.children_data) {
2655    var node_data = mom.children_data[i];
2656    linkText = node_data[0];
2657
2658    if(linkText.match("^"+"com.google.android")=="com.google.android"){
2659      linkText = linkText.substr(19, linkText.length);
2660    }
2661      mom.children[i] = new_google_node(me, mom, linkText, node_data[1],
2662          node_data[2], node_data[3]);
2663  }
2664}
2665function showGoogleRefTree() {
2666  init_default_google_navtree(toRoot);
2667  init_default_gcm_navtree(toRoot);
2668}
2669
2670function init_default_google_navtree(toroot) {
2671  // load json file for navtree data
2672  $.getScript(toRoot + 'gms_navtree_data.js', function(data, textStatus, jqxhr) {
2673      // when the file is loaded, initialize the tree
2674      if(jqxhr.status === 200) {
2675          init_google_navtree("gms-tree-list", toroot, GMS_NAVTREE_DATA);
2676          highlightSidenav();
2677          resizeNav();
2678      }
2679  });
2680}
2681
2682function init_default_gcm_navtree(toroot) {
2683  // load json file for navtree data
2684  $.getScript(toRoot + 'gcm_navtree_data.js', function(data, textStatus, jqxhr) {
2685      // when the file is loaded, initialize the tree
2686      if(jqxhr.status === 200) {
2687          init_google_navtree("gcm-tree-list", toroot, GCM_NAVTREE_DATA);
2688          highlightSidenav();
2689          resizeNav();
2690      }
2691  });
2692}
2693
2694/* TOGGLE INHERITED MEMBERS */
2695
2696/* Toggle an inherited class (arrow toggle)
2697 * @param linkObj  The link that was clicked.
2698 * @param expand  'true' to ensure it's expanded. 'false' to ensure it's closed.
2699 *                'null' to simply toggle.
2700 */
2701function toggleInherited(linkObj, expand) {
2702    var base = linkObj.getAttribute("id");
2703    var list = document.getElementById(base + "-list");
2704    var summary = document.getElementById(base + "-summary");
2705    var trigger = document.getElementById(base + "-trigger");
2706    var a = $(linkObj);
2707    if ( (expand == null && a.hasClass("closed")) || expand ) {
2708        list.style.display = "none";
2709        summary.style.display = "block";
2710        trigger.src = toRoot + "assets/images/triangle-opened.png";
2711        a.removeClass("closed");
2712        a.addClass("opened");
2713    } else if ( (expand == null && a.hasClass("opened")) || (expand == false) ) {
2714        list.style.display = "block";
2715        summary.style.display = "none";
2716        trigger.src = toRoot + "assets/images/triangle-closed.png";
2717        a.removeClass("opened");
2718        a.addClass("closed");
2719    }
2720    return false;
2721}
2722
2723/* Toggle all inherited classes in a single table (e.g. all inherited methods)
2724 * @param linkObj  The link that was clicked.
2725 * @param expand  'true' to ensure it's expanded. 'false' to ensure it's closed.
2726 *                'null' to simply toggle.
2727 */
2728function toggleAllInherited(linkObj, expand) {
2729  var a = $(linkObj);
2730  var table = $(a.parent().parent().parent()); // ugly way to get table/tbody
2731  var expandos = $(".jd-expando-trigger", table);
2732  if ( (expand == null && a.text() == "[Expand]") || expand ) {
2733    expandos.each(function(i) {
2734      toggleInherited(this, true);
2735    });
2736    a.text("[Collapse]");
2737  } else if ( (expand == null && a.text() == "[Collapse]") || (expand == false) ) {
2738    expandos.each(function(i) {
2739      toggleInherited(this, false);
2740    });
2741    a.text("[Expand]");
2742  }
2743  return false;
2744}
2745
2746/* Toggle all inherited members in the class (link in the class title)
2747 */
2748function toggleAllClassInherited() {
2749  var a = $("#toggleAllClassInherited"); // get toggle link from class title
2750  var toggles = $(".toggle-all", $("#body-content"));
2751  if (a.text() == "[Expand All]") {
2752    toggles.each(function(i) {
2753      toggleAllInherited(this, true);
2754    });
2755    a.text("[Collapse All]");
2756  } else {
2757    toggles.each(function(i) {
2758      toggleAllInherited(this, false);
2759    });
2760    a.text("[Expand All]");
2761  }
2762  return false;
2763}
2764
2765/* Expand all inherited members in the class. Used when initiating page search */
2766function ensureAllInheritedExpanded() {
2767  var toggles = $(".toggle-all", $("#body-content"));
2768  toggles.each(function(i) {
2769    toggleAllInherited(this, true);
2770  });
2771  $("#toggleAllClassInherited").text("[Collapse All]");
2772}
2773
2774
2775/* HANDLE KEY EVENTS
2776 * - Listen for Ctrl+F (Cmd on Mac) and expand all inherited members (to aid page search)
2777 */
2778var agent = navigator['userAgent'].toLowerCase();
2779var mac = agent.indexOf("macintosh") != -1;
2780
2781$(document).keydown( function(e) {
2782var control = mac ? e.metaKey && !e.ctrlKey : e.ctrlKey; // get ctrl key
2783  if (control && e.which == 70) {  // 70 is "F"
2784    ensureAllInheritedExpanded();
2785  }
2786});
2787