OSDN Git Service

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