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