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