OSDN Git Service

am de295276: add javascript action events to search suggestions and search results...
[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 var GOOGLE_DATA; // combined data for google service apis, used for search suggest
13   
14
15 /******  ON LOAD SET UP STUFF *********/
16
17 var 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 :
220 false; // 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
592 function 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
618 function setNavBarLeftPos() {
619   navBarLeftPos = $('#body-content').offset().left;
620 }
621
622
623 function 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
637 function 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
649 var agent = navigator['userAgent'].toLowerCase();
650 // If a mobile phone, set flag and do mobile setup
651 if ((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
659 addLoadEvent( function() {
660   $("pre:not(.no-pretty-print)").addClass("prettyprint");
661   prettyPrint();
662 } );
663
664
665
666
667 /* ######### RESIZE THE SIDENAV HEIGHT ########## */
668
669 function 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
735 var updateScrollbars = false;
736 var 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  */
743 function 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. */
764 function 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 */
776 function 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
784 function 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. */
802 function 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 */
826 function 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
839 function 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
856 function 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
897 REMEMBER THE PREVIOUS PAGE FOR EACH TAB
898
899 function 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
940 function 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
964 function 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
1006 function getNavPref() {
1007   var v = readCookie('reference_nav');
1008   if (v != NAV_PREF_TREE) {
1009     v = NAV_PREF_PANELS;
1010   }
1011   return v;
1012 }
1013
1014 function 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
1024 function 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
1062 function 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
1075 function 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
1089 function 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
1100 function 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
1122 function loadLangPref() {
1123   var lang = readCookie("pref_lang");
1124   if (lang != 0) {
1125     $("#language").find("option[value='"+lang+"']").attr("selected",true);
1126   }
1127 }
1128
1129 function 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 */
1146 function 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 */
1168 function 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
1178 function 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
1502 var gSelectedIndex = -1;
1503 var gMatches = new Array();
1504 var gLastText = "";
1505 var gInitialized = false;
1506 var ROW_COUNT_FRAMEWORK = 20;       // max number of results in list
1507 var gListLength = 0;
1508
1509
1510 var gGoogleMatches = new Array();
1511 var ROW_COUNT_GOOGLE = 15;          // max number of results in list
1512 var gGoogleListLength = 0;
1513
1514 function 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
1520 function 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
1529 function 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
1536 function 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
1554 function 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
1602 function 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 */
1707 function 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 */
1778 function 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
1797 function 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
1807 function 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
1816 function 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
1834 google.load('search', '1');
1835 var searchControl;
1836
1837 function 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
1920 google.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
1954 function 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
1964 function 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
1987 function 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
1998 function 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 */
2005 function 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 */
2021 if (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
2033 var API_LEVEL_COOKIE = "api_level";
2034 var minLevel = 1;
2035 var 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   
2054 function 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
2065 function 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
2076 function 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
2100 function 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
2131 function 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
2166 function 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
2244 function 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
2265 function 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
2275 function 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
2302 function 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
2321 function 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
2329 function 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 */
2363 function 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
2382 function 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
2433 function 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 }
2448 function showGoogleRefTree() {
2449   init_default_google_navtree(toRoot);
2450   init_default_gcm_navtree(toRoot);
2451   resizeNav();
2452 }
2453
2454 function init_default_google_navtree(toroot) {
2455   init_google_navtree("gms-tree-list", toroot, GMS_NAVTREE_DATA);
2456 }
2457
2458 function 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  */
2469 function 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  */
2496 function 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  */
2516 function 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 */
2534 function 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  */
2546 var agent = navigator['userAgent'].toLowerCase();
2547 var mac = agent.indexOf("macintosh") != -1;
2548
2549 $(document).keydown( function(e) {
2550 var control = mac ? e.metaKey && !e.ctrlKey : e.ctrlKey; // get ctrl key
2551   if (control && e.which == 70) {  // 70 is "F"
2552     ensureAllInheritedExpanded();
2553   }
2554 });