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