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