docs.js revision 77c51357eb1ba3b55d0a9363cb981daab1b484d2
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    if (submit) {
1067      window.location = getBaseUri(location.pathname);
1068    }
1069  }
1070}
1071
1072function loadLangPref() {
1073  var lang = readCookie("pref_lang");
1074  if (lang != 0) {
1075    $("#language").find("option[value='"+lang+"']").attr("selected",true);
1076  }
1077}
1078
1079function getLangPref() {
1080  var lang = $("#language").find(":selected").attr("value");
1081  if (!lang) {
1082    lang = readCookie("pref_lang");
1083  }
1084  return (lang != 0) ? lang : 'en';
1085}
1086
1087/* ##########     END LOCALIZATION     ############ */
1088
1089
1090
1091
1092
1093
1094/* Used to hide and reveal supplemental content, such as long code samples.
1095   See the companion CSS in android-developer-docs.css */
1096function toggleContent(obj) {
1097  var div = $(obj.parentNode.parentNode);
1098  var toggleMe = $(".toggle-content-toggleme",div);
1099  if (div.hasClass("closed")) { // if it's closed, open it
1100    toggleMe.slideDown();
1101    $(".toggle-content-text", obj).toggle();
1102    div.removeClass("closed").addClass("open");
1103    $(".toggle-content-img", div).attr("title", "hide").attr("src", toRoot
1104                  + "assets/images/triangle-opened.png");
1105  } else { // if it's open, close it
1106    toggleMe.slideUp('fast', function() {  // Wait until the animation is done before closing arrow
1107      $(".toggle-content-text", obj).toggle();
1108      div.removeClass("open").addClass("closed");
1109      $(".toggle-content-img", div).attr("title", "show").attr("src", toRoot
1110                  + "assets/images/triangle-closed.png");
1111    });
1112  }
1113  return false;
1114}
1115
1116
1117/* New version of expandable content */
1118function toggleExpandable(link,id) {
1119  if($(id).is(':visible')) {
1120    $(id).slideUp();
1121    $(link).removeClass('expanded');
1122  } else {
1123    $(id).slideDown();
1124    $(link).addClass('expanded');
1125  }
1126}
1127
1128function hideExpandable(ids) {
1129  $(ids).slideUp();
1130  $(ids).prev('h4').find('a.expandable').removeClass('expanded');
1131}
1132
1133
1134
1135
1136
1137/*
1138 *  Slideshow 1.0
1139 *  Used on /index.html and /develop/index.html for carousel
1140 *
1141 *  Sample usage:
1142 *  HTML -
1143 *  <div class="slideshow-container">
1144 *   <a href="" class="slideshow-prev">Prev</a>
1145 *   <a href="" class="slideshow-next">Next</a>
1146 *   <ul>
1147 *       <li class="item"><img src="images/marquee1.jpg"></li>
1148 *       <li class="item"><img src="images/marquee2.jpg"></li>
1149 *       <li class="item"><img src="images/marquee3.jpg"></li>
1150 *       <li class="item"><img src="images/marquee4.jpg"></li>
1151 *   </ul>
1152 *  </div>
1153 *
1154 *   <script type="text/javascript">
1155 *   $('.slideshow-container').dacSlideshow({
1156 *       auto: true,
1157 *       btnPrev: '.slideshow-prev',
1158 *       btnNext: '.slideshow-next'
1159 *   });
1160 *   </script>
1161 *
1162 *  Options:
1163 *  btnPrev:    optional identifier for previous button
1164 *  btnNext:    optional identifier for next button
1165 *  btnPause:   optional identifier for pause button
1166 *  auto:       whether or not to auto-proceed
1167 *  speed:      animation speed
1168 *  autoTime:   time between auto-rotation
1169 *  easing:     easing function for transition
1170 *  start:      item to select by default
1171 *  scroll:     direction to scroll in
1172 *  pagination: whether or not to include dotted pagination
1173 *
1174 */
1175
1176 (function($) {
1177 $.fn.dacSlideshow = function(o) {
1178
1179     //Options - see above
1180     o = $.extend({
1181         btnPrev:   null,
1182         btnNext:   null,
1183         btnPause:  null,
1184         auto:      true,
1185         speed:     500,
1186         autoTime:  12000,
1187         easing:    null,
1188         start:     0,
1189         scroll:    1,
1190         pagination: true
1191
1192     }, o || {});
1193
1194     //Set up a carousel for each
1195     return this.each(function() {
1196
1197         var running = false;
1198         var animCss = o.vertical ? "top" : "left";
1199         var sizeCss = o.vertical ? "height" : "width";
1200         var div = $(this);
1201         var ul = $("ul", div);
1202         var tLi = $("li", ul);
1203         var tl = tLi.size();
1204         var timer = null;
1205
1206         var li = $("li", ul);
1207         var itemLength = li.size();
1208         var curr = o.start;
1209
1210         li.css({float: o.vertical ? "none" : "left"});
1211         ul.css({margin: "0", padding: "0", position: "relative", "list-style-type": "none", "z-index": "1"});
1212         div.css({position: "relative", "z-index": "2", left: "0px"});
1213
1214         var liSize = o.vertical ? height(li) : width(li);
1215         var ulSize = liSize * itemLength;
1216         var divSize = liSize;
1217
1218         li.css({width: li.width(), height: li.height()});
1219         ul.css(sizeCss, ulSize+"px").css(animCss, -(curr*liSize));
1220
1221         div.css(sizeCss, divSize+"px");
1222
1223         //Pagination
1224         if (o.pagination) {
1225             var pagination = $("<div class='pagination'></div>");
1226             var pag_ul = $("<ul></ul>");
1227             if (tl > 1) {
1228               for (var i=0;i<tl;i++) {
1229                    var li = $("<li>"+i+"</li>");
1230                    pag_ul.append(li);
1231                    if (i==o.start) li.addClass('active');
1232                        li.click(function() {
1233                        go(parseInt($(this).text()));
1234                    })
1235                }
1236                pagination.append(pag_ul);
1237                div.append(pagination);
1238             }
1239         }
1240
1241         //Previous button
1242         if(o.btnPrev)
1243             $(o.btnPrev).click(function(e) {
1244                 e.preventDefault();
1245                 return go(curr-o.scroll);
1246             });
1247
1248         //Next button
1249         if(o.btnNext)
1250             $(o.btnNext).click(function(e) {
1251                 e.preventDefault();
1252                 return go(curr+o.scroll);
1253             });
1254
1255         //Pause button
1256         if(o.btnPause)
1257             $(o.btnPause).click(function(e) {
1258                 e.preventDefault();
1259                 if ($(this).hasClass('paused')) {
1260                     startRotateTimer();
1261                 } else {
1262                     pauseRotateTimer();
1263                 }
1264             });
1265
1266         //Auto rotation
1267         if(o.auto) startRotateTimer();
1268
1269         function startRotateTimer() {
1270             clearInterval(timer);
1271             timer = setInterval(function() {
1272                  if (curr == tl-1) {
1273                    go(0);
1274                  } else {
1275                    go(curr+o.scroll);
1276                  }
1277              }, o.autoTime);
1278             $(o.btnPause).removeClass('paused');
1279         }
1280
1281         function pauseRotateTimer() {
1282             clearInterval(timer);
1283             $(o.btnPause).addClass('paused');
1284         }
1285
1286         //Go to an item
1287         function go(to) {
1288             if(!running) {
1289
1290                 if(to<0) {
1291                    to = itemLength-1;
1292                 } else if (to>itemLength-1) {
1293                    to = 0;
1294                 }
1295                 curr = to;
1296
1297                 running = true;
1298
1299                 ul.animate(
1300                     animCss == "left" ? { left: -(curr*liSize) } : { top: -(curr*liSize) } , o.speed, o.easing,
1301                     function() {
1302                         running = false;
1303                     }
1304                 );
1305
1306                 $(o.btnPrev + "," + o.btnNext).removeClass("disabled");
1307                 $( (curr-o.scroll<0 && o.btnPrev)
1308                     ||
1309                    (curr+o.scroll > itemLength && o.btnNext)
1310                     ||
1311                    []
1312                  ).addClass("disabled");
1313
1314
1315                 var nav_items = $('li', pagination);
1316                 nav_items.removeClass('active');
1317                 nav_items.eq(to).addClass('active');
1318
1319
1320             }
1321             if(o.auto) startRotateTimer();
1322             return false;
1323         };
1324     });
1325 };
1326
1327 function css(el, prop) {
1328     return parseInt($.css(el[0], prop)) || 0;
1329 };
1330 function width(el) {
1331     return  el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight');
1332 };
1333 function height(el) {
1334     return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom');
1335 };
1336
1337 })(jQuery);
1338
1339
1340/*
1341 *  dacSlideshow 1.0
1342 *  Used on develop/index.html for side-sliding tabs
1343 *
1344 *  Sample usage:
1345 *  HTML -
1346 *  <div class="slideshow-container">
1347 *   <a href="" class="slideshow-prev">Prev</a>
1348 *   <a href="" class="slideshow-next">Next</a>
1349 *   <ul>
1350 *       <li class="item"><img src="images/marquee1.jpg"></li>
1351 *       <li class="item"><img src="images/marquee2.jpg"></li>
1352 *       <li class="item"><img src="images/marquee3.jpg"></li>
1353 *       <li class="item"><img src="images/marquee4.jpg"></li>
1354 *   </ul>
1355 *  </div>
1356 *
1357 *   <script type="text/javascript">
1358 *   $('.slideshow-container').dacSlideshow({
1359 *       auto: true,
1360 *       btnPrev: '.slideshow-prev',
1361 *       btnNext: '.slideshow-next'
1362 *   });
1363 *   </script>
1364 *
1365 *  Options:
1366 *  btnPrev:    optional identifier for previous button
1367 *  btnNext:    optional identifier for next button
1368 *  auto:       whether or not to auto-proceed
1369 *  speed:      animation speed
1370 *  autoTime:   time between auto-rotation
1371 *  easing:     easing function for transition
1372 *  start:      item to select by default
1373 *  scroll:     direction to scroll in
1374 *  pagination: whether or not to include dotted pagination
1375 *
1376 */
1377 (function($) {
1378 $.fn.dacTabbedList = function(o) {
1379
1380     //Options - see above
1381     o = $.extend({
1382         speed : 250,
1383         easing: null,
1384         nav_id: null,
1385         frame_id: null
1386     }, o || {});
1387
1388     //Set up a carousel for each
1389     return this.each(function() {
1390
1391         var curr = 0;
1392         var running = false;
1393         var animCss = "margin-left";
1394         var sizeCss = "width";
1395         var div = $(this);
1396
1397         var nav = $(o.nav_id, div);
1398         var nav_li = $("li", nav);
1399         var nav_size = nav_li.size();
1400         var frame = div.find(o.frame_id);
1401         var content_width = $(frame).find('ul').width();
1402         //Buttons
1403         $(nav_li).click(function(e) {
1404           go($(nav_li).index($(this)));
1405         })
1406
1407         //Go to an item
1408         function go(to) {
1409             if(!running) {
1410                 curr = to;
1411                 running = true;
1412
1413                 frame.animate({ 'margin-left' : -(curr*content_width) }, o.speed, o.easing,
1414                     function() {
1415                         running = false;
1416                     }
1417                 );
1418
1419
1420                 nav_li.removeClass('active');
1421                 nav_li.eq(to).addClass('active');
1422
1423
1424             }
1425             return false;
1426         };
1427     });
1428 };
1429
1430 function css(el, prop) {
1431     return parseInt($.css(el[0], prop)) || 0;
1432 };
1433 function width(el) {
1434     return  el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight');
1435 };
1436 function height(el) {
1437     return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom');
1438 };
1439
1440 })(jQuery);
1441
1442
1443
1444
1445
1446/* ######################################################## */
1447/* ################  SEARCH SUGGESTIONS  ################## */
1448/* ######################################################## */
1449
1450
1451var gSelectedIndex = -1;
1452var gSelectedID = -1;
1453var gMatches = new Array();
1454var gLastText = "";
1455var ROW_COUNT = 20;
1456var gInitialized = false;
1457
1458function set_item_selected($li, selected)
1459{
1460    if (selected) {
1461        $li.attr('class','jd-autocomplete jd-selected');
1462    } else {
1463        $li.attr('class','jd-autocomplete');
1464    }
1465}
1466
1467function set_item_values(toroot, $li, match)
1468{
1469    var $link = $('a',$li);
1470    $link.html(match.__hilabel || match.label);
1471    $link.attr('href',toroot + match.link);
1472}
1473
1474function sync_selection_table(toroot)
1475{
1476    var $list = $("#search_filtered");
1477    var $li; //list item jquery object
1478    var i; //list item iterator
1479    gSelectedID = -1;
1480
1481    //initialize the table; draw it for the first time (but not visible).
1482    if (!gInitialized) {
1483        for (i=0; i<ROW_COUNT; i++) {
1484            var $li = $("<li class='jd-autocomplete'></li>");
1485            $list.append($li);
1486
1487            $li.mousedown(function() {
1488                window.location = this.firstChild.getAttribute("href");
1489            });
1490            $li.mouseover(function() {
1491                $('#search_filtered li').removeClass('jd-selected');
1492                $(this).addClass('jd-selected');
1493                gSelectedIndex = $('#search_filtered li').index(this);
1494            });
1495            $li.append('<a></a>');
1496        }
1497        gInitialized = true;
1498    }
1499
1500    //if we have results, make the table visible and initialize result info
1501    if (gMatches.length > 0) {
1502        $('#search_filtered_div').removeClass('no-display');
1503        var N = gMatches.length < ROW_COUNT ? gMatches.length : ROW_COUNT;
1504        for (i=0; i<N; i++) {
1505            $li = $('#search_filtered li:nth-child('+(i+1)+')');
1506            $li.attr('class','show-item');
1507            set_item_values(toroot, $li, gMatches[i]);
1508            set_item_selected($li, i == gSelectedIndex);
1509            if (i == gSelectedIndex) {
1510                gSelectedID = gMatches[i].id;
1511            }
1512        }
1513        //start hiding rows that are no longer matches
1514        for (; i<ROW_COUNT; i++) {
1515            $li = $('#search_filtered li:nth-child('+(i+1)+')');
1516            $li.attr('class','no-display');
1517        }
1518        //if there are more results we're not showing, so say so.
1519/*      if (gMatches.length > ROW_COUNT) {
1520            li = list.rows[ROW_COUNT];
1521            li.className = "show-item";
1522            c1 = li.cells[0];
1523            c1.innerHTML = "plus " + (gMatches.length-ROW_COUNT) + " more";
1524        } else {
1525            list.rows[ROW_COUNT].className = "hide-item";
1526        }*/
1527    //if we have no results, hide the table
1528    } else {
1529        $('#search_filtered_div').addClass('no-display');
1530    }
1531}
1532
1533function search_changed(e, kd, toroot)
1534{
1535    var search = document.getElementById("search_autocomplete");
1536    var text = search.value.replace(/(^ +)|( +$)/g, '');
1537
1538    // show/hide the close button
1539    if (text != '') {
1540        $(".search .close").removeClass("hide");
1541    } else {
1542        $(".search .close").addClass("hide");
1543    }
1544
1545    // 13 = enter
1546    if (e.keyCode == 13) {
1547        $('#search_filtered_div').addClass('no-display');
1548        if (!$('#search_filtered_div').hasClass('no-display') || (gSelectedIndex < 0)) {
1549            if ($("#searchResults").is(":hidden")) {
1550              // if results aren't showing, return true to allow search to execute
1551              return true;
1552            } else {
1553              // otherwise, results are already showing, so allow ajax to auto refresh the results
1554              // and ignore this Enter press to avoid the reload.
1555              return false;
1556            }
1557        } else if (kd && gSelectedIndex >= 0) {
1558            window.location = toroot + gMatches[gSelectedIndex].link;
1559            return false;
1560        }
1561    }
1562    // 38 -- arrow up
1563    else if (kd && (e.keyCode == 38)) {
1564        if (gSelectedIndex >= 0) {
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    // 40 -- arrow down
1572    else if (kd && (e.keyCode == 40)) {
1573        if (gSelectedIndex < gMatches.length-1
1574                        && gSelectedIndex < ROW_COUNT-1) {
1575            $('#search_filtered li').removeClass('jd-selected');
1576            gSelectedIndex++;
1577            $('#search_filtered li:nth-child('+(gSelectedIndex+1)+')').addClass('jd-selected');
1578        }
1579        return false;
1580    }
1581    else if (!kd && (e.keyCode != 40) && (e.keyCode != 38)) {
1582        gMatches = new Array();
1583        matchedCount = 0;
1584        gSelectedIndex = -1;
1585        for (var i=0; i<DATA.length; i++) {
1586            var s = DATA[i];
1587            if (text.length != 0 &&
1588                  s.label.toLowerCase().indexOf(text.toLowerCase()) != -1) {
1589                gMatches[matchedCount] = s;
1590                matchedCount++;
1591            }
1592        }
1593        rank_autocomplete_results(text);
1594        for (var i=0; i<gMatches.length; i++) {
1595            var s = gMatches[i];
1596            if (gSelectedID == s.id) {
1597                gSelectedIndex = i;
1598            }
1599        }
1600        highlight_autocomplete_result_labels(text);
1601        sync_selection_table(toroot);
1602        return true; // allow the event to bubble up to the search api
1603    }
1604}
1605
1606function rank_autocomplete_results(query) {
1607    query = query || '';
1608    if (!gMatches || !gMatches.length)
1609      return;
1610
1611    // helper function that gets the last occurence index of the given regex
1612    // in the given string, or -1 if not found
1613    var _lastSearch = function(s, re) {
1614      if (s == '')
1615        return -1;
1616      var l = -1;
1617      var tmp;
1618      while ((tmp = s.search(re)) >= 0) {
1619        if (l < 0) l = 0;
1620        l += tmp;
1621        s = s.substr(tmp + 1);
1622      }
1623      return l;
1624    };
1625
1626    // helper function that counts the occurrences of a given character in
1627    // a given string
1628    var _countChar = function(s, c) {
1629      var n = 0;
1630      for (var i=0; i<s.length; i++)
1631        if (s.charAt(i) == c) ++n;
1632      return n;
1633    };
1634
1635    var queryLower = query.toLowerCase();
1636    var queryAlnum = (queryLower.match(/\w+/) || [''])[0];
1637    var partPrefixAlnumRE = new RegExp('\\b' + queryAlnum);
1638    var partExactAlnumRE = new RegExp('\\b' + queryAlnum + '\\b');
1639
1640    var _resultScoreFn = function(result) {
1641        // scores are calculated based on exact and prefix matches,
1642        // and then number of path separators (dots) from the last
1643        // match (i.e. favoring classes and deep package names)
1644        var score = 1.0;
1645        var labelLower = result.label.toLowerCase();
1646        var t;
1647        t = _lastSearch(labelLower, partExactAlnumRE);
1648        if (t >= 0) {
1649            // exact part match
1650            var partsAfter = _countChar(labelLower.substr(t + 1), '.');
1651            score *= 200 / (partsAfter + 1);
1652        } else {
1653            t = _lastSearch(labelLower, partPrefixAlnumRE);
1654            if (t >= 0) {
1655                // part prefix match
1656                var partsAfter = _countChar(labelLower.substr(t + 1), '.');
1657                score *= 20 / (partsAfter + 1);
1658            }
1659        }
1660
1661        return score;
1662    };
1663
1664    for (var i=0; i<gMatches.length; i++) {
1665        gMatches[i].__resultScore = _resultScoreFn(gMatches[i]);
1666    }
1667
1668    gMatches.sort(function(a,b){
1669        var n = b.__resultScore - a.__resultScore;
1670        if (n == 0) // lexicographical sort if scores are the same
1671            n = (a.label < b.label) ? -1 : 1;
1672        return n;
1673    });
1674}
1675
1676function highlight_autocomplete_result_labels(query) {
1677    query = query || '';
1678    if (!gMatches || !gMatches.length)
1679      return;
1680
1681    var queryLower = query.toLowerCase();
1682    var queryAlnumDot = (queryLower.match(/[\w\.]+/) || [''])[0];
1683    var queryRE = new RegExp(
1684        '(' + queryAlnumDot.replace(/\./g, '\\.') + ')', 'ig');
1685    for (var i=0; i<gMatches.length; i++) {
1686        gMatches[i].__hilabel = gMatches[i].label.replace(
1687            queryRE, '<b>$1</b>');
1688    }
1689}
1690
1691function search_focus_changed(obj, focused)
1692{
1693    if (!focused) {
1694        if(obj.value == ""){
1695          $(".search .close").addClass("hide");
1696        }
1697        document.getElementById("search_filtered_div").className = "no-display";
1698    }
1699}
1700
1701function submit_search() {
1702  var query = document.getElementById('search_autocomplete').value;
1703  location.hash = 'q=' + query;
1704  loadSearchResults();
1705  $("#searchResults").slideDown('slow');
1706  return false;
1707}
1708
1709
1710function hideResults() {
1711  $("#searchResults").slideUp();
1712  $(".search .close").addClass("hide");
1713  location.hash = '';
1714
1715  $("#search_autocomplete").val("").blur();
1716
1717  // reset the ajax search callback to nothing, so results don't appear unless ENTER
1718  searchControl.setSearchStartingCallback(this, function(control, searcher, query) {});
1719  return false;
1720}
1721
1722
1723
1724/* ########################################################## */
1725/* ################  CUSTOM SEARCH ENGINE  ################## */
1726/* ########################################################## */
1727
1728google.load('search', '1');
1729var searchControl;
1730
1731function loadSearchResults() {
1732  document.getElementById("search_autocomplete").style.color = "#000";
1733
1734  // create search control
1735  searchControl = new google.search.SearchControl();
1736
1737  // use our existing search form and use tabs when multiple searchers are used
1738  drawOptions = new google.search.DrawOptions();
1739  drawOptions.setDrawMode(google.search.SearchControl.DRAW_MODE_TABBED);
1740  drawOptions.setInput(document.getElementById("search_autocomplete"));
1741
1742  // configure search result options
1743  searchOptions = new google.search.SearcherOptions();
1744  searchOptions.setExpandMode(GSearchControl.EXPAND_MODE_OPEN);
1745
1746  // configure each of the searchers, for each tab
1747  devSiteSearcher = new google.search.WebSearch();
1748  devSiteSearcher.setUserDefinedLabel("All");
1749  devSiteSearcher.setSiteRestriction("001482626316274216503:zu90b7s047u");
1750
1751  designSearcher = new google.search.WebSearch();
1752  designSearcher.setUserDefinedLabel("Design");
1753  designSearcher.setSiteRestriction("http://developer.android.com/design/");
1754
1755  trainingSearcher = new google.search.WebSearch();
1756  trainingSearcher.setUserDefinedLabel("Training");
1757  trainingSearcher.setSiteRestriction("http://developer.android.com/training/");
1758
1759  guidesSearcher = new google.search.WebSearch();
1760  guidesSearcher.setUserDefinedLabel("Guides");
1761  guidesSearcher.setSiteRestriction("http://developer.android.com/guide/");
1762
1763  referenceSearcher = new google.search.WebSearch();
1764  referenceSearcher.setUserDefinedLabel("Reference");
1765  referenceSearcher.setSiteRestriction("http://developer.android.com/reference/");
1766
1767  googleSearcher = new google.search.WebSearch();
1768  googleSearcher.setUserDefinedLabel("Google Services");
1769  googleSearcher.setSiteRestriction("http://developer.android.com/google/");
1770
1771  blogSearcher = new google.search.WebSearch();
1772  blogSearcher.setUserDefinedLabel("Blog");
1773  blogSearcher.setSiteRestriction("http://android-developers.blogspot.com");
1774
1775  // add each searcher to the search control
1776  searchControl.addSearcher(devSiteSearcher, searchOptions);
1777  searchControl.addSearcher(designSearcher, searchOptions);
1778  searchControl.addSearcher(trainingSearcher, searchOptions);
1779  searchControl.addSearcher(guidesSearcher, searchOptions);
1780  searchControl.addSearcher(referenceSearcher, searchOptions);
1781  searchControl.addSearcher(googleSearcher, searchOptions);
1782  searchControl.addSearcher(blogSearcher, searchOptions);
1783
1784  // configure result options
1785  searchControl.setResultSetSize(google.search.Search.LARGE_RESULTSET);
1786  searchControl.setLinkTarget(google.search.Search.LINK_TARGET_SELF);
1787  searchControl.setTimeoutInterval(google.search.SearchControl.TIMEOUT_SHORT);
1788  searchControl.setNoResultsString(google.search.SearchControl.NO_RESULTS_DEFAULT_STRING);
1789
1790  // upon ajax search, refresh the url and search title
1791  searchControl.setSearchStartingCallback(this, function(control, searcher, query) {
1792    updateResultTitle(query);
1793    var query = document.getElementById('search_autocomplete').value;
1794    location.hash = 'q=' + query;
1795  });
1796
1797  // draw the search results box
1798  searchControl.draw(document.getElementById("leftSearchControl"), drawOptions);
1799
1800  // get query and execute the search
1801  searchControl.execute(decodeURI(getQuery(location.hash)));
1802
1803  document.getElementById("search_autocomplete").focus();
1804  addTabListeners();
1805}
1806// End of loadSearchResults
1807
1808
1809google.setOnLoadCallback(function(){
1810  if (location.hash.indexOf("q=") == -1) {
1811    // if there's no query in the url, don't search and make sure results are hidden
1812    $('#searchResults').hide();
1813    return;
1814  } else {
1815    // first time loading search results for this page
1816    $('#searchResults').slideDown('slow');
1817    $(".search .close").removeClass("hide");
1818    loadSearchResults();
1819  }
1820}, true);
1821
1822// when an event on the browser history occurs (back, forward, load) requery hash and do search
1823$(window).hashchange( function(){
1824  // Exit if the hash isn't a search query or there's an error in the query
1825  if ((location.hash.indexOf("q=") == -1) || (query == "undefined")) {
1826    // If the results pane is open, close it.
1827    if (!$("#searchResults").is(":hidden")) {
1828      hideResults();
1829    }
1830    return;
1831  }
1832
1833  // Otherwise, we have a search to do
1834  var query = decodeURI(getQuery(location.hash));
1835  searchControl.execute(query);
1836  $('#searchResults').slideDown('slow');
1837  $("#search_autocomplete").focus();
1838  $(".search .close").removeClass("hide");
1839
1840  updateResultTitle(query);
1841});
1842
1843function updateResultTitle(query) {
1844  $("#searchTitle").html("Results for <em>" + escapeHTML(query) + "</em>");
1845}
1846
1847// forcefully regain key-up event control (previously jacked by search api)
1848$("#search_autocomplete").keyup(function(event) {
1849  return search_changed(event, false, toRoot);
1850});
1851
1852// add event listeners to each tab so we can track the browser history
1853function addTabListeners() {
1854  var tabHeaders = $(".gsc-tabHeader");
1855  for (var i = 0; i < tabHeaders.length; i++) {
1856    $(tabHeaders[i]).attr("id",i).click(function() {
1857    /*
1858      // make a copy of the page numbers for the search left pane
1859      setTimeout(function() {
1860        // remove any residual page numbers
1861        $('#searchResults .gsc-tabsArea .gsc-cursor-box.gs-bidi-start-align').remove();
1862        // move the page numbers to the left position; make a clone,
1863        // because the element is drawn to the DOM only once
1864        // and because we're going to remove it (previous line),
1865        // we need it to be available to move again as the user navigates
1866        $('#searchResults .gsc-webResult .gsc-cursor-box.gs-bidi-start-align:visible')
1867                        .clone().appendTo('#searchResults .gsc-tabsArea');
1868        }, 200);
1869      */
1870    });
1871  }
1872  setTimeout(function(){$(tabHeaders[0]).click()},200);
1873}
1874
1875
1876function getQuery(hash) {
1877  var queryParts = hash.split('=');
1878  return queryParts[1];
1879}
1880
1881/* returns the given string with all HTML brackets converted to entities
1882    TODO: move this to the site's JS library */
1883function escapeHTML(string) {
1884  return string.replace(/</g,"&lt;")
1885                .replace(/>/g,"&gt;");
1886}
1887
1888
1889
1890
1891
1892
1893
1894/* ######################################################## */
1895/* #################  JAVADOC REFERENCE ################### */
1896/* ######################################################## */
1897
1898/* Initialize some droiddoc stuff, but only if we're in the reference */
1899if (location.pathname.indexOf("/reference")) {
1900  if(!location.pathname.indexOf("/reference-gms/packages.html")
1901    && !location.pathname.indexOf("/reference-gcm/packages.html")
1902    && !location.pathname.indexOf("/reference/com/google") == 0) {
1903    $(document).ready(function() {
1904      // init available apis based on user pref
1905      changeApiLevel();
1906      initSidenavHeightResize()
1907      });
1908  }
1909}
1910
1911var API_LEVEL_COOKIE = "api_level";
1912var minLevel = 1;
1913var maxLevel = 1;
1914
1915/******* SIDENAV DIMENSIONS ************/
1916
1917  function initSidenavHeightResize() {
1918    // Change the drag bar size to nicely fit the scrollbar positions
1919    var $dragBar = $(".ui-resizable-s");
1920    $dragBar.css({'width': $dragBar.parent().width() - 5 + "px"});
1921
1922    $( "#resize-packages-nav" ).resizable({
1923      containment: "#nav-panels",
1924      handles: "s",
1925      alsoResize: "#packages-nav",
1926      resize: function(event, ui) { resizeNav(); }, /* resize the nav while dragging */
1927      stop: function(event, ui) { saveNavPanels(); } /* once stopped, save the sizes to cookie  */
1928      });
1929
1930  }
1931
1932function updateSidenavFixedWidth() {
1933  if (!navBarIsFixed) return;
1934  $('#devdoc-nav').css({
1935    'width' : $('#side-nav').css('width'),
1936    'margin' : $('#side-nav').css('margin')
1937  });
1938  $('#devdoc-nav a.totop').css({'display':'block','width':$("#nav").innerWidth()+'px'});
1939
1940  initSidenavHeightResize();
1941}
1942
1943function updateSidenavFullscreenWidth() {
1944  if (!navBarIsFixed) return;
1945  $('#devdoc-nav').css({
1946    'width' : $('#side-nav').css('width'),
1947    'margin' : $('#side-nav').css('margin')
1948  });
1949  $('#devdoc-nav .totop').css({'left': 'inherit'});
1950
1951  initSidenavHeightResize();
1952}
1953
1954function buildApiLevelSelector() {
1955  maxLevel = SINCE_DATA.length;
1956  var userApiLevel = parseInt(readCookie(API_LEVEL_COOKIE));
1957  userApiLevel = userApiLevel == 0 ? maxLevel : userApiLevel; // If there's no cookie (zero), use the max by default
1958
1959  minLevel = parseInt($("#doc-api-level").attr("class"));
1960  // Handle provisional api levels; the provisional level will always be the highest possible level
1961  // Provisional api levels will also have a length; other stuff that's just missing a level won't,
1962  // so leave those kinds of entities at the default level of 1 (for example, the R.styleable class)
1963  if (isNaN(minLevel) && minLevel.length) {
1964    minLevel = maxLevel;
1965  }
1966  var select = $("#apiLevelSelector").html("").change(changeApiLevel);
1967  for (var i = maxLevel-1; i >= 0; i--) {
1968    var option = $("<option />").attr("value",""+SINCE_DATA[i]).append(""+SINCE_DATA[i]);
1969  //  if (SINCE_DATA[i] < minLevel) option.addClass("absent"); // always false for strings (codenames)
1970    select.append(option);
1971  }
1972
1973  // get the DOM element and use setAttribute cuz IE6 fails when using jquery .attr('selected',true)
1974  var selectedLevelItem = $("#apiLevelSelector option[value='"+userApiLevel+"']").get(0);
1975  selectedLevelItem.setAttribute('selected',true);
1976}
1977
1978function changeApiLevel() {
1979  maxLevel = SINCE_DATA.length;
1980  var selectedLevel = maxLevel;
1981
1982  selectedLevel = parseInt($("#apiLevelSelector option:selected").val());
1983  toggleVisisbleApis(selectedLevel, "body");
1984
1985  var date = new Date();
1986  date.setTime(date.getTime()+(10*365*24*60*60*1000)); // keep this for 10 years
1987  var expiration = date.toGMTString();
1988  writeCookie(API_LEVEL_COOKIE, selectedLevel, null, expiration);
1989
1990  if (selectedLevel < minLevel) {
1991    var thing = ($("#jd-header").html().indexOf("package") != -1) ? "package" : "class";
1992    $("#naMessage").show().html("<div><p><strong>This " + thing
1993              + " requires API level " + minLevel + " or higher.</strong></p>"
1994              + "<p>This document is hidden because your selected API level for the documentation is "
1995              + selectedLevel + ". You can change the documentation API level with the selector "
1996              + "above the left navigation.</p>"
1997              + "<p>For more information about specifying the API level your app requires, "
1998              + "read <a href='" + toRoot + "training/basics/supporting-devices/platforms.html'"
1999              + ">Supporting Different Platform Versions</a>.</p>"
2000              + "<input type='button' value='OK, make this page visible' "
2001              + "title='Change the API level to " + minLevel + "' "
2002              + "onclick='$(\"#apiLevelSelector\").val(\"" + minLevel + "\");changeApiLevel();' />"
2003              + "</div>");
2004  } else {
2005    $("#naMessage").hide();
2006  }
2007}
2008
2009function toggleVisisbleApis(selectedLevel, context) {
2010  var apis = $(".api",context);
2011  apis.each(function(i) {
2012    var obj = $(this);
2013    var className = obj.attr("class");
2014    var apiLevelIndex = className.lastIndexOf("-")+1;
2015    var apiLevelEndIndex = className.indexOf(" ", apiLevelIndex);
2016    apiLevelEndIndex = apiLevelEndIndex != -1 ? apiLevelEndIndex : className.length;
2017    var apiLevel = className.substring(apiLevelIndex, apiLevelEndIndex);
2018    if (apiLevel.length == 0) { // for odd cases when the since data is actually missing, just bail
2019      return;
2020    }
2021    apiLevel = parseInt(apiLevel);
2022
2023    // Handle provisional api levels; if this item's level is the provisional one, set it to the max
2024    var selectedLevelNum = parseInt(selectedLevel)
2025    var apiLevelNum = parseInt(apiLevel);
2026    if (isNaN(apiLevelNum)) {
2027        apiLevelNum = maxLevel;
2028    }
2029
2030    // Grey things out that aren't available and give a tooltip title
2031    if (apiLevelNum > selectedLevelNum) {
2032      obj.addClass("absent").attr("title","Requires API Level \""
2033            + apiLevel + "\" or higher");
2034    }
2035    else obj.removeClass("absent").removeAttr("title");
2036  });
2037}
2038
2039
2040
2041
2042/* #################  SIDENAV TREE VIEW ################### */
2043
2044function new_node(me, mom, text, link, children_data, api_level)
2045{
2046  var node = new Object();
2047  node.children = Array();
2048  node.children_data = children_data;
2049  node.depth = mom.depth + 1;
2050
2051  node.li = document.createElement("li");
2052  mom.get_children_ul().appendChild(node.li);
2053
2054  node.label_div = document.createElement("div");
2055  node.label_div.className = "label";
2056  if (api_level != null) {
2057    $(node.label_div).addClass("api");
2058    $(node.label_div).addClass("api-level-"+api_level);
2059  }
2060  node.li.appendChild(node.label_div);
2061
2062  if (children_data != null) {
2063    node.expand_toggle = document.createElement("a");
2064    node.expand_toggle.href = "javascript:void(0)";
2065    node.expand_toggle.onclick = function() {
2066          if (node.expanded) {
2067            $(node.get_children_ul()).slideUp("fast");
2068            node.plus_img.src = me.toroot + "assets/images/triangle-closed-small.png";
2069            node.expanded = false;
2070          } else {
2071            expand_node(me, node);
2072          }
2073       };
2074    node.label_div.appendChild(node.expand_toggle);
2075
2076    node.plus_img = document.createElement("img");
2077    node.plus_img.src = me.toroot + "assets/images/triangle-closed-small.png";
2078    node.plus_img.className = "plus";
2079    node.plus_img.width = "8";
2080    node.plus_img.border = "0";
2081    node.expand_toggle.appendChild(node.plus_img);
2082
2083    node.expanded = false;
2084  }
2085
2086  var a = document.createElement("a");
2087  node.label_div.appendChild(a);
2088  node.label = document.createTextNode(text);
2089  a.appendChild(node.label);
2090  if (link) {
2091    a.href = me.toroot + link;
2092  } else {
2093    if (children_data != null) {
2094      a.className = "nolink";
2095      a.href = "javascript:void(0)";
2096      a.onclick = node.expand_toggle.onclick;
2097      // This next line shouldn't be necessary.  I'll buy a beer for the first
2098      // person who figures out how to remove this line and have the link
2099      // toggle shut on the first try. --joeo@android.com
2100      node.expanded = false;
2101    }
2102  }
2103
2104
2105  node.children_ul = null;
2106  node.get_children_ul = function() {
2107      if (!node.children_ul) {
2108        node.children_ul = document.createElement("ul");
2109        node.children_ul.className = "children_ul";
2110        node.children_ul.style.display = "none";
2111        node.li.appendChild(node.children_ul);
2112      }
2113      return node.children_ul;
2114    };
2115
2116  return node;
2117}
2118
2119
2120
2121
2122function expand_node(me, node)
2123{
2124  if (node.children_data && !node.expanded) {
2125    if (node.children_visited) {
2126      $(node.get_children_ul()).slideDown("fast");
2127    } else {
2128      get_node(me, node);
2129      if ($(node.label_div).hasClass("absent")) {
2130        $(node.get_children_ul()).addClass("absent");
2131      }
2132      $(node.get_children_ul()).slideDown("fast");
2133    }
2134    node.plus_img.src = me.toroot + "assets/images/triangle-opened-small.png";
2135    node.expanded = true;
2136
2137    // perform api level toggling because new nodes are new to the DOM
2138    var selectedLevel = $("#apiLevelSelector option:selected").val();
2139    toggleVisisbleApis(selectedLevel, "#side-nav");
2140  }
2141}
2142
2143function get_node(me, mom)
2144{
2145  mom.children_visited = true;
2146  for (var i in mom.children_data) {
2147    var node_data = mom.children_data[i];
2148    mom.children[i] = new_node(me, mom, node_data[0], node_data[1],
2149        node_data[2], node_data[3]);
2150  }
2151}
2152
2153function this_page_relative(toroot)
2154{
2155  var full = document.location.pathname;
2156  var file = "";
2157  if (toroot.substr(0, 1) == "/") {
2158    if (full.substr(0, toroot.length) == toroot) {
2159      return full.substr(toroot.length);
2160    } else {
2161      // the file isn't under toroot.  Fail.
2162      return null;
2163    }
2164  } else {
2165    if (toroot != "./") {
2166      toroot = "./" + toroot;
2167    }
2168    do {
2169      if (toroot.substr(toroot.length-3, 3) == "../" || toroot == "./") {
2170        var pos = full.lastIndexOf("/");
2171        file = full.substr(pos) + file;
2172        full = full.substr(0, pos);
2173        toroot = toroot.substr(0, toroot.length-3);
2174      }
2175    } while (toroot != "" && toroot != "/");
2176    return file.substr(1);
2177  }
2178}
2179
2180function find_page(url, data)
2181{
2182  var nodes = data;
2183  var result = null;
2184  for (var i in nodes) {
2185    var d = nodes[i];
2186    if (d[1] == url) {
2187      return new Array(i);
2188    }
2189    else if (d[2] != null) {
2190      result = find_page(url, d[2]);
2191      if (result != null) {
2192        return (new Array(i).concat(result));
2193      }
2194    }
2195  }
2196  return null;
2197}
2198
2199function init_default_navtree(toroot) {
2200  init_navtree("tree-list", toroot, NAVTREE_DATA);
2201
2202  // perform api level toggling because because the whole tree is new to the DOM
2203  var selectedLevel = $("#apiLevelSelector option:selected").val();
2204  toggleVisisbleApis(selectedLevel, "#side-nav");
2205}
2206
2207function init_navtree(navtree_id, toroot, root_nodes)
2208{
2209  var me = new Object();
2210  me.toroot = toroot;
2211  me.node = new Object();
2212
2213  me.node.li = document.getElementById(navtree_id);
2214  me.node.children_data = root_nodes;
2215  me.node.children = new Array();
2216  me.node.children_ul = document.createElement("ul");
2217  me.node.get_children_ul = function() { return me.node.children_ul; };
2218  //me.node.children_ul.className = "children_ul";
2219  me.node.li.appendChild(me.node.children_ul);
2220  me.node.depth = 0;
2221
2222  get_node(me, me.node);
2223
2224  me.this_page = this_page_relative(toroot);
2225  me.breadcrumbs = find_page(me.this_page, root_nodes);
2226  if (me.breadcrumbs != null && me.breadcrumbs.length != 0) {
2227    var mom = me.node;
2228    for (var i in me.breadcrumbs) {
2229      var j = me.breadcrumbs[i];
2230      mom = mom.children[j];
2231      expand_node(me, mom);
2232    }
2233    mom.label_div.className = mom.label_div.className + " selected";
2234    addLoadEvent(function() {
2235      scrollIntoView("nav-tree");
2236      });
2237  }
2238}
2239
2240/* TODO: eliminate redundancy with non-google functions */
2241function init_google_navtree(navtree_id, toroot, root_nodes)
2242{
2243  var me = new Object();
2244  me.toroot = toroot;
2245  me.node = new Object();
2246
2247  me.node.li = document.getElementById(navtree_id);
2248  me.node.children_data = root_nodes;
2249  me.node.children = new Array();
2250  me.node.children_ul = document.createElement("ul");
2251  me.node.get_children_ul = function() { return me.node.children_ul; };
2252  //me.node.children_ul.className = "children_ul";
2253  me.node.li.appendChild(me.node.children_ul);
2254  me.node.depth = 0;
2255
2256  get_google_node(me, me.node);
2257
2258}
2259
2260function new_google_node(me, mom, text, link, children_data, api_level)
2261{
2262  var node = new Object();
2263  var child;
2264  node.children = Array();
2265  node.children_data = children_data;
2266  node.depth = mom.depth + 1;
2267  node.get_children_ul = function() {
2268      if (!node.children_ul) {
2269        node.children_ul = document.createElement("ul");
2270        node.children_ul.className = "tree-list-children";
2271        node.li.appendChild(node.children_ul);
2272      }
2273      return node.children_ul;
2274    };
2275  node.li = document.createElement("li");
2276
2277  mom.get_children_ul().appendChild(node.li);
2278
2279
2280  if(link) {
2281    child = document.createElement("a");
2282
2283  }
2284  else {
2285    child = document.createElement("span");
2286    child.className = "tree-list-subtitle";
2287
2288  }
2289  if (children_data != null) {
2290    node.li.className="nav-section";
2291    node.label_div = document.createElement("div");
2292    node.label_div.className = "nav-section-header-ref";
2293    node.li.appendChild(node.label_div);
2294    get_google_node(me, node);
2295    node.label_div.appendChild(child);
2296  }
2297  else {
2298    node.li.appendChild(child);
2299  }
2300  if(link) {
2301    child.href = me.toroot + link;
2302  }
2303  node.label = document.createTextNode(text);
2304  child.appendChild(node.label);
2305
2306  node.children_ul = null;
2307
2308  return node;
2309}
2310
2311function get_google_node(me, mom)
2312{
2313  mom.children_visited = true;
2314  var linkText;
2315  for (var i in mom.children_data) {
2316    var node_data = mom.children_data[i];
2317    linkText = node_data[0];
2318
2319    if(linkText.match("^"+"com.google.android")=="com.google.android"){
2320      linkText = linkText.substr(19, linkText.length);
2321    }
2322      mom.children[i] = new_google_node(me, mom, linkText, node_data[1],
2323          node_data[2], node_data[3]);
2324  }
2325}
2326function showGoogleRefTree() {
2327  init_default_google_navtree(toRoot);
2328  init_default_gcm_navtree(toRoot);
2329  resizeNav();
2330}
2331
2332function init_default_google_navtree(toroot) {
2333  init_google_navtree("gms-tree-list", toroot, GMS_NAVTREE_DATA);
2334}
2335
2336function init_default_gcm_navtree(toroot) {
2337  init_google_navtree("gcm-tree-list", toroot, GCM_NAVTREE_DATA);
2338}
2339
2340/* TOGGLE INHERITED MEMBERS */
2341
2342/* Toggle an inherited class (arrow toggle)
2343 * @param linkObj  The link that was clicked.
2344 * @param expand  'true' to ensure it's expanded. 'false' to ensure it's closed.
2345 *                'null' to simply toggle.
2346 */
2347function toggleInherited(linkObj, expand) {
2348    var base = linkObj.getAttribute("id");
2349    var list = document.getElementById(base + "-list");
2350    var summary = document.getElementById(base + "-summary");
2351    var trigger = document.getElementById(base + "-trigger");
2352    var a = $(linkObj);
2353    if ( (expand == null && a.hasClass("closed")) || expand ) {
2354        list.style.display = "none";
2355        summary.style.display = "block";
2356        trigger.src = toRoot + "assets/images/triangle-opened.png";
2357        a.removeClass("closed");
2358        a.addClass("opened");
2359    } else if ( (expand == null && a.hasClass("opened")) || (expand == false) ) {
2360        list.style.display = "block";
2361        summary.style.display = "none";
2362        trigger.src = toRoot + "assets/images/triangle-closed.png";
2363        a.removeClass("opened");
2364        a.addClass("closed");
2365    }
2366    return false;
2367}
2368
2369/* Toggle all inherited classes in a single table (e.g. all inherited methods)
2370 * @param linkObj  The link that was clicked.
2371 * @param expand  'true' to ensure it's expanded. 'false' to ensure it's closed.
2372 *                'null' to simply toggle.
2373 */
2374function toggleAllInherited(linkObj, expand) {
2375  var a = $(linkObj);
2376  var table = $(a.parent().parent().parent()); // ugly way to get table/tbody
2377  var expandos = $(".jd-expando-trigger", table);
2378  if ( (expand == null && a.text() == "[Expand]") || expand ) {
2379    expandos.each(function(i) {
2380      toggleInherited(this, true);
2381    });
2382    a.text("[Collapse]");
2383  } else if ( (expand == null && a.text() == "[Collapse]") || (expand == false) ) {
2384    expandos.each(function(i) {
2385      toggleInherited(this, false);
2386    });
2387    a.text("[Expand]");
2388  }
2389  return false;
2390}
2391
2392/* Toggle all inherited members in the class (link in the class title)
2393 */
2394function toggleAllClassInherited() {
2395  var a = $("#toggleAllClassInherited"); // get toggle link from class title
2396  var toggles = $(".toggle-all", $("#body-content"));
2397  if (a.text() == "[Expand All]") {
2398    toggles.each(function(i) {
2399      toggleAllInherited(this, true);
2400    });
2401    a.text("[Collapse All]");
2402  } else {
2403    toggles.each(function(i) {
2404      toggleAllInherited(this, false);
2405    });
2406    a.text("[Expand All]");
2407  }
2408  return false;
2409}
2410
2411/* Expand all inherited members in the class. Used when initiating page search */
2412function ensureAllInheritedExpanded() {
2413  var toggles = $(".toggle-all", $("#body-content"));
2414  toggles.each(function(i) {
2415    toggleAllInherited(this, true);
2416  });
2417  $("#toggleAllClassInherited").text("[Collapse All]");
2418}
2419
2420
2421/* HANDLE KEY EVENTS
2422 * - Listen for Ctrl+F (Cmd on Mac) and expand all inherited members (to aid page search)
2423 */
2424var agent = navigator['userAgent'].toLowerCase();
2425var mac = agent.indexOf("macintosh") != -1;
2426
2427$(document).keydown( function(e) {
2428var control = mac ? e.metaKey && !e.ctrlKey : e.ctrlKey; // get ctrl key
2429  if (control && e.which == 70) {  // 70 is "F"
2430    ensureAllInheritedExpanded();
2431  }
2432});
2433