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