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