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