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