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