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