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