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 var mPagePath; // initialized in ready() function
     10 
     11 var basePath = getBaseUri(location.pathname);
     12 var SITE_ROOT = toRoot + basePath.substring(1,basePath.indexOf("/",1));
     13 var GOOGLE_DATA; // combined data for google service apis, used for search suggest
     14 
     15 // Ensure that all ajax getScript() requests allow caching
     16 $.ajaxSetup({
     17   cache: true
     18 });
     19 
     20 /******  ON LOAD SET UP STUFF *********/
     21 
     22 $(document).ready(function() {
     23 
     24   // show lang dialog if the URL includes /intl/
     25   //if (location.pathname.substring(0,6) == "/intl/") {
     26   //  var lang = location.pathname.split('/')[2];
     27    // if (lang != getLangPref()) {
     28    //   $("#langMessage a.yes").attr("onclick","changeLangPref('" + lang
     29    //       + "', true); $('#langMessage').hide(); return false;");
     30   //    $("#langMessage .lang." + lang).show();
     31    //   $("#langMessage").show();
     32    // }
     33   //}
     34 
     35   // load json file for JD doc search suggestions
     36   $.getScript(toRoot + 'jd_lists_unified.js');
     37   // load json file for Android API search suggestions
     38   $.getScript(toRoot + 'reference/lists.js');
     39   // load json files for Google services API suggestions
     40   $.getScript(toRoot + 'reference/gcm_lists.js', function(data, textStatus, jqxhr) {
     41       // once the GCM json (GCM_DATA) is loaded, load the GMS json (GMS_DATA) and merge the data
     42       if(jqxhr.status === 200) {
     43           $.getScript(toRoot + 'reference/gms_lists.js', function(data, textStatus, jqxhr) {
     44               if(jqxhr.status === 200) {
     45                   // combine GCM and GMS data
     46                   GOOGLE_DATA = GMS_DATA;
     47                   var start = GOOGLE_DATA.length;
     48                   for (var i=0; i<GCM_DATA.length; i++) {
     49                       GOOGLE_DATA.push({id:start+i, label:GCM_DATA[i].label,
     50                               link:GCM_DATA[i].link, type:GCM_DATA[i].type});
     51                   }
     52               }
     53           });
     54       }
     55   });
     56 
     57   // setup keyboard listener for search shortcut
     58   $('body').keyup(function(event) {
     59     if (event.which == 191) {
     60       $('#search_autocomplete').focus();
     61     }
     62   });
     63 
     64   // init the fullscreen toggle click event
     65   $('#nav-swap .fullscreen').click(function(){
     66     if ($(this).hasClass('disabled')) {
     67       toggleFullscreen(true);
     68     } else {
     69       toggleFullscreen(false);
     70     }
     71   });
     72 
     73   // initialize the divs with custom scrollbars
     74   $('.scroll-pane').jScrollPane( {verticalGutter:0} );
     75 
     76   // add HRs below all H2s (except for a few other h2 variants)
     77   $('h2').not('#qv h2')
     78          .not('#tb h2')
     79          .not('.sidebox h2')
     80          .not('#devdoc-nav h2')
     81          .not('h2.norule').css({marginBottom:0})
     82          .after('<hr/>');
     83 
     84   // set up the search close button
     85   $('.search .close').click(function() {
     86     $searchInput = $('#search_autocomplete');
     87     $searchInput.attr('value', '');
     88     $(this).addClass("hide");
     89     $("#search-container").removeClass('active');
     90     $("#search_autocomplete").blur();
     91     search_focus_changed($searchInput.get(), false);
     92     hideResults();
     93   });
     94 
     95   // Set up quicknav
     96   var quicknav_open = false;
     97   $("#btn-quicknav").click(function() {
     98     if (quicknav_open) {
     99       $(this).removeClass('active');
    100       quicknav_open = false;
    101       collapse();
    102     } else {
    103       $(this).addClass('active');
    104       quicknav_open = true;
    105       expand();
    106     }
    107   })
    108 
    109   var expand = function() {
    110    $('#header-wrap').addClass('quicknav');
    111    $('#quicknav').stop().show().animate({opacity:'1'});
    112   }
    113 
    114   var collapse = function() {
    115     $('#quicknav').stop().animate({opacity:'0'}, 100, function() {
    116       $(this).hide();
    117       $('#header-wrap').removeClass('quicknav');
    118     });
    119   }
    120 
    121 
    122   //Set up search
    123   $("#search_autocomplete").focus(function() {
    124     $("#search-container").addClass('active');
    125   })
    126   $("#search-container").mouseover(function() {
    127     $("#search-container").addClass('active');
    128     $("#search_autocomplete").focus();
    129   })
    130   $("#search-container").mouseout(function() {
    131     if ($("#search_autocomplete").is(":focus")) return;
    132     if ($("#search_autocomplete").val() == '') {
    133       setTimeout(function(){
    134         $("#search-container").removeClass('active');
    135         $("#search_autocomplete").blur();
    136       },250);
    137     }
    138   })
    139   $("#search_autocomplete").blur(function() {
    140     if ($("#search_autocomplete").val() == '') {
    141       $("#search-container").removeClass('active');
    142     }
    143   })
    144 
    145 
    146   // prep nav expandos
    147   var pagePath = document.location.pathname;
    148   // account for intl docs by removing the intl/*/ path
    149   if (pagePath.indexOf("/intl/") == 0) {
    150     pagePath = pagePath.substr(pagePath.indexOf("/",6)); // start after intl/ to get last /
    151   }
    152 
    153   if (pagePath.indexOf(SITE_ROOT) == 0) {
    154     if (pagePath == '' || pagePath.charAt(pagePath.length - 1) == '/') {
    155       pagePath += 'index.html';
    156     }
    157   }
    158 
    159   // Need a copy of the pagePath before it gets changed in the next block;
    160   // it's needed to perform proper tab highlighting in offline docs (see rootDir below)
    161   var pagePathOriginal = pagePath;
    162   if (SITE_ROOT.match(/\.\.\//) || SITE_ROOT == '') {
    163     // If running locally, SITE_ROOT will be a relative path, so account for that by
    164     // finding the relative URL to this page. This will allow us to find links on the page
    165     // leading back to this page.
    166     var pathParts = pagePath.split('/');
    167     var relativePagePathParts = [];
    168     var upDirs = (SITE_ROOT.match(/(\.\.\/)+/) || [''])[0].length / 3;
    169     for (var i = 0; i < upDirs; i++) {
    170       relativePagePathParts.push('..');
    171     }
    172     for (var i = 0; i < upDirs; i++) {
    173       relativePagePathParts.push(pathParts[pathParts.length - (upDirs - i) - 1]);
    174     }
    175     relativePagePathParts.push(pathParts[pathParts.length - 1]);
    176     pagePath = relativePagePathParts.join('/');
    177   } else {
    178     // Otherwise the page path is already an absolute URL
    179   }
    180 
    181   // Highlight the header tabs...
    182   // highlight Design tab
    183   if ($("body").hasClass("design")) {
    184     $("#header li.design a").addClass("selected");
    185     $("#sticky-header").addClass("design");
    186 
    187   // highlight About tabs
    188   } else if ($("body").hasClass("about")) {
    189     var rootDir = pagePathOriginal.substring(1,pagePathOriginal.indexOf('/', 1));
    190     if (rootDir == "about") {
    191       $("#nav-x li.about a").addClass("selected");
    192     } else if (rootDir == "wear") {
    193       $("#nav-x li.wear a").addClass("selected");
    194     } else if (rootDir == "tv") {
    195       $("#nav-x li.tv a").addClass("selected");
    196     } else if (rootDir == "auto") {
    197       $("#nav-x li.auto a").addClass("selected");
    198     }
    199   // highlight Develop tab
    200   } else if ($("body").hasClass("develop") || $("body").hasClass("google")) {
    201     $("#header li.develop a").addClass("selected");
    202     $("#sticky-header").addClass("develop");
    203     // In Develop docs, also highlight appropriate sub-tab
    204     var rootDir = pagePathOriginal.substring(1,pagePathOriginal.indexOf('/', 1));
    205     if (rootDir == "training") {
    206       $("#nav-x li.training a").addClass("selected");
    207     } else if (rootDir == "guide") {
    208       $("#nav-x li.guide a").addClass("selected");
    209     } else if (rootDir == "reference") {
    210       // If the root is reference, but page is also part of Google Services, select Google
    211       if ($("body").hasClass("google")) {
    212         $("#nav-x li.google a").addClass("selected");
    213       } else {
    214         $("#nav-x li.reference a").addClass("selected");
    215       }
    216     } else if ((rootDir == "tools") || (rootDir == "sdk")) {
    217       $("#nav-x li.tools a").addClass("selected");
    218     } else if ($("body").hasClass("google")) {
    219       $("#nav-x li.google a").addClass("selected");
    220     } else if ($("body").hasClass("samples")) {
    221       $("#nav-x li.samples a").addClass("selected");
    222     } else if (rootDir == "ndk") {
    223       if ($("body").hasClass("guide")) {
    224         $("#nav-x li.guide a").addClass("selected");
    225       } else if ($("body").hasClass("samples")) {
    226         $("#nav-x li.samples a").addClass("selected");
    227       } else if ($("body").hasClass("downloads")) {
    228         $("#nav-x li.downloads a").addClass("selected");
    229       } else if ($("body").hasClass("reference")) {
    230         $("#nav-x li.reference a").addClass("selected");
    231       }
    232     }
    233 
    234   // highlight Distribute tab
    235   } else if ($("body").hasClass("distribute")) {
    236     $("#header li.distribute a").addClass("selected");
    237     $("#sticky-header").addClass("distribute");
    238 
    239     var baseFrag = pagePathOriginal.indexOf('/', 1) + 1;
    240     var secondFrag = pagePathOriginal.substring(baseFrag, pagePathOriginal.indexOf('/', baseFrag));
    241     if (secondFrag == "users") {
    242       $("#nav-x li.users a").addClass("selected");
    243     } else if (secondFrag == "engage") {
    244       $("#nav-x li.engage a").addClass("selected");
    245     } else if (secondFrag == "monetize") {
    246       $("#nav-x li.monetize a").addClass("selected");
    247     } else if (secondFrag == "analyze") {
    248       $("#nav-x li.analyze a").addClass("selected");
    249     } else if (secondFrag == "tools") {
    250       $("#nav-x li.disttools a").addClass("selected");
    251     } else if (secondFrag == "stories") {
    252       $("#nav-x li.stories a").addClass("selected");
    253     } else if (secondFrag == "essentials") {
    254       $("#nav-x li.essentials a").addClass("selected");
    255     } else if (secondFrag == "googleplay") {
    256       $("#nav-x li.googleplay a").addClass("selected");
    257     }
    258   } else if ($("body").hasClass("about")) {
    259     $("#sticky-header").addClass("about");
    260   }
    261 
    262   // set global variable so we can highlight the sidenav a bit later (such as for google reference)
    263   // and highlight the sidenav
    264   mPagePath = pagePath;
    265   highlightSidenav();
    266   buildBreadcrumbs();
    267 
    268   // set up prev/next links if they exist
    269   var $selNavLink = $('#nav').find('a[href="' + pagePath + '"]');
    270   var $selListItem;
    271   if ($selNavLink.length) {
    272     $selListItem = $selNavLink.closest('li');
    273 
    274     // set up prev links
    275     var $prevLink = [];
    276     var $prevListItem = $selListItem.prev('li');
    277 
    278     var crossBoundaries = ($("body.design").length > 0) || ($("body.guide").length > 0) ? true :
    279 false; // navigate across topic boundaries only in design docs
    280     if ($prevListItem.length) {
    281       if ($prevListItem.hasClass('nav-section') || crossBoundaries) {
    282         // jump to last topic of previous section
    283         $prevLink = $prevListItem.find('a:last');
    284       } else if (!$selListItem.hasClass('nav-section')) {
    285         // jump to previous topic in this section
    286         $prevLink = $prevListItem.find('a:eq(0)');
    287       }
    288     } else {
    289       // jump to this section's index page (if it exists)
    290       var $parentListItem = $selListItem.parents('li');
    291       $prevLink = $selListItem.parents('li').find('a');
    292 
    293       // except if cross boundaries aren't allowed, and we're at the top of a section already
    294       // (and there's another parent)
    295       if (!crossBoundaries && $parentListItem.hasClass('nav-section')
    296                            && $selListItem.hasClass('nav-section')) {
    297         $prevLink = [];
    298       }
    299     }
    300 
    301     // set up next links
    302     var $nextLink = [];
    303     var startClass = false;
    304     var isCrossingBoundary = false;
    305 
    306     if ($selListItem.hasClass('nav-section') && $selListItem.children('div.empty').length == 0) {
    307       // we're on an index page, jump to the first topic
    308       $nextLink = $selListItem.find('ul:eq(0)').find('a:eq(0)');
    309 
    310       // if there aren't any children, go to the next section (required for About pages)
    311       if($nextLink.length == 0) {
    312         $nextLink = $selListItem.next('li').find('a');
    313       } else if ($('.topic-start-link').length) {
    314         // as long as there's a child link and there is a "topic start link" (we're on a landing)
    315         // then set the landing page "start link" text to be the first doc title
    316         $('.topic-start-link').text($nextLink.text().toUpperCase());
    317       }
    318 
    319       // If the selected page has a description, then it's a class or article homepage
    320       if ($selListItem.find('a[description]').length) {
    321         // this means we're on a class landing page
    322         startClass = true;
    323       }
    324     } else {
    325       // jump to the next topic in this section (if it exists)
    326       $nextLink = $selListItem.next('li').find('a:eq(0)');
    327       if ($nextLink.length == 0) {
    328         isCrossingBoundary = true;
    329         // no more topics in this section, jump to the first topic in the next section
    330         $nextLink = $selListItem.parents('li:eq(0)').next('li').find('a:eq(0)');
    331         if (!$nextLink.length) {  // Go up another layer to look for next page (lesson > class > course)
    332           $nextLink = $selListItem.parents('li:eq(1)').next('li.nav-section').find('a:eq(0)');
    333           if ($nextLink.length == 0) {
    334             // if that doesn't work, we're at the end of the list, so disable NEXT link
    335             $('.next-page-link').attr('href','').addClass("disabled")
    336                                 .click(function() { return false; });
    337             // and completely hide the one in the footer
    338             $('.content-footer .next-page-link').hide();
    339           }
    340         }
    341       }
    342     }
    343 
    344     if (startClass) {
    345       $('.start-class-link').attr('href', $nextLink.attr('href')).removeClass("hide");
    346 
    347       // if there's no training bar (below the start button),
    348       // then we need to add a bottom border to button
    349       if (!$("#tb").length) {
    350         $('.start-class-link').css({'border-bottom':'1px solid #DADADA'});
    351       }
    352     } else if (isCrossingBoundary && !$('body.design').length) {  // Design always crosses boundaries
    353       $('.content-footer.next-class').show();
    354       $('.next-page-link').attr('href','')
    355                           .removeClass("hide").addClass("disabled")
    356                           .click(function() { return false; });
    357       // and completely hide the one in the footer
    358       $('.content-footer .next-page-link').hide();
    359       if ($nextLink.length) {
    360         $('.next-class-link').attr('href',$nextLink.attr('href'))
    361                              .removeClass("hide")
    362                              .append(": " + $nextLink.html());
    363         $('.next-class-link').find('.new').empty();
    364       }
    365     } else {
    366       $('.next-page-link').attr('href', $nextLink.attr('href'))
    367                           .removeClass("hide");
    368       // for the footer link, also add the next page title
    369       $('.content-footer .next-page-link').append(": " + $nextLink.html());
    370     }
    371 
    372     if (!startClass && $prevLink.length) {
    373       var prevHref = $prevLink.attr('href');
    374       if (prevHref == SITE_ROOT + 'index.html') {
    375         // Don't show Previous when it leads to the homepage
    376       } else {
    377         $('.prev-page-link').attr('href', $prevLink.attr('href')).removeClass("hide");
    378       }
    379     }
    380 
    381   }
    382 
    383 
    384 
    385   // Set up the course landing pages for Training with class names and descriptions
    386   if ($('body.trainingcourse').length) {
    387     var $classLinks = $selListItem.find('ul li a').not('#nav .nav-section .nav-section ul a');
    388 
    389     // create an array for all the class descriptions
    390     var $classDescriptions = new Array($classLinks.length);
    391     var lang = getLangPref();
    392     $classLinks.each(function(index) {
    393       var langDescr = $(this).attr(lang + "-description");
    394       if (typeof langDescr !== 'undefined' && langDescr !== false) {
    395         // if there's a class description in the selected language, use that
    396         $classDescriptions[index] = langDescr;
    397       } else {
    398         // otherwise, use the default english description
    399         $classDescriptions[index] = $(this).attr("description");
    400       }
    401     });
    402 
    403     var $olClasses  = $('<ol class="class-list"></ol>');
    404     var $liClass;
    405     var $imgIcon;
    406     var $h2Title;
    407     var $pSummary;
    408     var $olLessons;
    409     var $liLesson;
    410     $classLinks.each(function(index) {
    411       $liClass  = $('<li></li>');
    412       $h2Title  = $('<a class="title" href="'+$(this).attr('href')+'"><h2>' + $(this).html()+'</h2><span></span></a>');
    413       $pSummary = $('<p class="description">' + $classDescriptions[index] + '</p>');
    414 
    415       $olLessons  = $('<ol class="lesson-list"></ol>');
    416 
    417       $lessons = $(this).closest('li').find('ul li a');
    418 
    419       if ($lessons.length) {
    420         $imgIcon = $('<img src="'+toRoot+'assets/images/resource-tutorial.png" '
    421             + ' width="64" height="64" alt=""/>');
    422         $lessons.each(function(index) {
    423           $olLessons.append('<li><a href="'+$(this).attr('href')+'">' + $(this).html()+'</a></li>');
    424         });
    425       } else {
    426         $imgIcon = $('<img src="'+toRoot+'assets/images/resource-article.png" '
    427             + ' width="64" height="64" alt=""/>');
    428         $pSummary.addClass('article');
    429       }
    430 
    431       $liClass.append($h2Title).append($imgIcon).append($pSummary).append($olLessons);
    432       $olClasses.append($liClass);
    433     });
    434     $('.jd-descr').append($olClasses);
    435   }
    436 
    437   // Set up expand/collapse behavior
    438   initExpandableNavItems("#nav");
    439 
    440 
    441   $(".scroll-pane").scroll(function(event) {
    442       event.preventDefault();
    443       return false;
    444   });
    445 
    446   /* Resize nav height when window height changes */
    447   $(window).resize(function() {
    448     if ($('#side-nav').length == 0) return;
    449     var stylesheet = $('link[rel="stylesheet"][class="fullscreen"]');
    450     setNavBarLeftPos(); // do this even if sidenav isn't fixed because it could become fixed
    451     // make sidenav behave when resizing the window and side-scolling is a concern
    452     if (sticky) {
    453       if ((stylesheet.attr("disabled") == "disabled") || stylesheet.length == 0) {
    454         updateSideNavPosition();
    455       } else {
    456         updateSidenavFullscreenWidth();
    457       }
    458     }
    459     resizeNav();
    460   });
    461 
    462 
    463   var navBarLeftPos;
    464   if ($('#devdoc-nav').length) {
    465     setNavBarLeftPos();
    466   }
    467 
    468 
    469   // Set up play-on-hover <video> tags.
    470   $('video.play-on-hover').bind('click', function(){
    471     $(this).get(0).load(); // in case the video isn't seekable
    472     $(this).get(0).play();
    473   });
    474 
    475   // Set up tooltips
    476   var TOOLTIP_MARGIN = 10;
    477   $('acronym,.tooltip-link').each(function() {
    478     var $target = $(this);
    479     var $tooltip = $('<div>')
    480         .addClass('tooltip-box')
    481         .append($target.attr('title'))
    482         .hide()
    483         .appendTo('body');
    484     $target.removeAttr('title');
    485 
    486     $target.hover(function() {
    487       // in
    488       var targetRect = $target.offset();
    489       targetRect.width = $target.width();
    490       targetRect.height = $target.height();
    491 
    492       $tooltip.css({
    493         left: targetRect.left,
    494         top: targetRect.top + targetRect.height + TOOLTIP_MARGIN
    495       });
    496       $tooltip.addClass('below');
    497       $tooltip.show();
    498     }, function() {
    499       // out
    500       $tooltip.hide();
    501     });
    502   });
    503 
    504   // Set up <h2> deeplinks
    505   $('h2').click(function() {
    506     var id = $(this).attr('id');
    507     if (id) {
    508       document.location.hash = id;
    509     }
    510   });
    511 
    512   //Loads the +1 button
    513   var po = document.createElement('script'); po.type = 'text/javascript'; po.async = true;
    514   po.src = 'https://apis.google.com/js/plusone.js';
    515   var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(po, s);
    516 
    517 
    518   // Revise the sidenav widths to make room for the scrollbar
    519   // which avoids the visible width from changing each time the bar appears
    520   var $sidenav = $("#side-nav");
    521   var sidenav_width = parseInt($sidenav.innerWidth());
    522 
    523   $("#devdoc-nav  #nav").css("width", sidenav_width - 4 + "px"); // 4px is scrollbar width
    524 
    525 
    526   $(".scroll-pane").removeAttr("tabindex"); // get rid of tabindex added by jscroller
    527 
    528   if ($(".scroll-pane").length > 1) {
    529     // Check if there's a user preference for the panel heights
    530     var cookieHeight = readCookie("reference_height");
    531     if (cookieHeight) {
    532       restoreHeight(cookieHeight);
    533     }
    534   }
    535 
    536   // Resize once loading is finished
    537   resizeNav();
    538   // Check if there's an anchor that we need to scroll into view.
    539   // A delay is needed, because some browsers do not immediately scroll down to the anchor
    540   window.setTimeout(offsetScrollForSticky, 100);
    541 
    542   /* init the language selector based on user cookie for lang */
    543   loadLangPref();
    544   changeNavLang(getLangPref());
    545 
    546   /* setup event handlers to ensure the overflow menu is visible while picking lang */
    547   $("#language select")
    548       .mousedown(function() {
    549         $("div.morehover").addClass("hover"); })
    550       .blur(function() {
    551         $("div.morehover").removeClass("hover"); });
    552 
    553   /* some global variable setup */
    554   resizePackagesNav = $("#resize-packages-nav");
    555   classesNav = $("#classes-nav");
    556   devdocNav = $("#devdoc-nav");
    557 
    558   var cookiePath = "";
    559   if (location.href.indexOf("/reference/") != -1) {
    560     cookiePath = "reference_";
    561   } else if (location.href.indexOf("/guide/") != -1) {
    562     cookiePath = "guide_";
    563   } else if (location.href.indexOf("/tools/") != -1) {
    564     cookiePath = "tools_";
    565   } else if (location.href.indexOf("/training/") != -1) {
    566     cookiePath = "training_";
    567   } else if (location.href.indexOf("/design/") != -1) {
    568     cookiePath = "design_";
    569   } else if (location.href.indexOf("/distribute/") != -1) {
    570     cookiePath = "distribute_";
    571   }
    572 
    573 
    574   /* setup shadowbox for any videos that want it */
    575   var $videoLinks = $("a.video-shadowbox-button, a.notice-developers-video");
    576   if ($videoLinks.length) {
    577     // if there's at least one, add the shadowbox HTML to the body
    578     $('body').prepend(
    579 '<div id="video-container">'+
    580   '<div id="video-frame">'+
    581     '<div class="video-close">'+
    582       '<span id="icon-video-close" onclick="closeVideo()">&nbsp;</span>'+
    583     '</div>'+
    584     '<div id="youTubePlayer"></div>'+
    585   '</div>'+
    586 '</div>');
    587 
    588     // loads the IFrame Player API code asynchronously.
    589     $.getScript("https://www.youtube.com/iframe_api");
    590 
    591     $videoLinks.each(function() {
    592       var videoId = $(this).attr('href').split('?v=')[1];
    593       $(this).click(function(event) {
    594         event.preventDefault();
    595         startYouTubePlayer(videoId);
    596       });
    597     });
    598   }
    599 });
    600 // END of the onload event
    601 
    602 
    603 var youTubePlayer;
    604 function onYouTubeIframeAPIReady() {
    605 }
    606 
    607 /* Returns the height the shadowbox video should be. It's based on the current
    608    height of the "video-frame" element, which is 100% height for the window.
    609    Then minus the margin so the video isn't actually the full window height. */
    610 function getVideoHeight() {
    611   var frameHeight = $("#video-frame").height();
    612   var marginTop = $("#video-frame").css('margin-top').split('px')[0];
    613   return frameHeight - (marginTop * 2);
    614 }
    615 
    616 var mPlayerPaused = false;
    617 
    618 function startYouTubePlayer(videoId) {
    619   $("#video-container").show();
    620   $("#video-frame").show();
    621   mPlayerPaused = false;
    622 
    623   // compute the size of the player so it's centered in window
    624   var maxWidth = 940;  // the width of the web site content
    625   var videoAspect = .5625; // based on 1280x720 resolution
    626   var maxHeight = maxWidth * videoAspect;
    627   var videoHeight = getVideoHeight();
    628   var videoWidth = videoHeight / videoAspect;
    629   if (videoWidth > maxWidth) {
    630     videoWidth = maxWidth;
    631     videoHeight = maxHeight;
    632   }
    633   $("#video-frame").css('width', videoWidth);
    634 
    635   // check if we've already created this player
    636   if (youTubePlayer == null) {
    637     // check if there's a start time specified
    638     var idAndHash = videoId.split("#");
    639     var startTime = 0;
    640     if (idAndHash.length > 1) {
    641       startTime = idAndHash[1].split("t=")[1] != undefined ? idAndHash[1].split("t=")[1] : 0;
    642     }
    643     // enable localized player
    644     var lang = getLangPref();
    645     var captionsOn = lang == 'en' ? 0 : 1;
    646 
    647     youTubePlayer = new YT.Player('youTubePlayer', {
    648       height: videoHeight,
    649       width: videoWidth,
    650       videoId: idAndHash[0],
    651       playerVars: {start: startTime, hl: lang, cc_load_policy: captionsOn},
    652       events: {
    653         'onReady': onPlayerReady,
    654         'onStateChange': onPlayerStateChange
    655       }
    656     });
    657   } else {
    658     // reset the size in case the user adjusted the window since last play
    659     youTubePlayer.setSize(videoWidth, videoHeight);
    660     // if a video different from the one already playing was requested, cue it up
    661     if (videoId != youTubePlayer.getVideoUrl().split('?v=')[1].split('&')[0].split('%')[0]) {
    662       youTubePlayer.cueVideoById(videoId);
    663     }
    664     youTubePlayer.playVideo();
    665   }
    666 }
    667 
    668 function onPlayerReady(event) {
    669   event.target.playVideo();
    670   mPlayerPaused = false;
    671 }
    672 
    673 function closeVideo() {
    674   try {
    675     youTubePlayer.pauseVideo();
    676   } catch(e) {
    677   }
    678   $("#video-container").fadeOut(200);
    679 }
    680 
    681 /* Track youtube playback for analytics */
    682 function onPlayerStateChange(event) {
    683     // Video starts, send the video ID
    684     if (event.data == YT.PlayerState.PLAYING) {
    685       if (mPlayerPaused) {
    686         ga('send', 'event', 'Videos', 'Resume',
    687             youTubePlayer.getVideoUrl().split('?v=')[1].split('&')[0].split('%')[0]);
    688       } else {
    689         // track the start playing event so we know from which page the video was selected
    690         ga('send', 'event', 'Videos', 'Start: ' +
    691             youTubePlayer.getVideoUrl().split('?v=')[1].split('&')[0].split('%')[0],
    692             'on: ' + document.location.href);
    693       }
    694       mPlayerPaused = false;
    695     }
    696     // Video paused, send video ID and video elapsed time
    697     if (event.data == YT.PlayerState.PAUSED) {
    698       ga('send', 'event', 'Videos', 'Paused',
    699             youTubePlayer.getVideoUrl().split('?v=')[1].split('&')[0].split('%')[0],
    700             youTubePlayer.getCurrentTime());
    701       mPlayerPaused = true;
    702     }
    703     // Video finished, send video ID and video elapsed time
    704     if (event.data == YT.PlayerState.ENDED) {
    705       ga('send', 'event', 'Videos', 'Finished',
    706             youTubePlayer.getVideoUrl().split('?v=')[1].split('&')[0].split('%')[0],
    707             youTubePlayer.getCurrentTime());
    708       mPlayerPaused = true;
    709     }
    710 }
    711 
    712 
    713 
    714 function initExpandableNavItems(rootTag) {
    715   $(rootTag + ' li.nav-section .nav-section-header').click(function() {
    716     var section = $(this).closest('li.nav-section');
    717     if (section.hasClass('expanded')) {
    718     /* hide me and descendants */
    719       section.find('ul').slideUp(250, function() {
    720         // remove 'expanded' class from my section and any children
    721         section.closest('li').removeClass('expanded');
    722         $('li.nav-section', section).removeClass('expanded');
    723         resizeNav();
    724       });
    725     } else {
    726     /* show me */
    727       // first hide all other siblings
    728       var $others = $('li.nav-section.expanded', $(this).closest('ul')).not('.sticky');
    729       $others.removeClass('expanded').children('ul').slideUp(250);
    730 
    731       // now expand me
    732       section.closest('li').addClass('expanded');
    733       section.children('ul').slideDown(250, function() {
    734         resizeNav();
    735       });
    736     }
    737   });
    738 
    739   // Stop expand/collapse behavior when clicking on nav section links
    740   // (since we're navigating away from the page)
    741   // This selector captures the first instance of <a>, but not those with "#" as the href.
    742   $('.nav-section-header').find('a:eq(0)').not('a[href="#"]').click(function(evt) {
    743     window.location.href = $(this).attr('href');
    744     return false;
    745   });
    746 }
    747 
    748 
    749 /** Create the list of breadcrumb links in the sticky header */
    750 function buildBreadcrumbs() {
    751   var $breadcrumbUl =  $("#sticky-header ul.breadcrumb");
    752   // Add the secondary horizontal nav item, if provided
    753   var $selectedSecondNav = $("div#nav-x ul.nav-x a.selected").clone().removeClass("selected");
    754   if ($selectedSecondNav.length) {
    755     $breadcrumbUl.prepend($("<li>").append($selectedSecondNav))
    756   }
    757   // Add the primary horizontal nav
    758   var $selectedFirstNav = $("div#header-wrap ul.nav-x a.selected").clone().removeClass("selected");
    759   // If there's no header nav item, use the logo link and title from alt text
    760   if ($selectedFirstNav.length < 1) {
    761     $selectedFirstNav = $("<a>")
    762         .attr('href', $("div#header .logo a").attr('href'))
    763         .text($("div#header .logo img").attr('alt'));
    764   }
    765   $breadcrumbUl.prepend($("<li>").append($selectedFirstNav));
    766 }
    767 
    768 
    769 
    770 /** Highlight the current page in sidenav, expanding children as appropriate */
    771 function highlightSidenav() {
    772   // if something is already highlighted, undo it. This is for dynamic navigation (Samples index)
    773   if ($("ul#nav li.selected").length) {
    774     unHighlightSidenav();
    775   }
    776   // look for URL in sidenav, including the hash
    777   var $selNavLink = $('#nav').find('a[href="' + mPagePath + location.hash + '"]');
    778 
    779   // If the selNavLink is still empty, look for it without the hash
    780   if ($selNavLink.length == 0) {
    781     $selNavLink = $('#nav').find('a[href="' + mPagePath + '"]');
    782   }
    783 
    784   var $selListItem;
    785   if ($selNavLink.length) {
    786     // Find this page's <li> in sidenav and set selected
    787     $selListItem = $selNavLink.closest('li');
    788     $selListItem.addClass('selected');
    789 
    790     // Traverse up the tree and expand all parent nav-sections
    791     $selNavLink.parents('li.nav-section').each(function() {
    792       $(this).addClass('expanded');
    793       $(this).children('ul').show();
    794     });
    795   }
    796 }
    797 
    798 function unHighlightSidenav() {
    799   $("ul#nav li.selected").removeClass("selected");
    800   $('ul#nav li.nav-section.expanded').removeClass('expanded').children('ul').hide();
    801 }
    802 
    803 function toggleFullscreen(enable) {
    804   var delay = 20;
    805   var enabled = true;
    806   var stylesheet = $('link[rel="stylesheet"][class="fullscreen"]');
    807   if (enable) {
    808     // Currently NOT USING fullscreen; enable fullscreen
    809     stylesheet.removeAttr('disabled');
    810     $('#nav-swap .fullscreen').removeClass('disabled');
    811     $('#devdoc-nav').css({left:''});
    812     setTimeout(updateSidenavFullscreenWidth,delay); // need to wait a moment for css to switch
    813     enabled = true;
    814   } else {
    815     // Currently USING fullscreen; disable fullscreen
    816     stylesheet.attr('disabled', 'disabled');
    817     $('#nav-swap .fullscreen').addClass('disabled');
    818     setTimeout(updateSidenavFixedWidth,delay); // need to wait a moment for css to switch
    819     enabled = false;
    820   }
    821   writeCookie("fullscreen", enabled, null);
    822   setNavBarLeftPos();
    823   resizeNav(delay);
    824   updateSideNavPosition();
    825   setTimeout(initSidenavHeightResize,delay);
    826 }
    827 
    828 
    829 function setNavBarLeftPos() {
    830   navBarLeftPos = $('#body-content').offset().left;
    831 }
    832 
    833 
    834 function updateSideNavPosition() {
    835   var newLeft = $(window).scrollLeft() - navBarLeftPos;
    836   $('#devdoc-nav').css({left: -newLeft});
    837   $('#devdoc-nav .totop').css({left: -(newLeft - parseInt($('#side-nav').css('margin-left')))});
    838 }
    839 
    840 // TODO: use $(document).ready instead
    841 function addLoadEvent(newfun) {
    842   var current = window.onload;
    843   if (typeof window.onload != 'function') {
    844     window.onload = newfun;
    845   } else {
    846     window.onload = function() {
    847       current();
    848       newfun();
    849     }
    850   }
    851 }
    852 
    853 var agent = navigator['userAgent'].toLowerCase();
    854 // If a mobile phone, set flag and do mobile setup
    855 if ((agent.indexOf("mobile") != -1) ||      // android, iphone, ipod
    856     (agent.indexOf("blackberry") != -1) ||
    857     (agent.indexOf("webos") != -1) ||
    858     (agent.indexOf("mini") != -1)) {        // opera mini browsers
    859   isMobile = true;
    860 }
    861 
    862 
    863 $(document).ready(function() {
    864   $("pre:not(.no-pretty-print)").addClass("prettyprint");
    865   prettyPrint();
    866 });
    867 
    868 
    869 
    870 
    871 /* ######### RESIZE THE SIDENAV HEIGHT ########## */
    872 
    873 function resizeNav(delay) {
    874   var $nav = $("#devdoc-nav");
    875   var $window = $(window);
    876   var navHeight;
    877 
    878   // Get the height of entire window and the total header height.
    879   // Then figure out based on scroll position whether the header is visible
    880   var windowHeight = $window.height();
    881   var scrollTop = $window.scrollTop();
    882   var headerHeight = $('#header-wrapper').outerHeight();
    883   var headerVisible = scrollTop < stickyTop;
    884 
    885   // get the height of space between nav and top of window.
    886   // Could be either margin or top position, depending on whether the nav is fixed.
    887   var topMargin = (parseInt($nav.css('margin-top')) || parseInt($nav.css('top'))) + 1;
    888   // add 1 for the #side-nav bottom margin
    889 
    890   // Depending on whether the header is visible, set the side nav's height.
    891   if (headerVisible) {
    892     // The sidenav height grows as the header goes off screen
    893     navHeight = windowHeight - (headerHeight - scrollTop) - topMargin;
    894   } else {
    895     // Once header is off screen, the nav height is almost full window height
    896     navHeight = windowHeight - topMargin;
    897   }
    898 
    899 
    900 
    901   $scrollPanes = $(".scroll-pane");
    902   if ($scrollPanes.length > 1) {
    903     // subtract the height of the api level widget and nav swapper from the available nav height
    904     navHeight -= ($('#api-nav-header').outerHeight(true) + $('#nav-swap').outerHeight(true));
    905 
    906     $("#swapper").css({height:navHeight + "px"});
    907     if ($("#nav-tree").is(":visible")) {
    908       $("#nav-tree").css({height:navHeight});
    909     }
    910 
    911     var classesHeight = navHeight - parseInt($("#resize-packages-nav").css("height")) - 10 + "px";
    912     //subtract 10px to account for drag bar
    913 
    914     // if the window becomes small enough to make the class panel height 0,
    915     // then the package panel should begin to shrink
    916     if (parseInt(classesHeight) <= 0) {
    917       $("#resize-packages-nav").css({height:navHeight - 10}); //subtract 10px for drag bar
    918       $("#packages-nav").css({height:navHeight - 10});
    919     }
    920 
    921     $("#classes-nav").css({'height':classesHeight, 'margin-top':'10px'});
    922     $("#classes-nav .jspContainer").css({height:classesHeight});
    923 
    924 
    925   } else {
    926     $nav.height(navHeight);
    927   }
    928 
    929   if (delay) {
    930     updateFromResize = true;
    931     delayedReInitScrollbars(delay);
    932   } else {
    933     reInitScrollbars();
    934   }
    935 
    936 }
    937 
    938 var updateScrollbars = false;
    939 var updateFromResize = false;
    940 
    941 /* Re-initialize the scrollbars to account for changed nav size.
    942  * This method postpones the actual update by a 1/4 second in order to optimize the
    943  * scroll performance while the header is still visible, because re-initializing the
    944  * scroll panes is an intensive process.
    945  */
    946 function delayedReInitScrollbars(delay) {
    947   // If we're scheduled for an update, but have received another resize request
    948   // before the scheduled resize has occured, just ignore the new request
    949   // (and wait for the scheduled one).
    950   if (updateScrollbars && updateFromResize) {
    951     updateFromResize = false;
    952     return;
    953   }
    954 
    955   // We're scheduled for an update and the update request came from this method's setTimeout
    956   if (updateScrollbars && !updateFromResize) {
    957     reInitScrollbars();
    958     updateScrollbars = false;
    959   } else {
    960     updateScrollbars = true;
    961     updateFromResize = false;
    962     setTimeout('delayedReInitScrollbars()',delay);
    963   }
    964 }
    965 
    966 /* Re-initialize the scrollbars to account for changed nav size. */
    967 function reInitScrollbars() {
    968   var pane = $(".scroll-pane").each(function(){
    969     var api = $(this).data('jsp');
    970     if (!api) { setTimeout(reInitScrollbars,300); return;}
    971     api.reinitialise( {verticalGutter:0} );
    972   });
    973   $(".scroll-pane").removeAttr("tabindex"); // get rid of tabindex added by jscroller
    974 }
    975 
    976 
    977 /* Resize the height of the nav panels in the reference,
    978  * and save the new size to a cookie */
    979 function saveNavPanels() {
    980   var basePath = getBaseUri(location.pathname);
    981   var section = basePath.substring(1,basePath.indexOf("/",1));
    982   writeCookie("height", resizePackagesNav.css("height"), section);
    983 }
    984 
    985 
    986 
    987 function restoreHeight(packageHeight) {
    988     $("#resize-packages-nav").height(packageHeight);
    989     $("#packages-nav").height(packageHeight);
    990   //  var classesHeight = navHeight - packageHeight;
    991  //   $("#classes-nav").css({height:classesHeight});
    992   //  $("#classes-nav .jspContainer").css({height:classesHeight});
    993 }
    994 
    995 
    996 
    997 /* ######### END RESIZE THE SIDENAV HEIGHT ########## */
    998 
    999 
   1000 
   1001 
   1002 
   1003 /** Scroll the jScrollPane to make the currently selected item visible
   1004     This is called when the page finished loading. */
   1005 function scrollIntoView(nav) {
   1006   var $nav = $("#"+nav);
   1007   var element = $nav.jScrollPane({/* ...settings... */});
   1008   var api = element.data('jsp');
   1009 
   1010   if ($nav.is(':visible')) {
   1011     var $selected = $(".selected", $nav);
   1012     if ($selected.length == 0) {
   1013       // If no selected item found, exit
   1014       return;
   1015     }
   1016     // get the selected item's offset from its container nav by measuring the item's offset
   1017     // relative to the document then subtract the container nav's offset relative to the document
   1018     var selectedOffset = $selected.offset().top - $nav.offset().top;
   1019     if (selectedOffset > $nav.height() * .8) { // multiply nav height by .8 so we move up the item
   1020                                                // if it's more than 80% down the nav
   1021       // scroll the item up by an amount equal to 80% the container nav's height
   1022       api.scrollTo(0, selectedOffset - ($nav.height() * .8), false);
   1023     }
   1024   }
   1025 }
   1026 
   1027 
   1028 
   1029 
   1030 
   1031 
   1032 /* Show popup dialogs */
   1033 function showDialog(id) {
   1034   $dialog = $("#"+id);
   1035   $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>');
   1036   $dialog.wrapInner('<div/>');
   1037   $dialog.removeClass("hide");
   1038 }
   1039 
   1040 
   1041 
   1042 
   1043 
   1044 /* #########    COOKIES!     ########## */
   1045 
   1046 function readCookie(cookie) {
   1047   var myCookie = cookie_namespace+"_"+cookie+"=";
   1048   if (document.cookie) {
   1049     var index = document.cookie.indexOf(myCookie);
   1050     if (index != -1) {
   1051       var valStart = index + myCookie.length;
   1052       var valEnd = document.cookie.indexOf(";", valStart);
   1053       if (valEnd == -1) {
   1054         valEnd = document.cookie.length;
   1055       }
   1056       var val = document.cookie.substring(valStart, valEnd);
   1057       return val;
   1058     }
   1059   }
   1060   return 0;
   1061 }
   1062 
   1063 function writeCookie(cookie, val, section) {
   1064   if (val==undefined) return;
   1065   section = section == null ? "_" : "_"+section+"_";
   1066   var age = 2*365*24*60*60; // set max-age to 2 years
   1067   var cookieValue = cookie_namespace + section + cookie + "=" + val
   1068                     + "; max-age=" + age +"; path=/";
   1069   document.cookie = cookieValue;
   1070 }
   1071 
   1072 /* #########     END COOKIES!     ########## */
   1073 
   1074 
   1075 var sticky = false;
   1076 var stickyTop;
   1077 var prevScrollLeft = 0; // used to compare current position to previous position of horiz scroll
   1078 /* Sets the vertical scoll position at which the sticky bar should appear.
   1079    This method is called to reset the position when search results appear or hide */
   1080 function setStickyTop() {
   1081   stickyTop = $('#header-wrapper').outerHeight() - $('#sticky-header').outerHeight();
   1082 }
   1083 
   1084 /*
   1085  * Displays sticky nav bar on pages when dac header scrolls out of view
   1086  */
   1087 $(window).scroll(function(event) {
   1088 
   1089   setStickyTop();
   1090   var hiding = false;
   1091   var $stickyEl = $('#sticky-header');
   1092   var $menuEl = $('.menu-container');
   1093   // Exit if there's no sidenav
   1094   if ($('#side-nav').length == 0) return;
   1095   // Exit if the mouse target is a DIV, because that means the event is coming
   1096   // from a scrollable div and so there's no need to make adjustments to our layout
   1097   if ($(event.target).nodeName == "DIV") {
   1098     return;
   1099   }
   1100 
   1101   var top = $(window).scrollTop();
   1102   // we set the navbar fixed when the scroll position is beyond the height of the site header...
   1103   var shouldBeSticky = top >= stickyTop;
   1104   // ... except if the document content is shorter than the sidenav height.
   1105   // (this is necessary to avoid crazy behavior on OSX Lion due to overscroll bouncing)
   1106   if ($("#doc-col").height() < $("#side-nav").height()) {
   1107     shouldBeSticky = false;
   1108   }
   1109   // Account for horizontal scroll
   1110   var scrollLeft = $(window).scrollLeft();
   1111   // When the sidenav is fixed and user scrolls horizontally, reposition the sidenav to match
   1112   if (sticky && (scrollLeft != prevScrollLeft)) {
   1113     updateSideNavPosition();
   1114     prevScrollLeft = scrollLeft;
   1115   }
   1116 
   1117   // Don't continue if the header is sufficently far away
   1118   // (to avoid intensive resizing that slows scrolling)
   1119   if (sticky == shouldBeSticky) {
   1120     return;
   1121   }
   1122 
   1123   // If sticky header visible and position is now near top, hide sticky
   1124   if (sticky && !shouldBeSticky) {
   1125     sticky = false;
   1126     hiding = true;
   1127     // make the sidenav static again
   1128     $('#devdoc-nav')
   1129         .removeClass('fixed')
   1130         .css({'width':'auto','margin':''})
   1131         .prependTo('#side-nav');
   1132     // delay hide the sticky
   1133     $menuEl.removeClass('sticky-menu');
   1134     $stickyEl.fadeOut(250);
   1135     hiding = false;
   1136 
   1137     // update the sidenaav position for side scrolling
   1138     updateSideNavPosition();
   1139   } else if (!sticky && shouldBeSticky) {
   1140     sticky = true;
   1141     $stickyEl.fadeIn(10);
   1142     $menuEl.addClass('sticky-menu');
   1143 
   1144     // make the sidenav fixed
   1145     var width = $('#devdoc-nav').width();
   1146     $('#devdoc-nav')
   1147         .addClass('fixed')
   1148         .css({'width':width+'px'})
   1149         .prependTo('#body-content');
   1150 
   1151     // update the sidenaav position for side scrolling
   1152     updateSideNavPosition();
   1153 
   1154   } else if (hiding && top < 15) {
   1155     $menuEl.removeClass('sticky-menu');
   1156     $stickyEl.hide();
   1157     hiding = false;
   1158   }
   1159   resizeNav(250); // pass true in order to delay the scrollbar re-initialization for performance
   1160 });
   1161 
   1162 /*
   1163  * Manages secion card states and nav resize to conclude loading
   1164  */
   1165 (function() {
   1166   $(document).ready(function() {
   1167 
   1168     // Stack hover states
   1169     $('.section-card-menu').each(function(index, el) {
   1170       var height = $(el).height();
   1171       $(el).css({height:height+'px', position:'relative'});
   1172       var $cardInfo = $(el).find('.card-info');
   1173 
   1174       $cardInfo.css({position: 'absolute', bottom:'0px', left:'0px', right:'0px', overflow:'visible'});
   1175     });
   1176 
   1177   });
   1178 
   1179 })();
   1180 
   1181 
   1182 
   1183 
   1184 
   1185 
   1186 
   1187 
   1188 
   1189 
   1190 
   1191 
   1192 
   1193 
   1194 /*      MISC LIBRARY FUNCTIONS     */
   1195 
   1196 
   1197 
   1198 
   1199 
   1200 function toggle(obj, slide) {
   1201   var ul = $("ul:first", obj);
   1202   var li = ul.parent();
   1203   if (li.hasClass("closed")) {
   1204     if (slide) {
   1205       ul.slideDown("fast");
   1206     } else {
   1207       ul.show();
   1208     }
   1209     li.removeClass("closed");
   1210     li.addClass("open");
   1211     $(".toggle-img", li).attr("title", "hide pages");
   1212   } else {
   1213     ul.slideUp("fast");
   1214     li.removeClass("open");
   1215     li.addClass("closed");
   1216     $(".toggle-img", li).attr("title", "show pages");
   1217   }
   1218 }
   1219 
   1220 
   1221 function buildToggleLists() {
   1222   $(".toggle-list").each(
   1223     function(i) {
   1224       $("div:first", this).append("<a class='toggle-img' href='#' title='show pages' onClick='toggle(this.parentNode.parentNode, true); return false;'></a>");
   1225       $(this).addClass("closed");
   1226     });
   1227 }
   1228 
   1229 
   1230 
   1231 function hideNestedItems(list, toggle) {
   1232   $list = $(list);
   1233   // hide nested lists
   1234   if($list.hasClass('showing')) {
   1235     $("li ol", $list).hide('fast');
   1236     $list.removeClass('showing');
   1237   // show nested lists
   1238   } else {
   1239     $("li ol", $list).show('fast');
   1240     $list.addClass('showing');
   1241   }
   1242   $(".more,.less",$(toggle)).toggle();
   1243 }
   1244 
   1245 
   1246 /* Call this to add listeners to a <select> element for Studio/Eclipse/Other docs */
   1247 function setupIdeDocToggle() {
   1248   $( "select.ide" ).change(function() {
   1249     var selected = $(this).find("option:selected").attr("value");
   1250     $(".select-ide").hide();
   1251     $(".select-ide."+selected).show();
   1252 
   1253     $("select.ide").val(selected);
   1254   });
   1255 }
   1256 
   1257 
   1258 
   1259 
   1260 
   1261 
   1262 
   1263 
   1264 
   1265 
   1266 
   1267 
   1268 
   1269 
   1270 
   1271 
   1272 
   1273 
   1274 
   1275 
   1276 
   1277 
   1278 
   1279 
   1280 /*      REFERENCE NAV SWAP     */
   1281 
   1282 
   1283 function getNavPref() {
   1284   var v = readCookie('reference_nav');
   1285   if (v != NAV_PREF_TREE) {
   1286     v = NAV_PREF_PANELS;
   1287   }
   1288   return v;
   1289 }
   1290 
   1291 function chooseDefaultNav() {
   1292   nav_pref = getNavPref();
   1293   if (nav_pref == NAV_PREF_TREE) {
   1294     $("#nav-panels").toggle();
   1295     $("#panel-link").toggle();
   1296     $("#nav-tree").toggle();
   1297     $("#tree-link").toggle();
   1298   }
   1299 }
   1300 
   1301 function swapNav() {
   1302   if (nav_pref == NAV_PREF_TREE) {
   1303     nav_pref = NAV_PREF_PANELS;
   1304   } else {
   1305     nav_pref = NAV_PREF_TREE;
   1306     init_default_navtree(toRoot);
   1307   }
   1308   writeCookie("nav", nav_pref, "reference");
   1309 
   1310   $("#nav-panels").toggle();
   1311   $("#panel-link").toggle();
   1312   $("#nav-tree").toggle();
   1313   $("#tree-link").toggle();
   1314 
   1315   resizeNav();
   1316 
   1317   // Gross nasty hack to make tree view show up upon first swap by setting height manually
   1318   $("#nav-tree .jspContainer:visible")
   1319       .css({'height':$("#nav-tree .jspContainer .jspPane").height() +'px'});
   1320   // Another nasty hack to make the scrollbar appear now that we have height
   1321   resizeNav();
   1322 
   1323   if ($("#nav-tree").is(':visible')) {
   1324     scrollIntoView("nav-tree");
   1325   } else {
   1326     scrollIntoView("packages-nav");
   1327     scrollIntoView("classes-nav");
   1328   }
   1329 }
   1330 
   1331 
   1332 
   1333 /* ############################################ */
   1334 /* ##########     LOCALIZATION     ############ */
   1335 /* ############################################ */
   1336 
   1337 function getBaseUri(uri) {
   1338   var intlUrl = (uri.substring(0,6) == "/intl/");
   1339   if (intlUrl) {
   1340     base = uri.substring(uri.indexOf('intl/')+5,uri.length);
   1341     base = base.substring(base.indexOf('/')+1, base.length);
   1342       //alert("intl, returning base url: /" + base);
   1343     return ("/" + base);
   1344   } else {
   1345       //alert("not intl, returning uri as found.");
   1346     return uri;
   1347   }
   1348 }
   1349 
   1350 function requestAppendHL(uri) {
   1351 //append "?hl=<lang> to an outgoing request (such as to blog)
   1352   var lang = getLangPref();
   1353   if (lang) {
   1354     var q = 'hl=' + lang;
   1355     uri += '?' + q;
   1356     window.location = uri;
   1357     return false;
   1358   } else {
   1359     return true;
   1360   }
   1361 }
   1362 
   1363 
   1364 function changeNavLang(lang) {
   1365   var $links = $("#devdoc-nav,#header,#nav-x,.training-nav-top,.content-footer").find("a["+lang+"-lang]");
   1366   $links.each(function(i){ // for each link with a translation
   1367     var $link = $(this);
   1368     if (lang != "en") { // No need to worry about English, because a language change invokes new request
   1369       // put the desired language from the attribute as the text
   1370       $link.text($link.attr(lang+"-lang"))
   1371     }
   1372   });
   1373 }
   1374 
   1375 function changeLangPref(lang, submit) {
   1376   writeCookie("pref_lang", lang, null);
   1377 
   1378   //  #######  TODO:  Remove this condition once we're stable on devsite #######
   1379   //  This condition is only needed if we still need to support legacy GAE server
   1380   if (devsite) {
   1381     // Switch language when on Devsite server
   1382     if (submit) {
   1383       $("#setlang").submit();
   1384     }
   1385   } else {
   1386     // Switch language when on legacy GAE server
   1387     if (submit) {
   1388       window.location = getBaseUri(location.pathname);
   1389     }
   1390   }
   1391 }
   1392 
   1393 function loadLangPref() {
   1394   var lang = readCookie("pref_lang");
   1395   if (lang != 0) {
   1396     $("#language").find("option[value='"+lang+"']").attr("selected",true);
   1397   }
   1398 }
   1399 
   1400 function getLangPref() {
   1401   var lang = $("#language").find(":selected").attr("value");
   1402   if (!lang) {
   1403     lang = readCookie("pref_lang");
   1404   }
   1405   return (lang != 0) ? lang : 'en';
   1406 }
   1407 
   1408 /* ##########     END LOCALIZATION     ############ */
   1409 
   1410 
   1411 
   1412 
   1413 
   1414 
   1415 /* Used to hide and reveal supplemental content, such as long code samples.
   1416    See the companion CSS in android-developer-docs.css */
   1417 function toggleContent(obj) {
   1418   var div = $(obj).closest(".toggle-content");
   1419   var toggleMe = $(".toggle-content-toggleme:eq(0)",div);
   1420   if (div.hasClass("closed")) { // if it's closed, open it
   1421     toggleMe.slideDown();
   1422     $(".toggle-content-text:eq(0)", obj).toggle();
   1423     div.removeClass("closed").addClass("open");
   1424     $(".toggle-content-img:eq(0)", div).attr("title", "hide").attr("src", toRoot
   1425                   + "assets/images/triangle-opened.png");
   1426   } else { // if it's open, close it
   1427     toggleMe.slideUp('fast', function() {  // Wait until the animation is done before closing arrow
   1428       $(".toggle-content-text:eq(0)", obj).toggle();
   1429       div.removeClass("open").addClass("closed");
   1430       div.find(".toggle-content").removeClass("open").addClass("closed")
   1431               .find(".toggle-content-toggleme").hide();
   1432       $(".toggle-content-img", div).attr("title", "show").attr("src", toRoot
   1433                   + "assets/images/triangle-closed.png");
   1434     });
   1435   }
   1436   return false;
   1437 }
   1438 
   1439 
   1440 /* New version of expandable content */
   1441 function toggleExpandable(link,id) {
   1442   if($(id).is(':visible')) {
   1443     $(id).slideUp();
   1444     $(link).removeClass('expanded');
   1445   } else {
   1446     $(id).slideDown();
   1447     $(link).addClass('expanded');
   1448   }
   1449 }
   1450 
   1451 function hideExpandable(ids) {
   1452   $(ids).slideUp();
   1453   $(ids).prev('h4').find('a.expandable').removeClass('expanded');
   1454 }
   1455 
   1456 
   1457 
   1458 
   1459 
   1460 /*
   1461  *  Slideshow 1.0
   1462  *  Used on /index.html and /develop/index.html for carousel
   1463  *
   1464  *  Sample usage:
   1465  *  HTML -
   1466  *  <div class="slideshow-container">
   1467  *   <a href="" class="slideshow-prev">Prev</a>
   1468  *   <a href="" class="slideshow-next">Next</a>
   1469  *   <ul>
   1470  *       <li class="item"><img src="images/marquee1.jpg"></li>
   1471  *       <li class="item"><img src="images/marquee2.jpg"></li>
   1472  *       <li class="item"><img src="images/marquee3.jpg"></li>
   1473  *       <li class="item"><img src="images/marquee4.jpg"></li>
   1474  *   </ul>
   1475  *  </div>
   1476  *
   1477  *   <script type="text/javascript">
   1478  *   $('.slideshow-container').dacSlideshow({
   1479  *       auto: true,
   1480  *       btnPrev: '.slideshow-prev',
   1481  *       btnNext: '.slideshow-next'
   1482  *   });
   1483  *   </script>
   1484  *
   1485  *  Options:
   1486  *  btnPrev:    optional identifier for previous button
   1487  *  btnNext:    optional identifier for next button
   1488  *  btnPause:   optional identifier for pause button
   1489  *  auto:       whether or not to auto-proceed
   1490  *  speed:      animation speed
   1491  *  autoTime:   time between auto-rotation
   1492  *  easing:     easing function for transition
   1493  *  start:      item to select by default
   1494  *  scroll:     direction to scroll in
   1495  *  pagination: whether or not to include dotted pagination
   1496  *
   1497  */
   1498 
   1499  (function($) {
   1500  $.fn.dacSlideshow = function(o) {
   1501 
   1502      //Options - see above
   1503      o = $.extend({
   1504          btnPrev:   null,
   1505          btnNext:   null,
   1506          btnPause:  null,
   1507          auto:      true,
   1508          speed:     500,
   1509          autoTime:  12000,
   1510          easing:    null,
   1511          start:     0,
   1512          scroll:    1,
   1513          pagination: true
   1514 
   1515      }, o || {});
   1516 
   1517      //Set up a carousel for each
   1518      return this.each(function() {
   1519 
   1520          var running = false;
   1521          var animCss = o.vertical ? "top" : "left";
   1522          var sizeCss = o.vertical ? "height" : "width";
   1523          var div = $(this);
   1524          var ul = $("ul", div);
   1525          var tLi = $("li", ul);
   1526          var tl = tLi.size();
   1527          var timer = null;
   1528 
   1529          var li = $("li", ul);
   1530          var itemLength = li.size();
   1531          var curr = o.start;
   1532 
   1533          li.css({float: o.vertical ? "none" : "left"});
   1534          ul.css({margin: "0", padding: "0", position: "relative", "list-style-type": "none", "z-index": "1"});
   1535          div.css({position: "relative", "z-index": "2", left: "0px"});
   1536 
   1537          var liSize = o.vertical ? height(li) : width(li);
   1538          var ulSize = liSize * itemLength;
   1539          var divSize = liSize;
   1540 
   1541          li.css({width: li.width(), height: li.height()});
   1542          ul.css(sizeCss, ulSize+"px").css(animCss, -(curr*liSize));
   1543 
   1544          div.css(sizeCss, divSize+"px");
   1545 
   1546          //Pagination
   1547          if (o.pagination) {
   1548              var pagination = $("<div class='pagination'></div>");
   1549              var pag_ul = $("<ul></ul>");
   1550              if (tl > 1) {
   1551                for (var i=0;i<tl;i++) {
   1552                     var li = $("<li>"+i+"</li>");
   1553                     pag_ul.append(li);
   1554                     if (i==o.start) li.addClass('active');
   1555                         li.click(function() {
   1556                         go(parseInt($(this).text()));
   1557                     })
   1558                 }
   1559                 pagination.append(pag_ul);
   1560                 div.append(pagination);
   1561              }
   1562          }
   1563 
   1564          //Previous button
   1565          if(o.btnPrev)
   1566              $(o.btnPrev).click(function(e) {
   1567                  e.preventDefault();
   1568                  return go(curr-o.scroll);
   1569              });
   1570 
   1571          //Next button
   1572          if(o.btnNext)
   1573              $(o.btnNext).click(function(e) {
   1574                  e.preventDefault();
   1575                  return go(curr+o.scroll);
   1576              });
   1577 
   1578          //Pause button
   1579          if(o.btnPause)
   1580              $(o.btnPause).click(function(e) {
   1581                  e.preventDefault();
   1582                  if ($(this).hasClass('paused')) {
   1583                      startRotateTimer();
   1584                  } else {
   1585                      pauseRotateTimer();
   1586                  }
   1587              });
   1588 
   1589          //Auto rotation
   1590          if(o.auto) startRotateTimer();
   1591 
   1592          function startRotateTimer() {
   1593              clearInterval(timer);
   1594              timer = setInterval(function() {
   1595                   if (curr == tl-1) {
   1596                     go(0);
   1597                   } else {
   1598                     go(curr+o.scroll);
   1599                   }
   1600               }, o.autoTime);
   1601              $(o.btnPause).removeClass('paused');
   1602          }
   1603 
   1604          function pauseRotateTimer() {
   1605              clearInterval(timer);
   1606              $(o.btnPause).addClass('paused');
   1607          }
   1608 
   1609          //Go to an item
   1610          function go(to) {
   1611              if(!running) {
   1612 
   1613                  if(to<0) {
   1614                     to = itemLength-1;
   1615                  } else if (to>itemLength-1) {
   1616                     to = 0;
   1617                  }
   1618                  curr = to;
   1619 
   1620                  running = true;
   1621 
   1622                  ul.animate(
   1623                      animCss == "left" ? { left: -(curr*liSize) } : { top: -(curr*liSize) } , o.speed, o.easing,
   1624                      function() {
   1625                          running = false;
   1626                      }
   1627                  );
   1628 
   1629                  $(o.btnPrev + "," + o.btnNext).removeClass("disabled");
   1630                  $( (curr-o.scroll<0 && o.btnPrev)
   1631                      ||
   1632                     (curr+o.scroll > itemLength && o.btnNext)
   1633                      ||
   1634                     []
   1635                   ).addClass("disabled");
   1636 
   1637 
   1638                  var nav_items = $('li', pagination);
   1639                  nav_items.removeClass('active');
   1640                  nav_items.eq(to).addClass('active');
   1641 
   1642 
   1643              }
   1644              if(o.auto) startRotateTimer();
   1645              return false;
   1646          };
   1647      });
   1648  };
   1649 
   1650  function css(el, prop) {
   1651      return parseInt($.css(el[0], prop)) || 0;
   1652  };
   1653  function width(el) {
   1654      return  el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight');
   1655  };
   1656  function height(el) {
   1657      return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom');
   1658  };
   1659 
   1660  })(jQuery);
   1661 
   1662 
   1663 /*
   1664  *  dacSlideshow 1.0
   1665  *  Used on develop/index.html for side-sliding tabs
   1666  *
   1667  *  Sample usage:
   1668  *  HTML -
   1669  *  <div class="slideshow-container">
   1670  *   <a href="" class="slideshow-prev">Prev</a>
   1671  *   <a href="" class="slideshow-next">Next</a>
   1672  *   <ul>
   1673  *       <li class="item"><img src="images/marquee1.jpg"></li>
   1674  *       <li class="item"><img src="images/marquee2.jpg"></li>
   1675  *       <li class="item"><img src="images/marquee3.jpg"></li>
   1676  *       <li class="item"><img src="images/marquee4.jpg"></li>
   1677  *   </ul>
   1678  *  </div>
   1679  *
   1680  *   <script type="text/javascript">
   1681  *   $('.slideshow-container').dacSlideshow({
   1682  *       auto: true,
   1683  *       btnPrev: '.slideshow-prev',
   1684  *       btnNext: '.slideshow-next'
   1685  *   });
   1686  *   </script>
   1687  *
   1688  *  Options:
   1689  *  btnPrev:    optional identifier for previous button
   1690  *  btnNext:    optional identifier for next button
   1691  *  auto:       whether or not to auto-proceed
   1692  *  speed:      animation speed
   1693  *  autoTime:   time between auto-rotation
   1694  *  easing:     easing function for transition
   1695  *  start:      item to select by default
   1696  *  scroll:     direction to scroll in
   1697  *  pagination: whether or not to include dotted pagination
   1698  *
   1699  */
   1700  (function($) {
   1701  $.fn.dacTabbedList = function(o) {
   1702 
   1703      //Options - see above
   1704      o = $.extend({
   1705          speed : 250,
   1706          easing: null,
   1707          nav_id: null,
   1708          frame_id: null
   1709      }, o || {});
   1710 
   1711      //Set up a carousel for each
   1712      return this.each(function() {
   1713 
   1714          var curr = 0;
   1715          var running = false;
   1716          var animCss = "margin-left";
   1717          var sizeCss = "width";
   1718          var div = $(this);
   1719 
   1720          var nav = $(o.nav_id, div);
   1721          var nav_li = $("li", nav);
   1722          var nav_size = nav_li.size();
   1723          var frame = div.find(o.frame_id);
   1724          var content_width = $(frame).find('ul').width();
   1725          //Buttons
   1726          $(nav_li).click(function(e) {
   1727            go($(nav_li).index($(this)));
   1728          })
   1729 
   1730          //Go to an item
   1731          function go(to) {
   1732              if(!running) {
   1733                  curr = to;
   1734                  running = true;
   1735 
   1736                  frame.animate({ 'margin-left' : -(curr*content_width) }, o.speed, o.easing,
   1737                      function() {
   1738                          running = false;
   1739                      }
   1740                  );
   1741 
   1742 
   1743                  nav_li.removeClass('active');
   1744                  nav_li.eq(to).addClass('active');
   1745 
   1746 
   1747              }
   1748              return false;
   1749          };
   1750      });
   1751  };
   1752 
   1753  function css(el, prop) {
   1754      return parseInt($.css(el[0], prop)) || 0;
   1755  };
   1756  function width(el) {
   1757      return  el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight');
   1758  };
   1759  function height(el) {
   1760      return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom');
   1761  };
   1762 
   1763  })(jQuery);
   1764 
   1765 
   1766 
   1767 
   1768 
   1769 /* ######################################################## */
   1770 /* ################  SEARCH SUGGESTIONS  ################## */
   1771 /* ######################################################## */
   1772 
   1773 
   1774 
   1775 var gSelectedIndex = -1;  // the index position of currently highlighted suggestion
   1776 var gSelectedColumn = -1;  // which column of suggestion lists is currently focused
   1777 
   1778 var gMatches = new Array();
   1779 var gLastText = "";
   1780 var gInitialized = false;
   1781 var ROW_COUNT_FRAMEWORK = 20;       // max number of results in list
   1782 var gListLength = 0;
   1783 
   1784 
   1785 var gGoogleMatches = new Array();
   1786 var ROW_COUNT_GOOGLE = 15;          // max number of results in list
   1787 var gGoogleListLength = 0;
   1788 
   1789 var gDocsMatches = new Array();
   1790 var ROW_COUNT_DOCS = 100;          // max number of results in list
   1791 var gDocsListLength = 0;
   1792 
   1793 function onSuggestionClick(link) {
   1794   // When user clicks a suggested document, track it
   1795   ga('send', 'event', 'Suggestion Click', 'clicked: ' + $(link).attr('href'),
   1796                 'query: ' + $("#search_autocomplete").val().toLowerCase());
   1797 }
   1798 
   1799 function set_item_selected($li, selected)
   1800 {
   1801     if (selected) {
   1802         $li.attr('class','jd-autocomplete jd-selected');
   1803     } else {
   1804         $li.attr('class','jd-autocomplete');
   1805     }
   1806 }
   1807 
   1808 function set_item_values(toroot, $li, match)
   1809 {
   1810     var $link = $('a',$li);
   1811     $link.html(match.__hilabel || match.label);
   1812     $link.attr('href',toroot + match.link);
   1813 }
   1814 
   1815 function set_item_values_jd(toroot, $li, match)
   1816 {
   1817     var $link = $('a',$li);
   1818     $link.html(match.title);
   1819     $link.attr('href',toroot + match.url);
   1820 }
   1821 
   1822 function new_suggestion($list) {
   1823     var $li = $("<li class='jd-autocomplete'></li>");
   1824     $list.append($li);
   1825 
   1826     $li.mousedown(function() {
   1827         window.location = this.firstChild.getAttribute("href");
   1828     });
   1829     $li.mouseover(function() {
   1830         $('.search_filtered_wrapper li').removeClass('jd-selected');
   1831         $(this).addClass('jd-selected');
   1832         gSelectedColumn = $(".search_filtered:visible").index($(this).closest('.search_filtered'));
   1833         gSelectedIndex = $("li", $(".search_filtered:visible")[gSelectedColumn]).index(this);
   1834     });
   1835     $li.append("<a onclick='onSuggestionClick(this)'></a>");
   1836     $li.attr('class','show-item');
   1837     return $li;
   1838 }
   1839 
   1840 function sync_selection_table(toroot)
   1841 {
   1842     var $li; //list item jquery object
   1843     var i; //list item iterator
   1844 
   1845     // if there are NO results at all, hide all columns
   1846     if (!(gMatches.length > 0) && !(gGoogleMatches.length > 0) && !(gDocsMatches.length > 0)) {
   1847         $('.suggest-card').hide(300);
   1848         return;
   1849     }
   1850 
   1851     // if there are api results
   1852     if ((gMatches.length > 0) || (gGoogleMatches.length > 0)) {
   1853       // reveal suggestion list
   1854       $('.suggest-card.dummy').show();
   1855       $('.suggest-card.reference').show();
   1856       var listIndex = 0; // list index position
   1857 
   1858       // reset the lists
   1859       $(".search_filtered_wrapper.reference li").remove();
   1860 
   1861       // ########### ANDROID RESULTS #############
   1862       if (gMatches.length > 0) {
   1863 
   1864           // determine android results to show
   1865           gListLength = gMatches.length < ROW_COUNT_FRAMEWORK ?
   1866                         gMatches.length : ROW_COUNT_FRAMEWORK;
   1867           for (i=0; i<gListLength; i++) {
   1868               var $li = new_suggestion($(".suggest-card.reference ul"));
   1869               set_item_values(toroot, $li, gMatches[i]);
   1870               set_item_selected($li, i == gSelectedIndex);
   1871           }
   1872       }
   1873 
   1874       // ########### GOOGLE RESULTS #############
   1875       if (gGoogleMatches.length > 0) {
   1876           // show header for list
   1877           $(".suggest-card.reference ul").append("<li class='header'>in Google Services:</li>");
   1878 
   1879           // determine google results to show
   1880           gGoogleListLength = gGoogleMatches.length < ROW_COUNT_GOOGLE ? gGoogleMatches.length : ROW_COUNT_GOOGLE;
   1881           for (i=0; i<gGoogleListLength; i++) {
   1882               var $li = new_suggestion($(".suggest-card.reference ul"));
   1883               set_item_values(toroot, $li, gGoogleMatches[i]);
   1884               set_item_selected($li, i == gSelectedIndex);
   1885           }
   1886       }
   1887     } else {
   1888       $('.suggest-card.reference').hide();
   1889       $('.suggest-card.dummy').hide();
   1890     }
   1891 
   1892     // ########### JD DOC RESULTS #############
   1893     if (gDocsMatches.length > 0) {
   1894         // reset the lists
   1895         $(".search_filtered_wrapper.docs li").remove();
   1896 
   1897         // determine google results to show
   1898         // NOTE: The order of the conditions below for the sugg.type MUST BE SPECIFIC:
   1899         // The order must match the reverse order that each section appears as a card in
   1900         // the suggestion UI... this may be only for the "develop" grouped items though.
   1901         gDocsListLength = gDocsMatches.length < ROW_COUNT_DOCS ? gDocsMatches.length : ROW_COUNT_DOCS;
   1902         for (i=0; i<gDocsListLength; i++) {
   1903             var sugg = gDocsMatches[i];
   1904             var $li;
   1905             if (sugg.type == "design") {
   1906                 $li = new_suggestion($(".suggest-card.design ul"));
   1907             } else
   1908             if (sugg.type == "distribute") {
   1909                 $li = new_suggestion($(".suggest-card.distribute ul"));
   1910             } else
   1911             if (sugg.type == "samples") {
   1912                 $li = new_suggestion($(".suggest-card.develop .child-card.samples"));
   1913             } else
   1914             if (sugg.type == "training") {
   1915                 $li = new_suggestion($(".suggest-card.develop .child-card.training"));
   1916             } else
   1917             if (sugg.type == "about"||"guide"||"tools"||"google") {
   1918                 $li = new_suggestion($(".suggest-card.develop .child-card.guides"));
   1919             } else {
   1920               continue;
   1921             }
   1922 
   1923             set_item_values_jd(toroot, $li, sugg);
   1924             set_item_selected($li, i == gSelectedIndex);
   1925         }
   1926 
   1927         // add heading and show or hide card
   1928         if ($(".suggest-card.design li").length > 0) {
   1929           $(".suggest-card.design ul").prepend("<li class='header'>Design:</li>");
   1930           $(".suggest-card.design").show(300);
   1931         } else {
   1932           $('.suggest-card.design').hide(300);
   1933         }
   1934         if ($(".suggest-card.distribute li").length > 0) {
   1935           $(".suggest-card.distribute ul").prepend("<li class='header'>Distribute:</li>");
   1936           $(".suggest-card.distribute").show(300);
   1937         } else {
   1938           $('.suggest-card.distribute').hide(300);
   1939         }
   1940         if ($(".child-card.guides li").length > 0) {
   1941           $(".child-card.guides").prepend("<li class='header'>Guides:</li>");
   1942           $(".child-card.guides li").appendTo(".suggest-card.develop ul");
   1943         }
   1944         if ($(".child-card.training li").length > 0) {
   1945           $(".child-card.training").prepend("<li class='header'>Training:</li>");
   1946           $(".child-card.training li").appendTo(".suggest-card.develop ul");
   1947         }
   1948         if ($(".child-card.samples li").length > 0) {
   1949           $(".child-card.samples").prepend("<li class='header'>Samples:</li>");
   1950           $(".child-card.samples li").appendTo(".suggest-card.develop ul");
   1951         }
   1952 
   1953         if ($(".suggest-card.develop li").length > 0) {
   1954           $(".suggest-card.develop").show(300);
   1955         } else {
   1956           $('.suggest-card.develop').hide(300);
   1957         }
   1958 
   1959     } else {
   1960       $('.search_filtered_wrapper.docs .suggest-card:not(.dummy)').hide(300);
   1961     }
   1962 }
   1963 
   1964 /** Called by the search input's onkeydown and onkeyup events.
   1965   * Handles navigation with keyboard arrows, Enter key to invoke search,
   1966   * otherwise invokes search suggestions on key-up event.
   1967   * @param e       The JS event
   1968   * @param kd      True if the event is key-down
   1969   * @param toroot  A string for the site's root path
   1970   * @returns       True if the event should bubble up
   1971   */
   1972 function search_changed(e, kd, toroot)
   1973 {
   1974     var currentLang = getLangPref();
   1975     var search = document.getElementById("search_autocomplete");
   1976     var text = search.value.replace(/(^ +)|( +$)/g, '');
   1977     // get the ul hosting the currently selected item
   1978     gSelectedColumn = gSelectedColumn >= 0 ? gSelectedColumn :  0;
   1979     var $columns = $(".search_filtered_wrapper").find(".search_filtered:visible");
   1980     var $selectedUl = $columns[gSelectedColumn];
   1981 
   1982     // show/hide the close button
   1983     if (text != '') {
   1984         $(".search .close").removeClass("hide");
   1985     } else {
   1986         $(".search .close").addClass("hide");
   1987     }
   1988     // 27 = esc
   1989     if (e.keyCode == 27) {
   1990         // close all search results
   1991         if (kd) $('.search .close').trigger('click');
   1992         return true;
   1993     }
   1994     // 13 = enter
   1995     else if (e.keyCode == 13) {
   1996         if (gSelectedIndex < 0) {
   1997             $('.suggest-card').hide();
   1998             if ($("#searchResults").is(":hidden") && (search.value != "")) {
   1999               // if results aren't showing (and text not empty), return true to allow search to execute
   2000               $('body,html').animate({scrollTop:0}, '500', 'swing');
   2001               return true;
   2002             } else {
   2003               // otherwise, results are already showing, so allow ajax to auto refresh the results
   2004               // and ignore this Enter press to avoid the reload.
   2005               return false;
   2006             }
   2007         } else if (kd && gSelectedIndex >= 0) {
   2008             // click the link corresponding to selected item
   2009             $("a",$("li",$selectedUl)[gSelectedIndex]).get()[0].click();
   2010             return false;
   2011         }
   2012     }
   2013     // If Google results are showing, return true to allow ajax search to execute
   2014     else if ($("#searchResults").is(":visible")) {
   2015         // Also, if search_results is scrolled out of view, scroll to top to make results visible
   2016         if ((sticky ) && (search.value != "")) {
   2017           $('body,html').animate({scrollTop:0}, '500', 'swing');
   2018         }
   2019         return true;
   2020     }
   2021     // 38 UP ARROW
   2022     else if (kd && (e.keyCode == 38)) {
   2023         // if the next item is a header, skip it
   2024         if ($($("li", $selectedUl)[gSelectedIndex-1]).hasClass("header")) {
   2025             gSelectedIndex--;
   2026         }
   2027         if (gSelectedIndex >= 0) {
   2028             $('li', $selectedUl).removeClass('jd-selected');
   2029             gSelectedIndex--;
   2030             $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
   2031             // If user reaches top, reset selected column
   2032             if (gSelectedIndex < 0) {
   2033               gSelectedColumn = -1;
   2034             }
   2035         }
   2036         return false;
   2037     }
   2038     // 40 DOWN ARROW
   2039     else if (kd && (e.keyCode == 40)) {
   2040         // if the next item is a header, skip it
   2041         if ($($("li", $selectedUl)[gSelectedIndex+1]).hasClass("header")) {
   2042             gSelectedIndex++;
   2043         }
   2044         if ((gSelectedIndex < $("li", $selectedUl).length-1) ||
   2045                         ($($("li", $selectedUl)[gSelectedIndex+1]).hasClass("header"))) {
   2046             $('li', $selectedUl).removeClass('jd-selected');
   2047             gSelectedIndex++;
   2048             $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
   2049         }
   2050         return false;
   2051     }
   2052     // Consider left/right arrow navigation
   2053     // NOTE: Order of suggest columns are reverse order (index position 0 is on right)
   2054     else if (kd && $columns.length > 1 && gSelectedColumn >= 0) {
   2055       // 37 LEFT ARROW
   2056       // go left only if current column is not left-most column (last column)
   2057       if (e.keyCode == 37 && gSelectedColumn < $columns.length - 1) {
   2058         $('li', $selectedUl).removeClass('jd-selected');
   2059         gSelectedColumn++;
   2060         $selectedUl = $columns[gSelectedColumn];
   2061         // keep or reset the selected item to last item as appropriate
   2062         gSelectedIndex = gSelectedIndex >
   2063                 $("li", $selectedUl).length-1 ?
   2064                 $("li", $selectedUl).length-1 : gSelectedIndex;
   2065         // if the corresponding item is a header, move down
   2066         if ($($("li", $selectedUl)[gSelectedIndex]).hasClass("header")) {
   2067           gSelectedIndex++;
   2068         }
   2069         // set item selected
   2070         $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
   2071         return false;
   2072       }
   2073       // 39 RIGHT ARROW
   2074       // go right only if current column is not the right-most column (first column)
   2075       else if (e.keyCode == 39 && gSelectedColumn > 0) {
   2076         $('li', $selectedUl).removeClass('jd-selected');
   2077         gSelectedColumn--;
   2078         $selectedUl = $columns[gSelectedColumn];
   2079         // keep or reset the selected item to last item as appropriate
   2080         gSelectedIndex = gSelectedIndex >
   2081                 $("li", $selectedUl).length-1 ?
   2082                 $("li", $selectedUl).length-1 : gSelectedIndex;
   2083         // if the corresponding item is a header, move down
   2084         if ($($("li", $selectedUl)[gSelectedIndex]).hasClass("header")) {
   2085           gSelectedIndex++;
   2086         }
   2087         // set item selected
   2088         $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
   2089         return false;
   2090       }
   2091     }
   2092 
   2093     // if key-up event and not arrow down/up/left/right,
   2094     // read the search query and add suggestions to gMatches
   2095     else if (!kd && (e.keyCode != 40)
   2096                  && (e.keyCode != 38)
   2097                  && (e.keyCode != 37)
   2098                  && (e.keyCode != 39)) {
   2099         gSelectedIndex = -1;
   2100         gMatches = new Array();
   2101         matchedCount = 0;
   2102         gGoogleMatches = new Array();
   2103         matchedCountGoogle = 0;
   2104         gDocsMatches = new Array();
   2105         matchedCountDocs = 0;
   2106 
   2107         // Search for Android matches
   2108         for (var i=0; i<DATA.length; i++) {
   2109             var s = DATA[i];
   2110             if (text.length != 0 &&
   2111                   s.label.toLowerCase().indexOf(text.toLowerCase()) != -1) {
   2112                 gMatches[matchedCount] = s;
   2113                 matchedCount++;
   2114             }
   2115         }
   2116         rank_autocomplete_api_results(text, gMatches);
   2117         for (var i=0; i<gMatches.length; i++) {
   2118             var s = gMatches[i];
   2119         }
   2120 
   2121 
   2122         // Search for Google matches
   2123         for (var i=0; i<GOOGLE_DATA.length; i++) {
   2124             var s = GOOGLE_DATA[i];
   2125             if (text.length != 0 &&
   2126                   s.label.toLowerCase().indexOf(text.toLowerCase()) != -1) {
   2127                 gGoogleMatches[matchedCountGoogle] = s;
   2128                 matchedCountGoogle++;
   2129             }
   2130         }
   2131         rank_autocomplete_api_results(text, gGoogleMatches);
   2132         for (var i=0; i<gGoogleMatches.length; i++) {
   2133             var s = gGoogleMatches[i];
   2134         }
   2135 
   2136         highlight_autocomplete_result_labels(text);
   2137 
   2138 
   2139 
   2140         // Search for matching JD docs
   2141         if (text.length >= 2) {
   2142           // Regex to match only the beginning of a word
   2143           var textRegex = new RegExp("\\b" + text.toLowerCase(), "g");
   2144 
   2145 
   2146           // Search for Training classes
   2147           for (var i=0; i<TRAINING_RESOURCES.length; i++) {
   2148             // current search comparison, with counters for tag and title,
   2149             // used later to improve ranking
   2150             var s = TRAINING_RESOURCES[i];
   2151             s.matched_tag = 0;
   2152             s.matched_title = 0;
   2153             var matched = false;
   2154 
   2155             // Check if query matches any tags; work backwards toward 1 to assist ranking
   2156             for (var j = s.keywords.length - 1; j >= 0; j--) {
   2157               // it matches a tag
   2158               if (s.keywords[j].toLowerCase().match(textRegex)) {
   2159                 matched = true;
   2160                 s.matched_tag = j + 1; // add 1 to index position
   2161               }
   2162             }
   2163             // Don't consider doc title for lessons (only for class landing pages),
   2164             // unless the lesson has a tag that already matches
   2165             if ((s.lang == currentLang) &&
   2166                   (!(s.type == "training" && s.url.indexOf("index.html") == -1) || matched)) {
   2167               // it matches the doc title
   2168               if (s.title.toLowerCase().match(textRegex)) {
   2169                 matched = true;
   2170                 s.matched_title = 1;
   2171               }
   2172             }
   2173             if (matched) {
   2174               gDocsMatches[matchedCountDocs] = s;
   2175               matchedCountDocs++;
   2176             }
   2177           }
   2178 
   2179 
   2180           // Search for API Guides
   2181           for (var i=0; i<GUIDE_RESOURCES.length; i++) {
   2182             // current search comparison, with counters for tag and title,
   2183             // used later to improve ranking
   2184             var s = GUIDE_RESOURCES[i];
   2185             s.matched_tag = 0;
   2186             s.matched_title = 0;
   2187             var matched = false;
   2188 
   2189             // Check if query matches any tags; work backwards toward 1 to assist ranking
   2190             for (var j = s.keywords.length - 1; j >= 0; j--) {
   2191               // it matches a tag
   2192               if (s.keywords[j].toLowerCase().match(textRegex)) {
   2193                 matched = true;
   2194                 s.matched_tag = j + 1; // add 1 to index position
   2195               }
   2196             }
   2197             // Check if query matches the doc title, but only for current language
   2198             if (s.lang == currentLang) {
   2199               // if query matches the doc title
   2200               if (s.title.toLowerCase().match(textRegex)) {
   2201                 matched = true;
   2202                 s.matched_title = 1;
   2203               }
   2204             }
   2205             if (matched) {
   2206               gDocsMatches[matchedCountDocs] = s;
   2207               matchedCountDocs++;
   2208             }
   2209           }
   2210 
   2211 
   2212           // Search for Tools Guides
   2213           for (var i=0; i<TOOLS_RESOURCES.length; i++) {
   2214             // current search comparison, with counters for tag and title,
   2215             // used later to improve ranking
   2216             var s = TOOLS_RESOURCES[i];
   2217             s.matched_tag = 0;
   2218             s.matched_title = 0;
   2219             var matched = false;
   2220 
   2221             // Check if query matches any tags; work backwards toward 1 to assist ranking
   2222             for (var j = s.keywords.length - 1; j >= 0; j--) {
   2223               // it matches a tag
   2224               if (s.keywords[j].toLowerCase().match(textRegex)) {
   2225                 matched = true;
   2226                 s.matched_tag = j + 1; // add 1 to index position
   2227               }
   2228             }
   2229             // Check if query matches the doc title, but only for current language
   2230             if (s.lang == currentLang) {
   2231               // if query matches the doc title
   2232               if (s.title.toLowerCase().match(textRegex)) {
   2233                 matched = true;
   2234                 s.matched_title = 1;
   2235               }
   2236             }
   2237             if (matched) {
   2238               gDocsMatches[matchedCountDocs] = s;
   2239               matchedCountDocs++;
   2240             }
   2241           }
   2242 
   2243 
   2244           // Search for About docs
   2245           for (var i=0; i<ABOUT_RESOURCES.length; i++) {
   2246             // current search comparison, with counters for tag and title,
   2247             // used later to improve ranking
   2248             var s = ABOUT_RESOURCES[i];
   2249             s.matched_tag = 0;
   2250             s.matched_title = 0;
   2251             var matched = false;
   2252 
   2253             // Check if query matches any tags; work backwards toward 1 to assist ranking
   2254             for (var j = s.keywords.length - 1; j >= 0; j--) {
   2255               // it matches a tag
   2256               if (s.keywords[j].toLowerCase().match(textRegex)) {
   2257                 matched = true;
   2258                 s.matched_tag = j + 1; // add 1 to index position
   2259               }
   2260             }
   2261             // Check if query matches the doc title, but only for current language
   2262             if (s.lang == currentLang) {
   2263               // if query matches the doc title
   2264               if (s.title.toLowerCase().match(textRegex)) {
   2265                 matched = true;
   2266                 s.matched_title = 1;
   2267               }
   2268             }
   2269             if (matched) {
   2270               gDocsMatches[matchedCountDocs] = s;
   2271               matchedCountDocs++;
   2272             }
   2273           }
   2274 
   2275 
   2276           // Search for Design guides
   2277           for (var i=0; i<DESIGN_RESOURCES.length; i++) {
   2278             // current search comparison, with counters for tag and title,
   2279             // used later to improve ranking
   2280             var s = DESIGN_RESOURCES[i];
   2281             s.matched_tag = 0;
   2282             s.matched_title = 0;
   2283             var matched = false;
   2284 
   2285             // Check if query matches any tags; work backwards toward 1 to assist ranking
   2286             for (var j = s.keywords.length - 1; j >= 0; j--) {
   2287               // it matches a tag
   2288               if (s.keywords[j].toLowerCase().match(textRegex)) {
   2289                 matched = true;
   2290                 s.matched_tag = j + 1; // add 1 to index position
   2291               }
   2292             }
   2293             // Check if query matches the doc title, but only for current language
   2294             if (s.lang == currentLang) {
   2295               // if query matches the doc title
   2296               if (s.title.toLowerCase().match(textRegex)) {
   2297                 matched = true;
   2298                 s.matched_title = 1;
   2299               }
   2300             }
   2301             if (matched) {
   2302               gDocsMatches[matchedCountDocs] = s;
   2303               matchedCountDocs++;
   2304             }
   2305           }
   2306 
   2307 
   2308           // Search for Distribute guides
   2309           for (var i=0; i<DISTRIBUTE_RESOURCES.length; i++) {
   2310             // current search comparison, with counters for tag and title,
   2311             // used later to improve ranking
   2312             var s = DISTRIBUTE_RESOURCES[i];
   2313             s.matched_tag = 0;
   2314             s.matched_title = 0;
   2315             var matched = false;
   2316 
   2317             // Check if query matches any tags; work backwards toward 1 to assist ranking
   2318             for (var j = s.keywords.length - 1; j >= 0; j--) {
   2319               // it matches a tag
   2320               if (s.keywords[j].toLowerCase().match(textRegex)) {
   2321                 matched = true;
   2322                 s.matched_tag = j + 1; // add 1 to index position
   2323               }
   2324             }
   2325             // Check if query matches the doc title, but only for current language
   2326             if (s.lang == currentLang) {
   2327               // if query matches the doc title
   2328               if (s.title.toLowerCase().match(textRegex)) {
   2329                 matched = true;
   2330                 s.matched_title = 1;
   2331               }
   2332             }
   2333             if (matched) {
   2334               gDocsMatches[matchedCountDocs] = s;
   2335               matchedCountDocs++;
   2336             }
   2337           }
   2338 
   2339 
   2340           // Search for Google guides
   2341           for (var i=0; i<GOOGLE_RESOURCES.length; i++) {
   2342             // current search comparison, with counters for tag and title,
   2343             // used later to improve ranking
   2344             var s = GOOGLE_RESOURCES[i];
   2345             s.matched_tag = 0;
   2346             s.matched_title = 0;
   2347             var matched = false;
   2348 
   2349             // Check if query matches any tags; work backwards toward 1 to assist ranking
   2350             for (var j = s.keywords.length - 1; j >= 0; j--) {
   2351               // it matches a tag
   2352               if (s.keywords[j].toLowerCase().match(textRegex)) {
   2353                 matched = true;
   2354                 s.matched_tag = j + 1; // add 1 to index position
   2355               }
   2356             }
   2357             // Check if query matches the doc title, but only for current language
   2358             if (s.lang == currentLang) {
   2359               // if query matches the doc title
   2360               if (s.title.toLowerCase().match(textRegex)) {
   2361                 matched = true;
   2362                 s.matched_title = 1;
   2363               }
   2364             }
   2365             if (matched) {
   2366               gDocsMatches[matchedCountDocs] = s;
   2367               matchedCountDocs++;
   2368             }
   2369           }
   2370 
   2371 
   2372           // Search for Samples
   2373           for (var i=0; i<SAMPLES_RESOURCES.length; i++) {
   2374             // current search comparison, with counters for tag and title,
   2375             // used later to improve ranking
   2376             var s = SAMPLES_RESOURCES[i];
   2377             s.matched_tag = 0;
   2378             s.matched_title = 0;
   2379             var matched = false;
   2380             // Check if query matches any tags; work backwards toward 1 to assist ranking
   2381             for (var j = s.keywords.length - 1; j >= 0; j--) {
   2382               // it matches a tag
   2383               if (s.keywords[j].toLowerCase().match(textRegex)) {
   2384                 matched = true;
   2385                 s.matched_tag = j + 1; // add 1 to index position
   2386               }
   2387             }
   2388             // Check if query matches the doc title, but only for current language
   2389             if (s.lang == currentLang) {
   2390               // if query matches the doc title.t
   2391               if (s.title.toLowerCase().match(textRegex)) {
   2392                 matched = true;
   2393                 s.matched_title = 1;
   2394               }
   2395             }
   2396             if (matched) {
   2397               gDocsMatches[matchedCountDocs] = s;
   2398               matchedCountDocs++;
   2399             }
   2400           }
   2401 
   2402           // Rank/sort all the matched pages
   2403           rank_autocomplete_doc_results(text, gDocsMatches);
   2404         }
   2405 
   2406         // draw the suggestions
   2407         sync_selection_table(toroot);
   2408         return true; // allow the event to bubble up to the search api
   2409     }
   2410 }
   2411 
   2412 /* Order the jd doc result list based on match quality */
   2413 function rank_autocomplete_doc_results(query, matches) {
   2414     query = query || '';
   2415     if (!matches || !matches.length)
   2416       return;
   2417 
   2418     var _resultScoreFn = function(match) {
   2419         var score = 1.0;
   2420 
   2421         // if the query matched a tag
   2422         if (match.matched_tag > 0) {
   2423           // multiply score by factor relative to position in tags list (max of 3)
   2424           score *= 3 / match.matched_tag;
   2425 
   2426           // if it also matched the title
   2427           if (match.matched_title > 0) {
   2428             score *= 2;
   2429           }
   2430         } else if (match.matched_title > 0) {
   2431           score *= 3;
   2432         }
   2433 
   2434         return score;
   2435     };
   2436 
   2437     for (var i=0; i<matches.length; i++) {
   2438         matches[i].__resultScore = _resultScoreFn(matches[i]);
   2439     }
   2440 
   2441     matches.sort(function(a,b){
   2442         var n = b.__resultScore - a.__resultScore;
   2443         if (n == 0) // lexicographical sort if scores are the same
   2444             n = (a.label < b.label) ? -1 : 1;
   2445         return n;
   2446     });
   2447 }
   2448 
   2449 /* Order the result list based on match quality */
   2450 function rank_autocomplete_api_results(query, matches) {
   2451     query = query || '';
   2452     if (!matches || !matches.length)
   2453       return;
   2454 
   2455     // helper function that gets the last occurence index of the given regex
   2456     // in the given string, or -1 if not found
   2457     var _lastSearch = function(s, re) {
   2458       if (s == '')
   2459         return -1;
   2460       var l = -1;
   2461       var tmp;
   2462       while ((tmp = s.search(re)) >= 0) {
   2463         if (l < 0) l = 0;
   2464         l += tmp;
   2465         s = s.substr(tmp + 1);
   2466       }
   2467       return l;
   2468     };
   2469 
   2470     // helper function that counts the occurrences of a given character in
   2471     // a given string
   2472     var _countChar = function(s, c) {
   2473       var n = 0;
   2474       for (var i=0; i<s.length; i++)
   2475         if (s.charAt(i) == c) ++n;
   2476       return n;
   2477     };
   2478 
   2479     var queryLower = query.toLowerCase();
   2480     var queryAlnum = (queryLower.match(/\w+/) || [''])[0];
   2481     var partPrefixAlnumRE = new RegExp('\\b' + queryAlnum);
   2482     var partExactAlnumRE = new RegExp('\\b' + queryAlnum + '\\b');
   2483 
   2484     var _resultScoreFn = function(result) {
   2485         // scores are calculated based on exact and prefix matches,
   2486         // and then number of path separators (dots) from the last
   2487         // match (i.e. favoring classes and deep package names)
   2488         var score = 1.0;
   2489         var labelLower = result.label.toLowerCase();
   2490         var t;
   2491         t = _lastSearch(labelLower, partExactAlnumRE);
   2492         if (t >= 0) {
   2493             // exact part match
   2494             var partsAfter = _countChar(labelLower.substr(t + 1), '.');
   2495             score *= 200 / (partsAfter + 1);
   2496         } else {
   2497             t = _lastSearch(labelLower, partPrefixAlnumRE);
   2498             if (t >= 0) {
   2499                 // part prefix match
   2500                 var partsAfter = _countChar(labelLower.substr(t + 1), '.');
   2501                 score *= 20 / (partsAfter + 1);
   2502             }
   2503         }
   2504 
   2505         return score;
   2506     };
   2507 
   2508     for (var i=0; i<matches.length; i++) {
   2509         // if the API is deprecated, default score is 0; otherwise, perform scoring
   2510         if (matches[i].deprecated == "true") {
   2511           matches[i].__resultScore = 0;
   2512         } else {
   2513           matches[i].__resultScore = _resultScoreFn(matches[i]);
   2514         }
   2515     }
   2516 
   2517     matches.sort(function(a,b){
   2518         var n = b.__resultScore - a.__resultScore;
   2519         if (n == 0) // lexicographical sort if scores are the same
   2520             n = (a.label < b.label) ? -1 : 1;
   2521         return n;
   2522     });
   2523 }
   2524 
   2525 /* Add emphasis to part of string that matches query */
   2526 function highlight_autocomplete_result_labels(query) {
   2527     query = query || '';
   2528     if ((!gMatches || !gMatches.length) && (!gGoogleMatches || !gGoogleMatches.length))
   2529       return;
   2530 
   2531     var queryLower = query.toLowerCase();
   2532     var queryAlnumDot = (queryLower.match(/[\w\.]+/) || [''])[0];
   2533     var queryRE = new RegExp(
   2534         '(' + queryAlnumDot.replace(/\./g, '\\.') + ')', 'ig');
   2535     for (var i=0; i<gMatches.length; i++) {
   2536         gMatches[i].__hilabel = gMatches[i].label.replace(
   2537             queryRE, '<b>$1</b>');
   2538     }
   2539     for (var i=0; i<gGoogleMatches.length; i++) {
   2540         gGoogleMatches[i].__hilabel = gGoogleMatches[i].label.replace(
   2541             queryRE, '<b>$1</b>');
   2542     }
   2543 }
   2544 
   2545 function search_focus_changed(obj, focused)
   2546 {
   2547     if (!focused) {
   2548         if(obj.value == ""){
   2549           $(".search .close").addClass("hide");
   2550         }
   2551         $(".suggest-card").hide();
   2552     }
   2553 }
   2554 
   2555 function submit_search() {
   2556   var query = document.getElementById('search_autocomplete').value;
   2557   location.hash = 'q=' + query;
   2558   loadSearchResults();
   2559   $("#searchResults").slideDown('slow', setStickyTop);
   2560   return false;
   2561 }
   2562 
   2563 
   2564 function hideResults() {
   2565   $("#searchResults").slideUp('fast', setStickyTop);
   2566   $(".search .close").addClass("hide");
   2567   location.hash = '';
   2568 
   2569   $("#search_autocomplete").val("").blur();
   2570 
   2571   // reset the ajax search callback to nothing, so results don't appear unless ENTER
   2572   searchControl.setSearchStartingCallback(this, function(control, searcher, query) {});
   2573 
   2574   // forcefully regain key-up event control (previously jacked by search api)
   2575   $("#search_autocomplete").keyup(function(event) {
   2576     return search_changed(event, false, toRoot);
   2577   });
   2578 
   2579   return false;
   2580 }
   2581 
   2582 
   2583 
   2584 /* ########################################################## */
   2585 /* ################  CUSTOM SEARCH ENGINE  ################## */
   2586 /* ########################################################## */
   2587 
   2588 var searchControl;
   2589 google.load('search', '1', {"callback" : function() {
   2590             searchControl = new google.search.SearchControl();
   2591           } });
   2592 
   2593 function loadSearchResults() {
   2594   document.getElementById("search_autocomplete").style.color = "#000";
   2595 
   2596   searchControl = new google.search.SearchControl();
   2597 
   2598   // use our existing search form and use tabs when multiple searchers are used
   2599   drawOptions = new google.search.DrawOptions();
   2600   drawOptions.setDrawMode(google.search.SearchControl.DRAW_MODE_TABBED);
   2601   drawOptions.setInput(document.getElementById("search_autocomplete"));
   2602 
   2603   // configure search result options
   2604   searchOptions = new google.search.SearcherOptions();
   2605   searchOptions.setExpandMode(GSearchControl.EXPAND_MODE_OPEN);
   2606 
   2607   // configure each of the searchers, for each tab
   2608   devSiteSearcher = new google.search.WebSearch();
   2609   devSiteSearcher.setUserDefinedLabel("All");
   2610   devSiteSearcher.setSiteRestriction("001482626316274216503:zu90b7s047u");
   2611 
   2612   designSearcher = new google.search.WebSearch();
   2613   designSearcher.setUserDefinedLabel("Design");
   2614   designSearcher.setSiteRestriction("http://developer.android.com/design/");
   2615 
   2616   trainingSearcher = new google.search.WebSearch();
   2617   trainingSearcher.setUserDefinedLabel("Training");
   2618   trainingSearcher.setSiteRestriction("http://developer.android.com/training/");
   2619 
   2620   guidesSearcher = new google.search.WebSearch();
   2621   guidesSearcher.setUserDefinedLabel("Guides");
   2622   guidesSearcher.setSiteRestriction("http://developer.android.com/guide/");
   2623 
   2624   referenceSearcher = new google.search.WebSearch();
   2625   referenceSearcher.setUserDefinedLabel("Reference");
   2626   referenceSearcher.setSiteRestriction("http://developer.android.com/reference/");
   2627 
   2628   googleSearcher = new google.search.WebSearch();
   2629   googleSearcher.setUserDefinedLabel("Google Services");
   2630   googleSearcher.setSiteRestriction("http://developer.android.com/google/");
   2631 
   2632   blogSearcher = new google.search.WebSearch();
   2633   blogSearcher.setUserDefinedLabel("Blog");
   2634   blogSearcher.setSiteRestriction("http://android-developers.blogspot.com");
   2635 
   2636   // add each searcher to the search control
   2637   searchControl.addSearcher(devSiteSearcher, searchOptions);
   2638   searchControl.addSearcher(designSearcher, searchOptions);
   2639   searchControl.addSearcher(trainingSearcher, searchOptions);
   2640   searchControl.addSearcher(guidesSearcher, searchOptions);
   2641   searchControl.addSearcher(referenceSearcher, searchOptions);
   2642   searchControl.addSearcher(googleSearcher, searchOptions);
   2643   searchControl.addSearcher(blogSearcher, searchOptions);
   2644 
   2645   // configure result options
   2646   searchControl.setResultSetSize(google.search.Search.LARGE_RESULTSET);
   2647   searchControl.setLinkTarget(google.search.Search.LINK_TARGET_SELF);
   2648   searchControl.setTimeoutInterval(google.search.SearchControl.TIMEOUT_SHORT);
   2649   searchControl.setNoResultsString(google.search.SearchControl.NO_RESULTS_DEFAULT_STRING);
   2650 
   2651   // upon ajax search, refresh the url and search title
   2652   searchControl.setSearchStartingCallback(this, function(control, searcher, query) {
   2653     updateResultTitle(query);
   2654     var query = document.getElementById('search_autocomplete').value;
   2655     location.hash = 'q=' + query;
   2656   });
   2657 
   2658   // once search results load, set up click listeners
   2659   searchControl.setSearchCompleteCallback(this, function(control, searcher, query) {
   2660     addResultClickListeners();
   2661   });
   2662 
   2663   // draw the search results box
   2664   searchControl.draw(document.getElementById("leftSearchControl"), drawOptions);
   2665 
   2666   // get query and execute the search
   2667   searchControl.execute(decodeURI(getQuery(location.hash)));
   2668 
   2669   document.getElementById("search_autocomplete").focus();
   2670   addTabListeners();
   2671 }
   2672 // End of loadSearchResults
   2673 
   2674 
   2675 google.setOnLoadCallback(function(){
   2676   if (location.hash.indexOf("q=") == -1) {
   2677     // if there's no query in the url, don't search and make sure results are hidden
   2678     $('#searchResults').hide();
   2679     return;
   2680   } else {
   2681     // first time loading search results for this page
   2682     $('#searchResults').slideDown('slow', setStickyTop);
   2683     $(".search .close").removeClass("hide");
   2684     loadSearchResults();
   2685   }
   2686 }, true);
   2687 
   2688 /* Adjust the scroll position to account for sticky header, only if the hash matches an id.
   2689    This does not handle <a name=""> tags. Some CSS fixes those, but only for reference docs. */
   2690 function offsetScrollForSticky() {
   2691   // Ignore if there's no search bar (some special pages have no header)
   2692   if ($("#search-container").length < 1) return;
   2693 
   2694   var hash = escape(location.hash.substr(1));
   2695   var $matchingElement = $("#"+hash);
   2696   // Sanity check that there's an element with that ID on the page
   2697   if ($matchingElement.length) {
   2698     // If the position of the target element is near the top of the page (<20px, where we expect it
   2699     // to be because we need to move it down 60px to become in view), then move it down 60px
   2700     if (Math.abs($matchingElement.offset().top - $(window).scrollTop()) < 20) {
   2701       $(window).scrollTop($(window).scrollTop() - 60);
   2702     }
   2703   }
   2704 }
   2705 
   2706 // when an event on the browser history occurs (back, forward, load) requery hash and do search
   2707 $(window).hashchange( function(){
   2708   // Ignore if there's no search bar (some special pages have no header)
   2709   if ($("#search-container").length < 1) return;
   2710 
   2711   // If the hash isn't a search query or there's an error in the query,
   2712   // then adjust the scroll position to account for sticky header, then exit.
   2713   if ((location.hash.indexOf("q=") == -1) || (query == "undefined")) {
   2714     // If the results pane is open, close it.
   2715     if (!$("#searchResults").is(":hidden")) {
   2716       hideResults();
   2717     }
   2718     offsetScrollForSticky();
   2719     return;
   2720   }
   2721 
   2722   // Otherwise, we have a search to do
   2723   var query = decodeURI(getQuery(location.hash));
   2724   searchControl.execute(query);
   2725   $('#searchResults').slideDown('slow', setStickyTop);
   2726   $("#search_autocomplete").focus();
   2727   $(".search .close").removeClass("hide");
   2728 
   2729   updateResultTitle(query);
   2730 });
   2731 
   2732 function updateResultTitle(query) {
   2733   $("#searchTitle").html("Results for <em>" + escapeHTML(query) + "</em>");
   2734 }
   2735 
   2736 // forcefully regain key-up event control (previously jacked by search api)
   2737 $("#search_autocomplete").keyup(function(event) {
   2738   return search_changed(event, false, toRoot);
   2739 });
   2740 
   2741 // add event listeners to each tab so we can track the browser history
   2742 function addTabListeners() {
   2743   var tabHeaders = $(".gsc-tabHeader");
   2744   for (var i = 0; i < tabHeaders.length; i++) {
   2745     $(tabHeaders[i]).attr("id",i).click(function() {
   2746     /*
   2747       // make a copy of the page numbers for the search left pane
   2748       setTimeout(function() {
   2749         // remove any residual page numbers
   2750         $('#searchResults .gsc-tabsArea .gsc-cursor-box.gs-bidi-start-align').remove();
   2751         // move the page numbers to the left position; make a clone,
   2752         // because the element is drawn to the DOM only once
   2753         // and because we're going to remove it (previous line),
   2754         // we need it to be available to move again as the user navigates
   2755         $('#searchResults .gsc-webResult .gsc-cursor-box.gs-bidi-start-align:visible')
   2756                         .clone().appendTo('#searchResults .gsc-tabsArea');
   2757         }, 200);
   2758       */
   2759     });
   2760   }
   2761   setTimeout(function(){$(tabHeaders[0]).click()},200);
   2762 }
   2763 
   2764 // add analytics tracking events to each result link
   2765 function addResultClickListeners() {
   2766   $("#searchResults a.gs-title").each(function(index, link) {
   2767     // When user clicks enter for Google search results, track it
   2768     $(link).click(function() {
   2769       ga('send', 'event', 'Google Click', 'clicked: ' + $(this).attr('href'),
   2770                 'query: ' + $("#search_autocomplete").val().toLowerCase());
   2771     });
   2772   });
   2773 }
   2774 
   2775 
   2776 function getQuery(hash) {
   2777   var queryParts = hash.split('=');
   2778   return queryParts[1];
   2779 }
   2780 
   2781 /* returns the given string with all HTML brackets converted to entities
   2782     TODO: move this to the site's JS library */
   2783 function escapeHTML(string) {
   2784   return string.replace(/</g,"&lt;")
   2785                 .replace(/>/g,"&gt;");
   2786 }
   2787 
   2788 
   2789 
   2790 
   2791 
   2792 
   2793 
   2794 /* ######################################################## */
   2795 /* #################  JAVADOC REFERENCE ################### */
   2796 /* ######################################################## */
   2797 
   2798 /* Initialize some droiddoc stuff, but only if we're in the reference */
   2799 if (location.pathname.indexOf("/reference") == 0) {
   2800   if(!(location.pathname.indexOf("/reference-gms/packages.html") == 0)
   2801     && !(location.pathname.indexOf("/reference-gcm/packages.html") == 0)
   2802     && !(location.pathname.indexOf("/reference/com/google") == 0)) {
   2803     $(document).ready(function() {
   2804       // init available apis based on user pref
   2805       changeApiLevel();
   2806       initSidenavHeightResize()
   2807       });
   2808   }
   2809 }
   2810 
   2811 var API_LEVEL_COOKIE = "api_level";
   2812 var minLevel = 1;
   2813 var maxLevel = 1;
   2814 
   2815 /******* SIDENAV DIMENSIONS ************/
   2816 
   2817   function initSidenavHeightResize() {
   2818     // Change the drag bar size to nicely fit the scrollbar positions
   2819     var $dragBar = $(".ui-resizable-s");
   2820     $dragBar.css({'width': $dragBar.parent().width() - 5 + "px"});
   2821 
   2822     $( "#resize-packages-nav" ).resizable({
   2823       containment: "#nav-panels",
   2824       handles: "s",
   2825       alsoResize: "#packages-nav",
   2826       resize: function(event, ui) { resizeNav(); }, /* resize the nav while dragging */
   2827       stop: function(event, ui) { saveNavPanels(); } /* once stopped, save the sizes to cookie  */
   2828       });
   2829 
   2830   }
   2831 
   2832 function updateSidenavFixedWidth() {
   2833   if (!sticky) return;
   2834   $('#devdoc-nav').css({
   2835     'width' : $('#side-nav').css('width'),
   2836     'margin' : $('#side-nav').css('margin')
   2837   });
   2838   $('#devdoc-nav a.totop').css({'display':'block','width':$("#nav").innerWidth()+'px'});
   2839 
   2840   initSidenavHeightResize();
   2841 }
   2842 
   2843 function updateSidenavFullscreenWidth() {
   2844   if (!sticky) return;
   2845   $('#devdoc-nav').css({
   2846     'width' : $('#side-nav').css('width'),
   2847     'margin' : $('#side-nav').css('margin')
   2848   });
   2849   $('#devdoc-nav .totop').css({'left': 'inherit'});
   2850 
   2851   initSidenavHeightResize();
   2852 }
   2853 
   2854 function buildApiLevelSelector() {
   2855   maxLevel = SINCE_DATA.length;
   2856   var userApiLevel = parseInt(readCookie(API_LEVEL_COOKIE));
   2857   userApiLevel = userApiLevel == 0 ? maxLevel : userApiLevel; // If there's no cookie (zero), use the max by default
   2858 
   2859   minLevel = parseInt($("#doc-api-level").attr("class"));
   2860   // Handle provisional api levels; the provisional level will always be the highest possible level
   2861   // Provisional api levels will also have a length; other stuff that's just missing a level won't,
   2862   // so leave those kinds of entities at the default level of 1 (for example, the R.styleable class)
   2863   if (isNaN(minLevel) && minLevel.length) {
   2864     minLevel = maxLevel;
   2865   }
   2866   var select = $("#apiLevelSelector").html("").change(changeApiLevel);
   2867   for (var i = maxLevel-1; i >= 0; i--) {
   2868     var option = $("<option />").attr("value",""+SINCE_DATA[i]).append(""+SINCE_DATA[i]);
   2869   //  if (SINCE_DATA[i] < minLevel) option.addClass("absent"); // always false for strings (codenames)
   2870     select.append(option);
   2871   }
   2872 
   2873   // get the DOM element and use setAttribute cuz IE6 fails when using jquery .attr('selected',true)
   2874   var selectedLevelItem = $("#apiLevelSelector option[value='"+userApiLevel+"']").get(0);
   2875   selectedLevelItem.setAttribute('selected',true);
   2876 }
   2877 
   2878 function changeApiLevel() {
   2879   maxLevel = SINCE_DATA.length;
   2880   var selectedLevel = maxLevel;
   2881 
   2882   selectedLevel = parseInt($("#apiLevelSelector option:selected").val());
   2883   toggleVisisbleApis(selectedLevel, "body");
   2884 
   2885   writeCookie(API_LEVEL_COOKIE, selectedLevel, null);
   2886 
   2887   if (selectedLevel < minLevel) {
   2888     var thing = ($("#jd-header").html().indexOf("package") != -1) ? "package" : "class";
   2889     $("#naMessage").show().html("<div><p><strong>This " + thing
   2890               + " requires API level " + minLevel + " or higher.</strong></p>"
   2891               + "<p>This document is hidden because your selected API level for the documentation is "
   2892               + selectedLevel + ". You can change the documentation API level with the selector "
   2893               + "above the left navigation.</p>"
   2894               + "<p>For more information about specifying the API level your app requires, "
   2895               + "read <a href='" + toRoot + "training/basics/supporting-devices/platforms.html'"
   2896               + ">Supporting Different Platform Versions</a>.</p>"
   2897               + "<input type='button' value='OK, make this page visible' "
   2898               + "title='Change the API level to " + minLevel + "' "
   2899               + "onclick='$(\"#apiLevelSelector\").val(\"" + minLevel + "\");changeApiLevel();' />"
   2900               + "</div>");
   2901   } else {
   2902     $("#naMessage").hide();
   2903   }
   2904 }
   2905 
   2906 function toggleVisisbleApis(selectedLevel, context) {
   2907   var apis = $(".api",context);
   2908   apis.each(function(i) {
   2909     var obj = $(this);
   2910     var className = obj.attr("class");
   2911     var apiLevelIndex = className.lastIndexOf("-")+1;
   2912     var apiLevelEndIndex = className.indexOf(" ", apiLevelIndex);
   2913     apiLevelEndIndex = apiLevelEndIndex != -1 ? apiLevelEndIndex : className.length;
   2914     var apiLevel = className.substring(apiLevelIndex, apiLevelEndIndex);
   2915     if (apiLevel.length == 0) { // for odd cases when the since data is actually missing, just bail
   2916       return;
   2917     }
   2918     apiLevel = parseInt(apiLevel);
   2919 
   2920     // Handle provisional api levels; if this item's level is the provisional one, set it to the max
   2921     var selectedLevelNum = parseInt(selectedLevel)
   2922     var apiLevelNum = parseInt(apiLevel);
   2923     if (isNaN(apiLevelNum)) {
   2924         apiLevelNum = maxLevel;
   2925     }
   2926 
   2927     // Grey things out that aren't available and give a tooltip title
   2928     if (apiLevelNum > selectedLevelNum) {
   2929       obj.addClass("absent").attr("title","Requires API Level \""
   2930             + apiLevel + "\" or higher. To reveal, change the target API level "
   2931               + "above the left navigation.");
   2932     }
   2933     else obj.removeClass("absent").removeAttr("title");
   2934   });
   2935 }
   2936 
   2937 
   2938 
   2939 
   2940 /* #################  SIDENAV TREE VIEW ################### */
   2941 
   2942 function new_node(me, mom, text, link, children_data, api_level)
   2943 {
   2944   var node = new Object();
   2945   node.children = Array();
   2946   node.children_data = children_data;
   2947   node.depth = mom.depth + 1;
   2948 
   2949   node.li = document.createElement("li");
   2950   mom.get_children_ul().appendChild(node.li);
   2951 
   2952   node.label_div = document.createElement("div");
   2953   node.label_div.className = "label";
   2954   if (api_level != null) {
   2955     $(node.label_div).addClass("api");
   2956     $(node.label_div).addClass("api-level-"+api_level);
   2957   }
   2958   node.li.appendChild(node.label_div);
   2959 
   2960   if (children_data != null) {
   2961     node.expand_toggle = document.createElement("a");
   2962     node.expand_toggle.href = "javascript:void(0)";
   2963     node.expand_toggle.onclick = function() {
   2964           if (node.expanded) {
   2965             $(node.get_children_ul()).slideUp("fast");
   2966             node.plus_img.src = me.toroot + "assets/images/triangle-closed-small.png";
   2967             node.expanded = false;
   2968           } else {
   2969             expand_node(me, node);
   2970           }
   2971        };
   2972     node.label_div.appendChild(node.expand_toggle);
   2973 
   2974     node.plus_img = document.createElement("img");
   2975     node.plus_img.src = me.toroot + "assets/images/triangle-closed-small.png";
   2976     node.plus_img.className = "plus";
   2977     node.plus_img.width = "8";
   2978     node.plus_img.border = "0";
   2979     node.expand_toggle.appendChild(node.plus_img);
   2980 
   2981     node.expanded = false;
   2982   }
   2983 
   2984   var a = document.createElement("a");
   2985   node.label_div.appendChild(a);
   2986   node.label = document.createTextNode(text);
   2987   a.appendChild(node.label);
   2988   if (link) {
   2989     a.href = me.toroot + link;
   2990   } else {
   2991     if (children_data != null) {
   2992       a.className = "nolink";
   2993       a.href = "javascript:void(0)";
   2994       a.onclick = node.expand_toggle.onclick;
   2995       // This next line shouldn't be necessary.  I'll buy a beer for the first
   2996       // person who figures out how to remove this line and have the link
   2997       // toggle shut on the first try. --joeo (a] android.com
   2998       node.expanded = false;
   2999     }
   3000   }
   3001 
   3002 
   3003   node.children_ul = null;
   3004   node.get_children_ul = function() {
   3005       if (!node.children_ul) {
   3006         node.children_ul = document.createElement("ul");
   3007         node.children_ul.className = "children_ul";
   3008         node.children_ul.style.display = "none";
   3009         node.li.appendChild(node.children_ul);
   3010       }
   3011       return node.children_ul;
   3012     };
   3013 
   3014   return node;
   3015 }
   3016 
   3017 
   3018 
   3019 
   3020 function expand_node(me, node)
   3021 {
   3022   if (node.children_data && !node.expanded) {
   3023     if (node.children_visited) {
   3024       $(node.get_children_ul()).slideDown("fast");
   3025     } else {
   3026       get_node(me, node);
   3027       if ($(node.label_div).hasClass("absent")) {
   3028         $(node.get_children_ul()).addClass("absent");
   3029       }
   3030       $(node.get_children_ul()).slideDown("fast");
   3031     }
   3032     node.plus_img.src = me.toroot + "assets/images/triangle-opened-small.png";
   3033     node.expanded = true;
   3034 
   3035     // perform api level toggling because new nodes are new to the DOM
   3036     var selectedLevel = $("#apiLevelSelector option:selected").val();
   3037     toggleVisisbleApis(selectedLevel, "#side-nav");
   3038   }
   3039 }
   3040 
   3041 function get_node(me, mom)
   3042 {
   3043   mom.children_visited = true;
   3044   for (var i in mom.children_data) {
   3045     var node_data = mom.children_data[i];
   3046     mom.children[i] = new_node(me, mom, node_data[0], node_data[1],
   3047         node_data[2], node_data[3]);
   3048   }
   3049 }
   3050 
   3051 function this_page_relative(toroot)
   3052 {
   3053   var full = document.location.pathname;
   3054   var file = "";
   3055   if (toroot.substr(0, 1) == "/") {
   3056     if (full.substr(0, toroot.length) == toroot) {
   3057       return full.substr(toroot.length);
   3058     } else {
   3059       // the file isn't under toroot.  Fail.
   3060       return null;
   3061     }
   3062   } else {
   3063     if (toroot != "./") {
   3064       toroot = "./" + toroot;
   3065     }
   3066     do {
   3067       if (toroot.substr(toroot.length-3, 3) == "../" || toroot == "./") {
   3068         var pos = full.lastIndexOf("/");
   3069         file = full.substr(pos) + file;
   3070         full = full.substr(0, pos);
   3071         toroot = toroot.substr(0, toroot.length-3);
   3072       }
   3073     } while (toroot != "" && toroot != "/");
   3074     return file.substr(1);
   3075   }
   3076 }
   3077 
   3078 function find_page(url, data)
   3079 {
   3080   var nodes = data;
   3081   var result = null;
   3082   for (var i in nodes) {
   3083     var d = nodes[i];
   3084     if (d[1] == url) {
   3085       return new Array(i);
   3086     }
   3087     else if (d[2] != null) {
   3088       result = find_page(url, d[2]);
   3089       if (result != null) {
   3090         return (new Array(i).concat(result));
   3091       }
   3092     }
   3093   }
   3094   return null;
   3095 }
   3096 
   3097 function init_default_navtree(toroot) {
   3098   // load json file for navtree data
   3099   $.getScript(toRoot + 'navtree_data.js', function(data, textStatus, jqxhr) {
   3100       // when the file is loaded, initialize the tree
   3101       if(jqxhr.status === 200) {
   3102           init_navtree("tree-list", toroot, NAVTREE_DATA);
   3103       }
   3104   });
   3105 
   3106   // perform api level toggling because because the whole tree is new to the DOM
   3107   var selectedLevel = $("#apiLevelSelector option:selected").val();
   3108   toggleVisisbleApis(selectedLevel, "#side-nav");
   3109 }
   3110 
   3111 function init_navtree(navtree_id, toroot, root_nodes)
   3112 {
   3113   var me = new Object();
   3114   me.toroot = toroot;
   3115   me.node = new Object();
   3116 
   3117   me.node.li = document.getElementById(navtree_id);
   3118   me.node.children_data = root_nodes;
   3119   me.node.children = new Array();
   3120   me.node.children_ul = document.createElement("ul");
   3121   me.node.get_children_ul = function() { return me.node.children_ul; };
   3122   //me.node.children_ul.className = "children_ul";
   3123   me.node.li.appendChild(me.node.children_ul);
   3124   me.node.depth = 0;
   3125 
   3126   get_node(me, me.node);
   3127 
   3128   me.this_page = this_page_relative(toroot);
   3129   me.breadcrumbs = find_page(me.this_page, root_nodes);
   3130   if (me.breadcrumbs != null && me.breadcrumbs.length != 0) {
   3131     var mom = me.node;
   3132     for (var i in me.breadcrumbs) {
   3133       var j = me.breadcrumbs[i];
   3134       mom = mom.children[j];
   3135       expand_node(me, mom);
   3136     }
   3137     mom.label_div.className = mom.label_div.className + " selected";
   3138     addLoadEvent(function() {
   3139       scrollIntoView("nav-tree");
   3140       });
   3141   }
   3142 }
   3143 
   3144 
   3145 
   3146 
   3147 
   3148 
   3149 
   3150 
   3151 /* TODO: eliminate redundancy with non-google functions */
   3152 function init_google_navtree(navtree_id, toroot, root_nodes)
   3153 {
   3154   var me = new Object();
   3155   me.toroot = toroot;
   3156   me.node = new Object();
   3157 
   3158   me.node.li = document.getElementById(navtree_id);
   3159   me.node.children_data = root_nodes;
   3160   me.node.children = new Array();
   3161   me.node.children_ul = document.createElement("ul");
   3162   me.node.get_children_ul = function() { return me.node.children_ul; };
   3163   //me.node.children_ul.className = "children_ul";
   3164   me.node.li.appendChild(me.node.children_ul);
   3165   me.node.depth = 0;
   3166 
   3167   get_google_node(me, me.node);
   3168 }
   3169 
   3170 function new_google_node(me, mom, text, link, children_data, api_level)
   3171 {
   3172   var node = new Object();
   3173   var child;
   3174   node.children = Array();
   3175   node.children_data = children_data;
   3176   node.depth = mom.depth + 1;
   3177   node.get_children_ul = function() {
   3178       if (!node.children_ul) {
   3179         node.children_ul = document.createElement("ul");
   3180         node.children_ul.className = "tree-list-children";
   3181         node.li.appendChild(node.children_ul);
   3182       }
   3183       return node.children_ul;
   3184     };
   3185   node.li = document.createElement("li");
   3186 
   3187   mom.get_children_ul().appendChild(node.li);
   3188 
   3189 
   3190   if(link) {
   3191     child = document.createElement("a");
   3192 
   3193   }
   3194   else {
   3195     child = document.createElement("span");
   3196     child.className = "tree-list-subtitle";
   3197 
   3198   }
   3199   if (children_data != null) {
   3200     node.li.className="nav-section";
   3201     node.label_div = document.createElement("div");
   3202     node.label_div.className = "nav-section-header-ref";
   3203     node.li.appendChild(node.label_div);
   3204     get_google_node(me, node);
   3205     node.label_div.appendChild(child);
   3206   }
   3207   else {
   3208     node.li.appendChild(child);
   3209   }
   3210   if(link) {
   3211     child.href = me.toroot + link;
   3212   }
   3213   node.label = document.createTextNode(text);
   3214   child.appendChild(node.label);
   3215 
   3216   node.children_ul = null;
   3217 
   3218   return node;
   3219 }
   3220 
   3221 function get_google_node(me, mom)
   3222 {
   3223   mom.children_visited = true;
   3224   var linkText;
   3225   for (var i in mom.children_data) {
   3226     var node_data = mom.children_data[i];
   3227     linkText = node_data[0];
   3228 
   3229     if(linkText.match("^"+"com.google.android")=="com.google.android"){
   3230       linkText = linkText.substr(19, linkText.length);
   3231     }
   3232       mom.children[i] = new_google_node(me, mom, linkText, node_data[1],
   3233           node_data[2], node_data[3]);
   3234   }
   3235 }
   3236 
   3237 
   3238 
   3239 
   3240 
   3241 
   3242 /****** NEW version of script to build google and sample navs dynamically ******/
   3243 // TODO: update Google reference docs to tolerate this new implementation
   3244 
   3245 var NODE_NAME = 0;
   3246 var NODE_HREF = 1;
   3247 var NODE_GROUP = 2;
   3248 var NODE_TAGS = 3;
   3249 var NODE_CHILDREN = 4;
   3250 
   3251 function init_google_navtree2(navtree_id, data)
   3252 {
   3253   var $containerUl = $("#"+navtree_id);
   3254   for (var i in data) {
   3255     var node_data = data[i];
   3256     $containerUl.append(new_google_node2(node_data));
   3257   }
   3258 
   3259   // Make all third-generation list items 'sticky' to prevent them from collapsing
   3260   $containerUl.find('li li li.nav-section').addClass('sticky');
   3261 
   3262   initExpandableNavItems("#"+navtree_id);
   3263 }
   3264 
   3265 function new_google_node2(node_data)
   3266 {
   3267   var linkText = node_data[NODE_NAME];
   3268   if(linkText.match("^"+"com.google.android")=="com.google.android"){
   3269     linkText = linkText.substr(19, linkText.length);
   3270   }
   3271   var $li = $('<li>');
   3272   var $a;
   3273   if (node_data[NODE_HREF] != null) {
   3274     $a = $('<a href="' + toRoot + node_data[NODE_HREF] + '" title="' + linkText + '" >'
   3275         + linkText + '</a>');
   3276   } else {
   3277     $a = $('<a href="#" onclick="return false;" title="' + linkText + '" >'
   3278         + linkText + '/</a>');
   3279   }
   3280   var $childUl = $('<ul>');
   3281   if (node_data[NODE_CHILDREN] != null) {
   3282     $li.addClass("nav-section");
   3283     $a = $('<div class="nav-section-header">').append($a);
   3284     if (node_data[NODE_HREF] == null) $a.addClass('empty');
   3285 
   3286     for (var i in node_data[NODE_CHILDREN]) {
   3287       var child_node_data = node_data[NODE_CHILDREN][i];
   3288       $childUl.append(new_google_node2(child_node_data));
   3289     }
   3290     $li.append($childUl);
   3291   }
   3292   $li.prepend($a);
   3293 
   3294   return $li;
   3295 }
   3296 
   3297 
   3298 
   3299 
   3300 
   3301 
   3302 
   3303 
   3304 
   3305 
   3306 
   3307 function showGoogleRefTree() {
   3308   init_default_google_navtree(toRoot);
   3309   init_default_gcm_navtree(toRoot);
   3310 }
   3311 
   3312 function init_default_google_navtree(toroot) {
   3313   // load json file for navtree data
   3314   $.getScript(toRoot + 'gms_navtree_data.js', function(data, textStatus, jqxhr) {
   3315       // when the file is loaded, initialize the tree
   3316       if(jqxhr.status === 200) {
   3317           init_google_navtree("gms-tree-list", toroot, GMS_NAVTREE_DATA);
   3318           highlightSidenav();
   3319           resizeNav();
   3320       }
   3321   });
   3322 }
   3323 
   3324 function init_default_gcm_navtree(toroot) {
   3325   // load json file for navtree data
   3326   $.getScript(toRoot + 'gcm_navtree_data.js', function(data, textStatus, jqxhr) {
   3327       // when the file is loaded, initialize the tree
   3328       if(jqxhr.status === 200) {
   3329           init_google_navtree("gcm-tree-list", toroot, GCM_NAVTREE_DATA);
   3330           highlightSidenav();
   3331           resizeNav();
   3332       }
   3333   });
   3334 }
   3335 
   3336 function showSamplesRefTree() {
   3337   init_default_samples_navtree(toRoot);
   3338 }
   3339 
   3340 function init_default_samples_navtree(toroot) {
   3341   // load json file for navtree data
   3342   $.getScript(toRoot + 'samples_navtree_data.js', function(data, textStatus, jqxhr) {
   3343       // when the file is loaded, initialize the tree
   3344       if(jqxhr.status === 200) {
   3345           // hack to remove the "about the samples" link then put it back in
   3346           // after we nuke the list to remove the dummy static list of samples
   3347           var $firstLi = $("#nav.samples-nav > li:first-child").clone();
   3348           $("#nav.samples-nav").empty();
   3349           $("#nav.samples-nav").append($firstLi);
   3350 
   3351           init_google_navtree2("nav.samples-nav", SAMPLES_NAVTREE_DATA);
   3352           highlightSidenav();
   3353           resizeNav();
   3354           if ($("#jd-content #samples").length) {
   3355             showSamples();
   3356           }
   3357       }
   3358   });
   3359 }
   3360 
   3361 /* TOGGLE INHERITED MEMBERS */
   3362 
   3363 /* Toggle an inherited class (arrow toggle)
   3364  * @param linkObj  The link that was clicked.
   3365  * @param expand  'true' to ensure it's expanded. 'false' to ensure it's closed.
   3366  *                'null' to simply toggle.
   3367  */
   3368 function toggleInherited(linkObj, expand) {
   3369     var base = linkObj.getAttribute("id");
   3370     var list = document.getElementById(base + "-list");
   3371     var summary = document.getElementById(base + "-summary");
   3372     var trigger = document.getElementById(base + "-trigger");
   3373     var a = $(linkObj);
   3374     if ( (expand == null && a.hasClass("closed")) || expand ) {
   3375         list.style.display = "none";
   3376         summary.style.display = "block";
   3377         trigger.src = toRoot + "assets/images/triangle-opened.png";
   3378         a.removeClass("closed");
   3379         a.addClass("opened");
   3380     } else if ( (expand == null && a.hasClass("opened")) || (expand == false) ) {
   3381         list.style.display = "block";
   3382         summary.style.display = "none";
   3383         trigger.src = toRoot + "assets/images/triangle-closed.png";
   3384         a.removeClass("opened");
   3385         a.addClass("closed");
   3386     }
   3387     return false;
   3388 }
   3389 
   3390 /* Toggle all inherited classes in a single table (e.g. all inherited methods)
   3391  * @param linkObj  The link that was clicked.
   3392  * @param expand  'true' to ensure it's expanded. 'false' to ensure it's closed.
   3393  *                'null' to simply toggle.
   3394  */
   3395 function toggleAllInherited(linkObj, expand) {
   3396   var a = $(linkObj);
   3397   var table = $(a.parent().parent().parent()); // ugly way to get table/tbody
   3398   var expandos = $(".jd-expando-trigger", table);
   3399   if ( (expand == null && a.text() == "[Expand]") || expand ) {
   3400     expandos.each(function(i) {
   3401       toggleInherited(this, true);
   3402     });
   3403     a.text("[Collapse]");
   3404   } else if ( (expand == null && a.text() == "[Collapse]") || (expand == false) ) {
   3405     expandos.each(function(i) {
   3406       toggleInherited(this, false);
   3407     });
   3408     a.text("[Expand]");
   3409   }
   3410   return false;
   3411 }
   3412 
   3413 /* Toggle all inherited members in the class (link in the class title)
   3414  */
   3415 function toggleAllClassInherited() {
   3416   var a = $("#toggleAllClassInherited"); // get toggle link from class title
   3417   var toggles = $(".toggle-all", $("#body-content"));
   3418   if (a.text() == "[Expand All]") {
   3419     toggles.each(function(i) {
   3420       toggleAllInherited(this, true);
   3421     });
   3422     a.text("[Collapse All]");
   3423   } else {
   3424     toggles.each(function(i) {
   3425       toggleAllInherited(this, false);
   3426     });
   3427     a.text("[Expand All]");
   3428   }
   3429   return false;
   3430 }
   3431 
   3432 /* Expand all inherited members in the class. Used when initiating page search */
   3433 function ensureAllInheritedExpanded() {
   3434   var toggles = $(".toggle-all", $("#body-content"));
   3435   toggles.each(function(i) {
   3436     toggleAllInherited(this, true);
   3437   });
   3438   $("#toggleAllClassInherited").text("[Collapse All]");
   3439 }
   3440 
   3441 
   3442 /* HANDLE KEY EVENTS
   3443  * - Listen for Ctrl+F (Cmd on Mac) and expand all inherited members (to aid page search)
   3444  */
   3445 var agent = navigator['userAgent'].toLowerCase();
   3446 var mac = agent.indexOf("macintosh") != -1;
   3447 
   3448 $(document).keydown( function(e) {
   3449 var control = mac ? e.metaKey && !e.ctrlKey : e.ctrlKey; // get ctrl key
   3450   if (control && e.which == 70) {  // 70 is "F"
   3451     ensureAllInheritedExpanded();
   3452   }
   3453 });
   3454 
   3455 
   3456 
   3457 
   3458 
   3459 
   3460 /* On-demand functions */
   3461 
   3462 /** Move sample code line numbers out of PRE block and into non-copyable column */
   3463 function initCodeLineNumbers() {
   3464   var numbers = $("#codesample-block a.number");
   3465   if (numbers.length) {
   3466     $("#codesample-line-numbers").removeClass("hidden").append(numbers);
   3467   }
   3468 
   3469   $(document).ready(function() {
   3470     // select entire line when clicked
   3471     $("span.code-line").click(function() {
   3472       if (!shifted) {
   3473         selectText(this);
   3474       }
   3475     });
   3476     // invoke line link on double click
   3477     $(".code-line").dblclick(function() {
   3478       document.location.hash = $(this).attr('id');
   3479     });
   3480     // highlight the line when hovering on the number
   3481     $("#codesample-line-numbers a.number").mouseover(function() {
   3482       var id = $(this).attr('href');
   3483       $(id).css('background','#e7e7e7');
   3484     });
   3485     $("#codesample-line-numbers a.number").mouseout(function() {
   3486       var id = $(this).attr('href');
   3487       $(id).css('background','none');
   3488     });
   3489   });
   3490 }
   3491 
   3492 // create SHIFT key binder to avoid the selectText method when selecting multiple lines
   3493 var shifted = false;
   3494 $(document).bind('keyup keydown', function(e){shifted = e.shiftKey; return true;} );
   3495 
   3496 // courtesy of jasonedelman.com
   3497 function selectText(element) {
   3498     var doc = document
   3499         , range, selection
   3500     ;
   3501     if (doc.body.createTextRange) { //ms
   3502         range = doc.body.createTextRange();
   3503         range.moveToElementText(element);
   3504         range.select();
   3505     } else if (window.getSelection) { //all others
   3506         selection = window.getSelection();
   3507         range = doc.createRange();
   3508         range.selectNodeContents(element);
   3509         selection.removeAllRanges();
   3510         selection.addRange(range);
   3511     }
   3512 }
   3513 
   3514 
   3515 
   3516 
   3517 /** Display links and other information about samples that match the
   3518     group specified by the URL */
   3519 function showSamples() {
   3520   var group = $("#samples").attr('class');
   3521   $("#samples").html("<p>Here are some samples for <b>" + group + "</b> apps:</p>");
   3522 
   3523   var $ul = $("<ul>");
   3524   $selectedLi = $("#nav li.selected");
   3525 
   3526   $selectedLi.children("ul").children("li").each(function() {
   3527       var $li = $("<li>").append($(this).find("a").first().clone());
   3528       $ul.append($li);
   3529   });
   3530 
   3531   $("#samples").append($ul);
   3532 
   3533 }
   3534 
   3535 
   3536 
   3537 /* ########################################################## */
   3538 /* ###################  RESOURCE CARDS  ##################### */
   3539 /* ########################################################## */
   3540 
   3541 /** Handle resource queries, collections, and grids (sections). Requires
   3542     jd_tag_helpers.js and the *_unified_data.js to be loaded. */
   3543 
   3544 (function() {
   3545   // Prevent the same resource from being loaded more than once per page.
   3546   var addedPageResources = {};
   3547 
   3548   $(document).ready(function() {
   3549     $('.resource-widget').each(function() {
   3550       initResourceWidget(this);
   3551     });
   3552 
   3553     /* Pass the line height to ellipsisfade() to adjust the height of the
   3554     text container to show the max number of lines possible, without
   3555     showing lines that are cut off. This works with the css ellipsis
   3556     classes to fade last text line and apply an ellipsis char. */
   3557 
   3558     //card text currently uses 15px line height.
   3559     var lineHeight = 15;
   3560     $('.card-info .text').ellipsisfade(lineHeight);
   3561   });
   3562 
   3563   /*
   3564     Three types of resource layouts:
   3565     Flow - Uses a fixed row-height flow using float left style.
   3566     Carousel - Single card slideshow all same dimension absolute.
   3567     Stack - Uses fixed columns and flexible element height.
   3568   */
   3569   function initResourceWidget(widget) {
   3570     var $widget = $(widget);
   3571     var isFlow = $widget.hasClass('resource-flow-layout'),
   3572         isCarousel = $widget.hasClass('resource-carousel-layout'),
   3573         isStack = $widget.hasClass('resource-stack-layout');
   3574 
   3575     // find size of widget by pulling out its class name
   3576     var sizeCols = 1;
   3577     var m = $widget.get(0).className.match(/\bcol-(\d+)\b/);
   3578     if (m) {
   3579       sizeCols = parseInt(m[1], 10);
   3580     }
   3581 
   3582     var opts = {
   3583       cardSizes: ($widget.data('cardsizes') || '').split(','),
   3584       maxResults: parseInt($widget.data('maxresults') || '100', 10),
   3585       itemsPerPage: $widget.data('itemsperpage'),
   3586       sortOrder: $widget.data('sortorder'),
   3587       query: $widget.data('query'),
   3588       section: $widget.data('section'),
   3589       sizeCols: sizeCols,
   3590       /* Added by LFL 6/6/14 */
   3591       resourceStyle: $widget.data('resourcestyle') || 'card',
   3592       stackSort: $widget.data('stacksort') || 'true'
   3593     };
   3594 
   3595     // run the search for the set of resources to show
   3596 
   3597     var resources = buildResourceList(opts);
   3598 
   3599     if (isFlow) {
   3600       drawResourcesFlowWidget($widget, opts, resources);
   3601     } else if (isCarousel) {
   3602       drawResourcesCarouselWidget($widget, opts, resources);
   3603     } else if (isStack) {
   3604       /* Looks like this got removed and is not used, so repurposing for the
   3605           homepage style layout.
   3606           Modified by LFL 6/6/14
   3607       */
   3608       //var sections = buildSectionList(opts);
   3609       opts['numStacks'] = $widget.data('numstacks');
   3610       drawResourcesStackWidget($widget, opts, resources/*, sections*/);
   3611     }
   3612   }
   3613 
   3614   /* Initializes a Resource Carousel Widget */
   3615   function drawResourcesCarouselWidget($widget, opts, resources) {
   3616     $widget.empty();
   3617     var plusone = true; //always show plusone on carousel
   3618 
   3619     $widget.addClass('resource-card slideshow-container')
   3620       .append($('<a>').addClass('slideshow-prev').text('Prev'))
   3621       .append($('<a>').addClass('slideshow-next').text('Next'));
   3622 
   3623     var css = { 'width': $widget.width() + 'px',
   3624                 'height': $widget.height() + 'px' };
   3625 
   3626     var $ul = $('<ul>');
   3627 
   3628     for (var i = 0; i < resources.length; ++i) {
   3629       var $card = $('<a>')
   3630         .attr('href', cleanUrl(resources[i].url))
   3631         .decorateResourceCard(resources[i],plusone);
   3632 
   3633       $('<li>').css(css)
   3634           .append($card)
   3635           .appendTo($ul);
   3636     }
   3637 
   3638     $('<div>').addClass('frame')
   3639       .append($ul)
   3640       .appendTo($widget);
   3641 
   3642     $widget.dacSlideshow({
   3643       auto: true,
   3644       btnPrev: '.slideshow-prev',
   3645       btnNext: '.slideshow-next'
   3646     });
   3647   };
   3648 
   3649   /* Initializes a Resource Card Stack Widget (column-based layout)
   3650      Modified by LFL 6/6/14
   3651    */
   3652   function drawResourcesStackWidget($widget, opts, resources, sections) {
   3653     // Don't empty widget, grab all items inside since they will be the first
   3654     // items stacked, followed by the resource query
   3655     var plusone = true; //by default show plusone on section cards
   3656     var cards = $widget.find('.resource-card').detach().toArray();
   3657     var numStacks = opts.numStacks || 1;
   3658     var $stacks = [];
   3659     var urlString;
   3660 
   3661     for (var i = 0; i < numStacks; ++i) {
   3662       $stacks[i] = $('<div>').addClass('resource-card-stack')
   3663           .appendTo($widget);
   3664     }
   3665 
   3666     var sectionResources = [];
   3667 
   3668     // Extract any subsections that are actually resource cards
   3669     if (sections) {
   3670       for (var i = 0; i < sections.length; ++i) {
   3671         if (!sections[i].sections || !sections[i].sections.length) {
   3672           // Render it as a resource card
   3673           sectionResources.push(
   3674             $('<a>')
   3675               .addClass('resource-card section-card')
   3676               .attr('href', cleanUrl(sections[i].resource.url))
   3677               .decorateResourceCard(sections[i].resource,plusone)[0]
   3678           );
   3679 
   3680         } else {
   3681           cards.push(
   3682             $('<div>')
   3683               .addClass('resource-card section-card-menu')
   3684               .decorateResourceSection(sections[i],plusone)[0]
   3685           );
   3686         }
   3687       }
   3688     }
   3689 
   3690     cards = cards.concat(sectionResources);
   3691 
   3692     for (var i = 0; i < resources.length; ++i) {
   3693       var $card = createResourceElement(resources[i], opts);
   3694 
   3695       if (opts.resourceStyle.indexOf('related') > -1) {
   3696         $card.addClass('related-card');
   3697       }
   3698 
   3699       cards.push($card[0]);
   3700     }
   3701 
   3702     if (opts.stackSort != 'false') {
   3703       for (var i = 0; i < cards.length; ++i) {
   3704         // Find the stack with the shortest height, but give preference to
   3705         // left to right order.
   3706         var minHeight = $stacks[0].height();
   3707         var minIndex = 0;
   3708 
   3709         for (var j = 1; j < numStacks; ++j) {
   3710           var height = $stacks[j].height();
   3711           if (height < minHeight - 45) {
   3712             minHeight = height;
   3713             minIndex = j;
   3714           }
   3715         }
   3716 
   3717         $stacks[minIndex].append($(cards[i]));
   3718       }
   3719     }
   3720 
   3721   };
   3722 
   3723   /*
   3724     Create a resource card using the given resource object and a list of html
   3725      configured options. Returns a jquery object containing the element.
   3726   */
   3727   function createResourceElement(resource, opts, plusone) {
   3728     var $el;
   3729 
   3730     // The difference here is that generic cards are not entirely clickable
   3731     // so its a div instead of an a tag, also the generic one is not given
   3732     // the resource-card class so it appears with a transparent background
   3733     // and can be styled in whatever way the css setup.
   3734     if (opts.resourceStyle == 'generic') {
   3735       $el = $('<div>')
   3736         .addClass('resource')
   3737         .attr('href', cleanUrl(resource.url))
   3738         .decorateResource(resource, opts);
   3739     } else {
   3740       var cls = 'resource resource-card';
   3741 
   3742       $el = $('<a>')
   3743         .addClass(cls)
   3744         .attr('href', cleanUrl(resource.url))
   3745         .decorateResourceCard(resource, plusone);
   3746     }
   3747 
   3748     return $el;
   3749   }
   3750 
   3751   /* Initializes a flow widget, see distribute.scss for generating accompanying css */
   3752   function drawResourcesFlowWidget($widget, opts, resources) {
   3753     $widget.empty();
   3754     var cardSizes = opts.cardSizes || ['6x6'];
   3755     var i = 0, j = 0;
   3756     var plusone = true; // by default show plusone on resource cards
   3757 
   3758     while (i < resources.length) {
   3759       var cardSize = cardSizes[j++ % cardSizes.length];
   3760       cardSize = cardSize.replace(/^\s+|\s+$/,'');
   3761       // Some card sizes do not get a plusone button, such as where space is constrained
   3762       // or for cards commonly embedded in docs (to improve overall page speed).
   3763       plusone = !((cardSize == "6x2") || (cardSize == "6x3") ||
   3764                   (cardSize == "9x2") || (cardSize == "9x3") ||
   3765                   (cardSize == "12x2") || (cardSize == "12x3"));
   3766 
   3767       // A stack has a third dimension which is the number of stacked items
   3768       var isStack = cardSize.match(/(\d+)x(\d+)x(\d+)/);
   3769       var stackCount = 0;
   3770       var $stackDiv = null;
   3771 
   3772       if (isStack) {
   3773         // Create a stack container which should have the dimensions defined
   3774         // by the product of the items inside.
   3775         $stackDiv = $('<div>').addClass('resource-card-stack resource-card-' + isStack[1]
   3776             + 'x' + isStack[2] * isStack[3]) .appendTo($widget);
   3777       }
   3778 
   3779       // Build each stack item or just a single item
   3780       do {
   3781         var resource = resources[i];
   3782 
   3783         var $card = createResourceElement(resources[i], opts, plusone);
   3784 
   3785         $card.addClass('resource-card-' + cardSize +
   3786           ' resource-card-' + resource.type);
   3787 
   3788         if (isStack) {
   3789           $card.addClass('resource-card-' + isStack[1] + 'x' + isStack[2]);
   3790           if (++stackCount == parseInt(isStack[3])) {
   3791             $card.addClass('resource-card-row-stack-last');
   3792             stackCount = 0;
   3793           }
   3794         } else {
   3795           stackCount = 0;
   3796         }
   3797 
   3798         $card.appendTo($stackDiv || $widget);
   3799 
   3800       } while (++i < resources.length && stackCount > 0);
   3801     }
   3802   }
   3803 
   3804   /* Build a site map of resources using a section as a root. */
   3805   function buildSectionList(opts) {
   3806     if (opts.section && SECTION_BY_ID[opts.section]) {
   3807       return SECTION_BY_ID[opts.section].sections || [];
   3808     }
   3809     return [];
   3810   }
   3811 
   3812   function buildResourceList(opts) {
   3813     var maxResults = opts.maxResults || 100;
   3814 
   3815     var query = opts.query || '';
   3816     var expressions = parseResourceQuery(query);
   3817     var addedResourceIndices = {};
   3818     var results = [];
   3819 
   3820     for (var i = 0; i < expressions.length; i++) {
   3821       var clauses = expressions[i];
   3822 
   3823       // build initial set of resources from first clause
   3824       var firstClause = clauses[0];
   3825       var resources = [];
   3826       switch (firstClause.attr) {
   3827         case 'type':
   3828           resources = ALL_RESOURCES_BY_TYPE[firstClause.value];
   3829           break;
   3830         case 'lang':
   3831           resources = ALL_RESOURCES_BY_LANG[firstClause.value];
   3832           break;
   3833         case 'tag':
   3834           resources = ALL_RESOURCES_BY_TAG[firstClause.value];
   3835           break;
   3836         case 'collection':
   3837           var urls = RESOURCE_COLLECTIONS[firstClause.value].resources || [];
   3838           resources = urls.map(function(url){ return ALL_RESOURCES_BY_URL[url]; });
   3839           break;
   3840         case 'section':
   3841           var urls = SITE_MAP[firstClause.value].sections || [];
   3842           resources = urls.map(function(url){ return ALL_RESOURCES_BY_URL[url]; });
   3843           break;
   3844       }
   3845       // console.log(firstClause.attr + ':' + firstClause.value);
   3846       resources = resources || [];
   3847 
   3848       // use additional clauses to filter corpus
   3849       if (clauses.length > 1) {
   3850         var otherClauses = clauses.slice(1);
   3851         resources = resources.filter(getResourceMatchesClausesFilter(otherClauses));
   3852       }
   3853 
   3854       // filter out resources already added
   3855       if (i > 1) {
   3856         resources = resources.filter(getResourceNotAlreadyAddedFilter(addedResourceIndices));
   3857       }
   3858 
   3859       // add to list of already added indices
   3860       for (var j = 0; j < resources.length; j++) {
   3861         // console.log(resources[j].title);
   3862         addedResourceIndices[resources[j].index] = 1;
   3863       }
   3864 
   3865       // concat to final results list
   3866       results = results.concat(resources);
   3867     }
   3868 
   3869     if (opts.sortOrder && results.length) {
   3870       var attr = opts.sortOrder;
   3871 
   3872       if (opts.sortOrder == 'random') {
   3873         var i = results.length, j, temp;
   3874         while (--i) {
   3875           j = Math.floor(Math.random() * (i + 1));
   3876           temp = results[i];
   3877           results[i] = results[j];
   3878           results[j] = temp;
   3879         }
   3880       } else {
   3881         var desc = attr.charAt(0) == '-';
   3882         if (desc) {
   3883           attr = attr.substring(1);
   3884         }
   3885         results = results.sort(function(x,y) {
   3886           return (desc ? -1 : 1) * (parseInt(x[attr], 10) - parseInt(y[attr], 10));
   3887         });
   3888       }
   3889     }
   3890 
   3891     results = results.filter(getResourceNotAlreadyAddedFilter(addedPageResources));
   3892     results = results.slice(0, maxResults);
   3893 
   3894     for (var j = 0; j < results.length; ++j) {
   3895       addedPageResources[results[j].index] = 1;
   3896     }
   3897 
   3898     return results;
   3899   }
   3900 
   3901 
   3902   function getResourceNotAlreadyAddedFilter(addedResourceIndices) {
   3903     return function(resource) {
   3904       return !addedResourceIndices[resource.index];
   3905     };
   3906   }
   3907 
   3908 
   3909   function getResourceMatchesClausesFilter(clauses) {
   3910     return function(resource) {
   3911       return doesResourceMatchClauses(resource, clauses);
   3912     };
   3913   }
   3914 
   3915 
   3916   function doesResourceMatchClauses(resource, clauses) {
   3917     for (var i = 0; i < clauses.length; i++) {
   3918       var map;
   3919       switch (clauses[i].attr) {
   3920         case 'type':
   3921           map = IS_RESOURCE_OF_TYPE[clauses[i].value];
   3922           break;
   3923         case 'lang':
   3924           map = IS_RESOURCE_IN_LANG[clauses[i].value];
   3925           break;
   3926         case 'tag':
   3927           map = IS_RESOURCE_TAGGED[clauses[i].value];
   3928           break;
   3929       }
   3930 
   3931       if (!map || (!!clauses[i].negative ? map[resource.index] : !map[resource.index])) {
   3932         return clauses[i].negative;
   3933       }
   3934     }
   3935     return true;
   3936   }
   3937 
   3938   function cleanUrl(url)
   3939   {
   3940     if (url && url.indexOf('//') === -1) {
   3941       url = toRoot + url;
   3942     }
   3943 
   3944     return url;
   3945   }
   3946 
   3947 
   3948   function parseResourceQuery(query) {
   3949     // Parse query into array of expressions (expression e.g. 'tag:foo + type:video')
   3950     var expressions = [];
   3951     var expressionStrs = query.split(',') || [];
   3952     for (var i = 0; i < expressionStrs.length; i++) {
   3953       var expr = expressionStrs[i] || '';
   3954 
   3955       // Break expression into clauses (clause e.g. 'tag:foo')
   3956       var clauses = [];
   3957       var clauseStrs = expr.split(/(?=[\+\-])/);
   3958       for (var j = 0; j < clauseStrs.length; j++) {
   3959         var clauseStr = clauseStrs[j] || '';
   3960 
   3961         // Get attribute and value from clause (e.g. attribute='tag', value='foo')
   3962         var parts = clauseStr.split(':');
   3963         var clause = {};
   3964 
   3965         clause.attr = parts[0].replace(/^\s+|\s+$/g,'');
   3966         if (clause.attr) {
   3967           if (clause.attr.charAt(0) == '+') {
   3968             clause.attr = clause.attr.substring(1);
   3969           } else if (clause.attr.charAt(0) == '-') {
   3970             clause.negative = true;
   3971             clause.attr = clause.attr.substring(1);
   3972           }
   3973         }
   3974 
   3975         if (parts.length > 1) {
   3976           clause.value = parts[1].replace(/^\s+|\s+$/g,'');
   3977         }
   3978 
   3979         clauses.push(clause);
   3980       }
   3981 
   3982       if (!clauses.length) {
   3983         continue;
   3984       }
   3985 
   3986       expressions.push(clauses);
   3987     }
   3988 
   3989     return expressions;
   3990   }
   3991 })();
   3992 
   3993 (function($) {
   3994 
   3995   /*
   3996     Utility method for creating dom for the description area of a card.
   3997     Used in decorateResourceCard and decorateResource.
   3998   */
   3999   function buildResourceCardDescription(resource, plusone) {
   4000     var $description = $('<div>').addClass('description ellipsis');
   4001 
   4002     $description.append($('<div>').addClass('text').html(resource.summary));
   4003 
   4004     if (resource.cta) {
   4005       $description.append($('<a>').addClass('cta').html(resource.cta));
   4006     }
   4007 
   4008     if (plusone) {
   4009       var plusurl = resource.url.indexOf("//") > -1 ? resource.url :
   4010         "//developer.android.com/" + resource.url;
   4011 
   4012       $description.append($('<div>').addClass('util')
   4013         .append($('<div>').addClass('g-plusone')
   4014           .attr('data-size', 'small')
   4015           .attr('data-align', 'right')
   4016           .attr('data-href', plusurl)));
   4017     }
   4018 
   4019     return $description;
   4020   }
   4021 
   4022 
   4023   /* Simple jquery function to create dom for a standard resource card */
   4024   $.fn.decorateResourceCard = function(resource,plusone) {
   4025     var section = resource.group || resource.type;
   4026     var imgUrl = resource.image ||
   4027       'assets/images/resource-card-default-android.jpg';
   4028 
   4029     if (imgUrl.indexOf('//') === -1) {
   4030       imgUrl = toRoot + imgUrl;
   4031     }
   4032 
   4033     $('<div>').addClass('card-bg')
   4034       .css('background-image', 'url(' + (imgUrl || toRoot +
   4035         'assets/images/resource-card-default-android.jpg') + ')')
   4036       .appendTo(this);
   4037 
   4038     $('<div>').addClass('card-info' + (!resource.summary ? ' empty-desc' : ''))
   4039       .append($('<div>').addClass('section').text(section))
   4040       .append($('<div>').addClass('title').html(resource.title))
   4041       .append(buildResourceCardDescription(resource, plusone))
   4042       .appendTo(this);
   4043 
   4044     return this;
   4045   };
   4046 
   4047   /* Simple jquery function to create dom for a resource section card (menu) */
   4048   $.fn.decorateResourceSection = function(section,plusone) {
   4049     var resource = section.resource;
   4050     //keep url clean for matching and offline mode handling
   4051     var urlPrefix = resource.image.indexOf("//") > -1 ? "" : toRoot;
   4052     var $base = $('<a>')
   4053         .addClass('card-bg')
   4054         .attr('href', resource.url)
   4055         .append($('<div>').addClass('card-section-icon')
   4056           .append($('<div>').addClass('icon'))
   4057           .append($('<div>').addClass('section').html(resource.title)))
   4058       .appendTo(this);
   4059 
   4060     var $cardInfo = $('<div>').addClass('card-info').appendTo(this);
   4061 
   4062     if (section.sections && section.sections.length) {
   4063       // Recurse the section sub-tree to find a resource image.
   4064       var stack = [section];
   4065 
   4066       while (stack.length) {
   4067         if (stack[0].resource.image) {
   4068           $base.css('background-image', 'url(' + urlPrefix + stack[0].resource.image + ')');
   4069           break;
   4070         }
   4071 
   4072         if (stack[0].sections) {
   4073           stack = stack.concat(stack[0].sections);
   4074         }
   4075 
   4076         stack.shift();
   4077       }
   4078 
   4079       var $ul = $('<ul>')
   4080         .appendTo($cardInfo);
   4081 
   4082       var max = section.sections.length > 3 ? 3 : section.sections.length;
   4083 
   4084       for (var i = 0; i < max; ++i) {
   4085 
   4086         var subResource = section.sections[i];
   4087         if (!plusone) {
   4088           $('<li>')
   4089             .append($('<a>').attr('href', subResource.url)
   4090               .append($('<div>').addClass('title').html(subResource.title))
   4091               .append($('<div>').addClass('description ellipsis')
   4092                 .append($('<div>').addClass('text').html(subResource.summary))
   4093                 .append($('<div>').addClass('util'))))
   4094           .appendTo($ul);
   4095         } else {
   4096           $('<li>')
   4097             .append($('<a>').attr('href', subResource.url)
   4098               .append($('<div>').addClass('title').html(subResource.title))
   4099               .append($('<div>').addClass('description ellipsis')
   4100                 .append($('<div>').addClass('text').html(subResource.summary))
   4101                 .append($('<div>').addClass('util')
   4102                   .append($('<div>').addClass('g-plusone')
   4103                     .attr('data-size', 'small')
   4104                     .attr('data-align', 'right')
   4105                     .attr('data-href', resource.url)))))
   4106           .appendTo($ul);
   4107         }
   4108       }
   4109 
   4110       // Add a more row
   4111       if (max < section.sections.length) {
   4112         $('<li>')
   4113           .append($('<a>').attr('href', resource.url)
   4114             .append($('<div>')
   4115               .addClass('title')
   4116               .text('More')))
   4117         .appendTo($ul);
   4118       }
   4119     } else {
   4120       // No sub-resources, just render description?
   4121     }
   4122 
   4123     return this;
   4124   };
   4125 
   4126 
   4127 
   4128 
   4129   /* Render other types of resource styles that are not cards. */
   4130   $.fn.decorateResource = function(resource, opts) {
   4131     var imgUrl = resource.image ||
   4132       'assets/images/resource-card-default-android.jpg';
   4133     var linkUrl = resource.url;
   4134 
   4135     if (imgUrl.indexOf('//') === -1) {
   4136       imgUrl = toRoot + imgUrl;
   4137     }
   4138 
   4139     if (linkUrl && linkUrl.indexOf('//') === -1) {
   4140       linkUrl = toRoot + linkUrl;
   4141     }
   4142 
   4143     $(this).append(
   4144       $('<div>').addClass('image')
   4145         .css('background-image', 'url(' + imgUrl + ')'),
   4146       $('<div>').addClass('info').append(
   4147         $('<h4>').addClass('title').html(resource.title),
   4148         $('<p>').addClass('summary').html(resource.summary),
   4149         $('<a>').attr('href', linkUrl).addClass('cta').html('Learn More')
   4150       )
   4151     );
   4152 
   4153     return this;
   4154   };
   4155 })(jQuery);
   4156 
   4157 
   4158 /* Calculate the vertical area remaining */
   4159 (function($) {
   4160     $.fn.ellipsisfade= function(lineHeight) {
   4161         this.each(function() {
   4162             // get element text
   4163             var $this = $(this);
   4164             var remainingHeight = $this.parent().parent().height();
   4165             $this.parent().siblings().each(function ()
   4166             {
   4167               if ($(this).is(":visible")) {
   4168                 var h = $(this).height();
   4169                 remainingHeight = remainingHeight - h;
   4170               }
   4171             });
   4172 
   4173             adjustedRemainingHeight = ((remainingHeight)/lineHeight>>0)*lineHeight
   4174             $this.parent().css({'height': adjustedRemainingHeight});
   4175             $this.css({'height': "auto"});
   4176         });
   4177 
   4178         return this;
   4179     };
   4180 }) (jQuery);
   4181 
   4182 /*
   4183   Fullscreen Carousel
   4184 
   4185   The following allows for an area at the top of the page that takes over the
   4186   entire browser height except for its top offset and an optional bottom
   4187   padding specified as a data attribute.
   4188 
   4189   HTML:
   4190 
   4191   <div class="fullscreen-carousel">
   4192     <div class="fullscreen-carousel-content">
   4193       <!-- content here -->
   4194     </div>
   4195     <div class="fullscreen-carousel-content">
   4196       <!-- content here -->
   4197     </div>
   4198 
   4199     etc ...
   4200 
   4201   </div>
   4202 
   4203   Control over how the carousel takes over the screen can mostly be defined in
   4204   a css file. Setting min-height on the .fullscreen-carousel-content elements
   4205   will prevent them from shrinking to far vertically when the browser is very
   4206   short, and setting max-height on the .fullscreen-carousel itself will prevent
   4207   the area from becoming to long in the case that the browser is stretched very
   4208   tall.
   4209 
   4210   There is limited functionality for having multiple sections since that request
   4211   was removed, but it is possible to add .next-arrow and .prev-arrow elements to
   4212   scroll between multiple content areas.
   4213 */
   4214 
   4215 (function() {
   4216   $(document).ready(function() {
   4217     $('.fullscreen-carousel').each(function() {
   4218       initWidget(this);
   4219     });
   4220   });
   4221 
   4222   function initWidget(widget) {
   4223     var $widget = $(widget);
   4224 
   4225     var topOffset = $widget.offset().top;
   4226     var padBottom = parseInt($widget.data('paddingbottom')) || 0;
   4227     var maxHeight = 0;
   4228     var minHeight = 0;
   4229     var $content = $widget.find('.fullscreen-carousel-content');
   4230     var $nextArrow = $widget.find('.next-arrow');
   4231     var $prevArrow = $widget.find('.prev-arrow');
   4232     var $curSection = $($content[0]);
   4233 
   4234     if ($content.length <= 1) {
   4235       $nextArrow.hide();
   4236       $prevArrow.hide();
   4237     } else {
   4238       $nextArrow.click(function() {
   4239         var index = ($content.index($curSection) + 1);
   4240         $curSection.hide();
   4241         $curSection = $($content[index >= $content.length ? 0 : index]);
   4242         $curSection.show();
   4243       });
   4244 
   4245       $prevArrow.click(function() {
   4246         var index = ($content.index($curSection) - 1);
   4247         $curSection.hide();
   4248         $curSection = $($content[index < 0 ? $content.length - 1 : 0]);
   4249         $curSection.show();
   4250       });
   4251     }
   4252 
   4253     // Just hide all content sections except first.
   4254     $content.each(function(index) {
   4255       if ($(this).height() > minHeight) minHeight = $(this).height();
   4256       $(this).css({position: 'absolute',  display: index > 0 ? 'none' : ''});
   4257     });
   4258 
   4259     // Register for changes to window size, and trigger.
   4260     $(window).resize(resizeWidget);
   4261     resizeWidget();
   4262 
   4263     function resizeWidget() {
   4264       var height = $(window).height() - topOffset - padBottom;
   4265       $widget.width($(window).width());
   4266       $widget.height(height < minHeight ? minHeight :
   4267         (maxHeight && height > maxHeight ? maxHeight : height));
   4268     }
   4269   }
   4270 })();
   4271 
   4272 
   4273 
   4274 
   4275 
   4276 /*
   4277   Tab Carousel
   4278 
   4279   The following allows tab widgets to be installed via the html below. Each
   4280   tab content section should have a data-tab attribute matching one of the
   4281   nav items'. Also each tab content section should have a width matching the
   4282   tab carousel.
   4283 
   4284   HTML:
   4285 
   4286   <div class="tab-carousel">
   4287     <ul class="tab-nav">
   4288       <li><a href="#" data-tab="handsets">Handsets</a>
   4289       <li><a href="#" data-tab="wearable">Wearable</a>
   4290       <li><a href="#" data-tab="tv">TV</a>
   4291     </ul>
   4292 
   4293     <div class="tab-carousel-content">
   4294       <div data-tab="handsets">
   4295         <!--Full width content here-->
   4296       </div>
   4297 
   4298       <div data-tab="wearable">
   4299         <!--Full width content here-->
   4300       </div>
   4301 
   4302       <div data-tab="tv">
   4303         <!--Full width content here-->
   4304       </div>
   4305     </div>
   4306   </div>
   4307 
   4308 */
   4309 (function() {
   4310   $(document).ready(function() {
   4311     $('.tab-carousel').each(function() {
   4312       initWidget(this);
   4313     });
   4314   });
   4315 
   4316   function initWidget(widget) {
   4317     var $widget = $(widget);
   4318     var $nav = $widget.find('.tab-nav');
   4319     var $anchors = $nav.find('[data-tab]');
   4320     var $li = $nav.find('li');
   4321     var $contentContainer = $widget.find('.tab-carousel-content');
   4322     var $tabs = $contentContainer.find('[data-tab]');
   4323     var $curTab = $($tabs[0]); // Current tab is first tab.
   4324     var width = $widget.width();
   4325 
   4326     // Setup nav interactivity.
   4327     $anchors.click(function(evt) {
   4328       evt.preventDefault();
   4329       var query = '[data-tab=' + $(this).data('tab') + ']';
   4330       transitionWidget($tabs.filter(query));
   4331     });
   4332 
   4333     // Add highlight for navigation on first item.
   4334     var $highlight = $('<div>').addClass('highlight')
   4335       .css({left:$li.position().left + 'px', width:$li.outerWidth() + 'px'})
   4336       .appendTo($nav);
   4337 
   4338     // Store height since we will change contents to absolute.
   4339     $contentContainer.height($contentContainer.height());
   4340 
   4341     // Absolutely position tabs so they're ready for transition.
   4342     $tabs.each(function(index) {
   4343       $(this).css({position: 'absolute', left: index > 0 ? width + 'px' : '0'});
   4344     });
   4345 
   4346     function transitionWidget($toTab) {
   4347       if (!$curTab.is($toTab)) {
   4348         var curIndex = $tabs.index($curTab[0]);
   4349         var toIndex = $tabs.index($toTab[0]);
   4350         var dir = toIndex > curIndex ? 1 : -1;
   4351 
   4352         // Animate content sections.
   4353         $toTab.css({left:(width * dir) + 'px'});
   4354         $curTab.animate({left:(width * -dir) + 'px'});
   4355         $toTab.animate({left:'0'});
   4356 
   4357         // Animate navigation highlight.
   4358         $highlight.animate({left:$($li[toIndex]).position().left + 'px',
   4359           width:$($li[toIndex]).outerWidth() + 'px'})
   4360 
   4361         // Store new current section.
   4362         $curTab = $toTab;
   4363       }
   4364     }
   4365   }
   4366 })();
   4367