jekyll / jekyll-feed

:memo: A Jekyll plugin to generate an Atom (RSS-like) feed of your Jekyll posts
MIT License
827 stars 202 forks source link

Validating generated feed produces change recommendations #144

Closed stkent closed 7 years ago

stkent commented 7 years ago

github-pages gem v100 ==> jekyll 3.2.1, jekyll-feed 0.7.2

I'd like to replace my manually-generated feed using this plugin. Here's what my manual feed.xml currently looks like:


---

---
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>{{ site.name | xml_escape }}</title>
    <description>{{ site.description | xml_escape }}</description>
    <link>{{ site.url }}{{ site.baseurl }}/</link>
    <atom:link href="{{ site.url }}/feed.xml" rel="self" type="application/rss+xml"/> {% for post in site.posts limit:10 %}
      <item>
        <title>{{ post.title | xml_escape }}</title>
        {% if post.author.name %}
          <dc:creator>{{ post.author.name | xml_escape }}</dc:creator>
        {% endif %}
        {% if post.excerpt %}
          <description>{{ post.excerpt | xml_escape }}</description>
        {% else %}
          <description>{{ post.content | xml_escape }}</description>
        {% endif %}
        <pubDate>{{ post.date | date: "%a, %d %b %Y %H:%M:%S %z" }}</pubDate>
        <link>{{ site.url }}{{ post.url }}</link>
        <guid isPermaLink="true">{{ site.url }}{{ post.url }}</guid>
      </item>
    {% endfor %}
  </channel>
</rss>
Generated RSS feed ``` Stuart Kent Stuart Kent http://stkent.github.io/ proguardFiles&#58; A Cautionary Tale <p>This week, an assumption I made about the Android Gradle plugin method <code class="highlighter-rouge">proguardFiles</code> nearly resulted in a minor security slip. Let’s all learn from my mistake!</p> Fri, 07 Oct 2016 00:00:00 +0000 http://stkent.github.io/2016/10/07/proguardfiles-a-cautionary-tale.html http://stkent.github.io/2016/10/07/proguardfiles-a-cautionary-tale.html Capturing Nougat screenshots using adb shell <p>Prior to Android Nougat, I used the following bash function to speedily save screenshots to my local machine:</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="k">function </span>screenshot<span class="o">()</span> <span class="o">{</span> adb shell screencap -p | perl -pe <span class="s1">'s/\x0D\x0A/\x0A/g'</span> &gt; <span class="s2">"</span><span class="k">${</span><span class="nv">HOME</span><span class="k">}</span><span class="s2">/Desktop/screenshot.png"</span> <span class="o">}</span></code></pre></figure> <p>However, executing this function when using a device running Android Nougat results in a corrupted output file. What gives?</p> Sun, 28 Aug 2016 00:00:00 +0000 http://stkent.github.io/2016/08/28/capturing-Nougat-screenshots-using-adb-shell.html http://stkent.github.io/2016/08/28/capturing-Nougat-screenshots-using-adb-shell.html Adventures with Javadocs, part 3 <p>This is the follow-up to Adventures with Javadocs parts <a href="/2016/01/28/adventures-with-javadocs-part-1.html">1</a> and <a href="/2016/02/05/adventures-with-javadocs-part-2.html">2</a>. If you haven’t already, please go read those - this post continues to build on the sample project constructed there, and explores the extra configuration needed to properly handle classes supplied by third party dependencies.</p> Fri, 10 Jun 2016 00:00:00 +0000 http://stkent.github.io/2016/06/10/adventures-with-javadocs-part-3.html http://stkent.github.io/2016/06/10/adventures-with-javadocs-part-3.html “The Human Context”&#58; A Followup <p>One of the risks of presenting on nascent APIs is that significant changes are fair game. Luckily, the timing of my Self Conference talk <a href="/2016/05/21/self-conference-2016.html">The Human Context: Exploring Google’s Nearby APIs</a> a couple weeks back allowed me to quickly update my slides to incorporate <em>most</em> of the changes announced at Google I/O two days earlier. Google Nearby guy <a href="https://twitter.com/andrewbunner">Andrew Bunner</a> was kind enough to review the recording of my talk and pass along some notes!</p> Fri, 03 Jun 2016 00:00:00 +0000 http://stkent.github.io/2016/06/03/the-human-context-a-followup.html http://stkent.github.io/2016/06/03/the-human-context-a-followup.html Self Conference 2016 <p><a href="http://selfconference.org/">Self Conference 2016</a> wrapped up today! Held here in Detroit, it’s an annual get-together where we talk about tech, people, and especially tech people. Having attended and enjoyed the conference immensely in 2014 and 2015, I was thrilled to be returning as a speaker this year! Check out the video (audio + slides) of my talk, “The Human Context: Exploring Google’s Nearby APIs” below!</p> <div class="video-container"> <iframe width="100%" src="//www.youtube.com/embed/fKSqMsCTDiI" style="display:block; float:center; margin: 0 auto" frameborder="0" allowfullscreen=""></iframe> </div> Sat, 21 May 2016 00:00:00 +0000 http://stkent.github.io/2016/05/21/self-conference-2016.html http://stkent.github.io/2016/05/21/self-conference-2016.html Feedback++ <p>Last week, I gave an internal talk at Detroit Labs about peer feedback. We discussed different types of feedback (appreciation, coaching, and evaluation), and how to get better at giving and guiding each kind. Check out the video (slides + audio) below!</p> <div class="video-container"> <iframe width="100%" src="//www.youtube.com/embed/SyR9wnJtwVc" style="display:block; float:center; margin: 0 auto" frameborder="0" allowfullscreen=""></iframe> </div> Tue, 23 Feb 2016 00:00:00 +0000 http://stkent.github.io/2016/02/23/feedback-plus-plus.html http://stkent.github.io/2016/02/23/feedback-plus-plus.html Adventures with Javadocs, part 2 <p>This is the follow-up to <a href="/2016/01/28/adventures-with-javadocs-part-1.html">Adventures with Javadocs, part 1</a>. If you haven’t already, please go read that - this post will build on the sample project constructed there, and explore the extra configuration needed to properly handle Android framework classes.</p> Fri, 05 Feb 2016 00:00:00 +0000 http://stkent.github.io/2016/02/05/adventures-with-javadocs-part-2.html http://stkent.github.io/2016/02/05/adventures-with-javadocs-part-2.html Adventures with Javadocs, part 1 <p>Hello! It’s been a while since I wrote a post. This is because I have been busy: first learning Swift and iOS development on a crunchy client project, and more lately working on some open source Android tools. I still have more to write about interpolators - fret not! - but right now it’s easier for me to write posts about my primary foci.</p> <p>Part of publishing high-quality libraries is providing high-quality documentation, which in Android-land means: high-quality Javadocs. There are tons of good resources around that explain proper Javadoc comment format and content, so in this series we’ll explore the actual generation of documentation using Gradle/Android Studio. Let’s begin!</p> Thu, 28 Jan 2016 00:00:00 +0000 http://stkent.github.io/2016/01/28/adventures-with-javadocs-part-1.html http://stkent.github.io/2016/01/28/adventures-with-javadocs-part-1.html Droidcon NYC 2015 Debrief <p>It was great to see so many prominent members of the Android community attending and presenting at Droidcon NYC 2015. Let’s review some of my favorite talks! If you only have time to watch 6 of the ~65 talks recorded, I’d suggest starting here. (Note the diversity of the topics represented in this list - a tribute to the all-encompassing scope of Droidcon NYC!)</p> Mon, 31 Aug 2015 00:00:00 +0000 http://stkent.github.io/2015/08/31/droidcon-NYC-2015-debrief.html http://stkent.github.io/2015/08/31/droidcon-NYC-2015-debrief.html Building Smooth Paths using B&eacute;zier Curves <p><a href="/2015/06/07/an-intro-to-pathinterpolatorcompat.html">Last post</a>, we built a super-simple <code class="highlighter-rouge">Path</code>-based interpolator using straight line segments. To produce smoother interpolators, without corners - typically preferred for animating motion - we’ll need correspondingly smooth generating Paths. Our primary goal in this post, then, will be:</p> <ul> <li>given a sequence of $n$ points in the cartesian plane, calculate a smooth <code class="highlighter-rouge">Path</code> passing through all points in order.</li> </ul> Fri, 03 Jul 2015 00:00:00 +0000 http://stkent.github.io/2015/07/03/building-smooth-paths-using-bezier-curves.html http://stkent.github.io/2015/07/03/building-smooth-paths-using-bezier-curves.html ```

The generated page passes validation, with a few recommendations. However, when I remove this file and instead enable the jekyll-feed plugin, the generated Atom feed does not pass validation at the same site.

Generated Atom feed ``` Jekyll2016-10-29T21:07:43-04:00http://stkent.github.io/Stuart KentStuart KentproguardFiles: A Cautionary Tale2016-10-07T00:00:00-04:002016-10-07T00:00:00-04:00http://stkent.github.io/2016/10/07/proguardfiles-a-cautionary-tale<p>This week, an assumption I made about the Android Gradle plugin method <code class="highlighter-rouge">proguardFiles</code> nearly resulted in a minor security slip. Let’s all learn from my mistake!</p> <!--more--> <h1 id="background">Background</h1> <p>The client codebase I’m currently working on has three build types: <code class="highlighter-rouge">debug</code>, <code class="highlighter-rouge">beta</code>, and <code class="highlighter-rouge">release</code>. The <code class="highlighter-rouge">beta</code> build type is a minor variation of the <code class="highlighter-rouge">debug</code> build type, so it’s configured using the Android Gradle plugin’s <a href="http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Types"><code class="highlighter-rouge">initWith</code></a> method as shown below:</p> <figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="n">buildTypes</span> <span class="o">{</span> <span class="n">debug</span> <span class="o">{</span> <span class="c1">// ...</span> <span class="o">}</span> <span class="n">beta</span> <span class="o">{</span> <span class="n">initWith</span><span class="o">(</span><span class="n">buildTypes</span><span class="o">.</span><span class="na">debug</span><span class="o">)</span> <span class="c1">// ...</span> <span class="o">}</span> <span class="n">release</span> <span class="o">{</span> <span class="c1">// ...</span> <span class="o">}</span> <span class="o">}</span></code></pre></figure> <p>My tasks today included enabling <a href="https://developer.android.com/studio/build/shrink-code.html">ProGuard</a> for every single build type. Here’s the code I initially wrote to accomplish this:</p> <figure class="highlight"><pre><code class="language-groovy" data-lang="groovy"><span class="n">buildTypes</span> <span class="o">{</span> <span class="n">debug</span> <span class="o">{</span> <span class="c1">// ...</span> <span class="n">proguardFiles</span> <span class="nf">getDefaultProguardFile</span><span class="o">(</span><span class="s1">'proguard-android.txt'</span><span class="o">),</span> <span class="s1">'proguard-rules.pro'</span><span class="o">,</span> <span class="s1">'proguard-debug.pro'</span> <span class="o">}</span> <span class="n">beta</span> <span class="o">{</span> <span class="n">initWith</span><span class="o">(</span><span class="n">buildTypes</span><span class="o">.</span><span class="na">debug</span><span class="o">)</span> <span class="c1">// ...</span> <span class="n">proguardFiles</span> <span class="nf">getDefaultProguardFile</span><span class="o">(</span><span class="s1">'proguard-android.txt'</span><span class="o">),</span> <span class="s1">'proguard-rules.pro'</span> <span class="o">}</span> <span class="n">release</span> <span class="o">{</span> <span class="c1">// ...</span> <span class="n">proguardFiles</span> <span class="nf">getDefaultProguardFile</span><span class="o">(</span><span class="s1">'proguard-android.txt'</span><span class="o">),</span> <span class="s1">'proguard-rules.pro'</span> <span class="o">}</span> <span class="o">}</span></code></pre></figure> <p>The common ProGuard rules defined in the <code class="highlighter-rouge">proguard-android.txt</code> and <code class="highlighter-rouge">proguard-rules.pro</code> files are applied to all three build types. The extra ProGuard rules defined in the <code class="highlighter-rouge">proguard-debug.pro</code> file are applied to the <code class="highlighter-rouge">debug</code> build type only.</p> <p>Right?</p> <h1 id="wrong">Wrong!</h1> <p>The <code class="highlighter-rouge">proguard-debug.pro</code> file contains exactly one line:</p> <figure class="highlight"><pre><code class="language-text" data-lang="text">-dontobfuscate</code></pre></figure> <p><a href="https://en.wikipedia.org/wiki/Obfuscation_(software)">Obfuscation</a> is a useful (but certainly not impenetrable) defense against <a href="https://en.wikipedia.org/wiki/Reverse_engineering">reverse engineering</a> of a compiled application. However, I have found in the past that it interferes with Android Studio’s debugger, so I like to disable it for the non-production build variants I actively develop with.</p> <p>As described above, my intention was to disable obfuscation for the <code class="highlighter-rouge">debug</code> build type <em>only</em>, leaving obfuscation enabled for the <code class="highlighter-rouge">beta</code> and <code class="highlighter-rouge">release</code> build types. To test that this was working as expected, I assembled a <code class="highlighter-rouge">beta</code> build and inspected the APK contents using <a href="https://github.com/google/android-classyshark">ClassyShark</a>. Here’s what our <code class="highlighter-rouge">Parcelable</code> utility class looked like in ClassyShark:</p> <div class="image-container"> <img src="/assets/images/proguardfiles-a-cautionary-tale-no-obfuscation.png" width="100%" /> </div> <p>Those method names are <em>definitely</em> not obfuscated.</p> <h1 id="huh">Huh?</h1> <p>Confused, I jumped to the definition of the <code class="highlighter-rouge">proguardFiles</code> method commonly used to apply ProGuard configuration to a build type (remember that in Android Studio you can do this using the <a href="https://www.jetbrains.com/help/idea/2016.2/navigating-to-declaration-or-type-declaration-of-a-symbol.html">“Go To Declaration” shortcut</a>):</p> <figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="n">BuildType</span> <span class="nf">proguardFiles</span><span class="o">(</span><span class="n">Object</span><span class="o">...</span> <span class="n">files</span><span class="o">)</span> <span class="o">{</span> <span class="n">Object</span><span class="o">[]</span> <span class="n">var2</span> <span class="o">=</span> <span class="n">files</span><span class="o">;</span> <span class="kt">int</span> <span class="n">var3</span> <span class="o">=</span> <span class="n">files</span><span class="o">.</span><span class="na">length</span><span class="o">;</span> <span class="k">for</span><span class="o">(</span><span class="kt">int</span> <span class="n">var4</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">var4</span> <span class="o">&lt;</span> <span class="n">var3</span><span class="o">;</span> <span class="o">++</span><span class="n">var4</span><span class="o">)</span> <span class="o">{</span> <span class="n">Object</span> <span class="n">file</span> <span class="o">=</span> <span class="n">var2</span><span class="o">[</span><span class="n">var4</span><span class="o">];</span> <span class="k">this</span><span class="o">.</span><span class="na">proguardFile</span><span class="o">(</span><span class="n">file</span><span class="o">);</span> <span class="o">}</span> <span class="k">return</span> <span class="k">this</span><span class="o">;</span> <span class="o">}</span></code></pre></figure> <p>Inside the <code class="highlighter-rouge">for</code> loop, each configuration file is passed to the <code class="highlighter-rouge">proguardFile</code> method:</p> <figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="n">BuildType</span> <span class="nf">proguardFile</span><span class="o">(</span><span class="n">Object</span> <span class="n">proguardFile</span><span class="o">)</span> <span class="o">{</span> <span class="k">this</span><span class="o">.</span><span class="na">getProguardFiles</span><span class="o">().</span><span class="na">add</span><span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">project</span><span class="o">.</span><span class="na">file</span><span class="o">(</span><span class="n">proguardFile</span><span class="o">));</span> <span class="k">return</span> <span class="k">this</span><span class="o">;</span> <span class="o">}</span></code></pre></figure> <p>Ah-ha!</p> <p>The <code class="highlighter-rouge">proguardFiles</code> method <strong>adds</strong> to an internal list of configuration files rather than specifying a <strong>new</strong> list of configuration files. In my opinion this is <a href="https://en.wikipedia.org/wiki/Principle_of_least_astonishment">not obvious from the method name</a>. At least the <a href="https://google.github.io/android-gradle-dsl/2.2/com.android.build.gradle.internal.dsl.BuildType.html#com.android.build.gradle.internal.dsl.BuildType:proguardFiles(java.lang.Object[])">documented behavior is clear</a>, though confusingly the name <code class="highlighter-rouge">proguardFiles</code> may also be used to refer to a <a href="https://google.github.io/android-gradle-dsl/2.2/com.android.build.gradle.internal.dsl.BuildType.html#com.android.build.gradle.internal.dsl.BuildType:proguardFiles">getter</a> defined on the same type!</p> <p>Armed with this knowledge, we can now walk through exactly what happens (with respect to ProGuard rules) when the <code class="highlighter-rouge">beta</code> build type is configured:</p> <ol> <li> <p><code class="highlighter-rouge">initWith(buildTypes.debug)</code> is called, which results in three ProGuard configuration files (<code class="highlighter-rouge">proguard-android.txt</code>, <code class="highlighter-rouge">proguard-rules.pro</code>, and <code class="highlighter-rouge">proguard-debug.pro</code>) being added to the <code class="highlighter-rouge">beta</code> build type’s internal list;</p> </li> <li> <p><code class="highlighter-rouge">proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'</code> is called, which results in duplicates of the <code class="highlighter-rouge">proguard-android.txt</code> and <code class="highlighter-rouge">proguard-rules.pro</code> being added to the <code class="highlighter-rouge">beta</code> build type’s internal list.</p> </li> </ol> <p>The result: <code class="highlighter-rouge">proguard-debug.pro</code> is unintentionally included in the <code class="highlighter-rouge">beta</code> build type’s internal list of configuration files, and the <code class="highlighter-rouge">-dontobfuscate</code> ProGuard rule is therefore applied when packaging our application. This is consistent with what we found in the decompiled APK.</p> <h1 id="setproguardfiles-to-the-rescue">setProguardFiles to the rescue</h1> <p>The most obvious solution to this problem might be to avoid initializing the <code class="highlighter-rouge">beta</code> build type using the <code class="highlighter-rouge">debug</code> build type. However, this would have lead to a lot more duplication in our build.gradle file. Luckily there’s a better way.</p> <p>The <code class="highlighter-rouge">BuildType</code> class exposes a <a href="https://google.github.io/android-gradle-dsl/2.2/com.android.build.gradle.internal.dsl.BuildType.html#com.android.build.gradle.internal.dsl.BuildType:setProguardFiles(java.lang.Iterable)"><code class="highlighter-rouge">setProguardFiles</code> method</a>, defined as follows:</p> <figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="n">BuildType</span> <span class="nf">setProguardFiles</span><span class="o">(</span><span class="n">Iterable</span><span class="o">&lt;?&gt;</span> <span class="n">proguardFileIterable</span><span class="o">)</span> <span class="o">{</span> <span class="k">this</span><span class="o">.</span><span class="na">getProguardFiles</span><span class="o">().</span><span class="na">clear</span><span class="o">();</span> <span class="k">this</span><span class="o">.</span><span class="na">proguardFiles</span><span class="o">(</span><span class="n">Iterables</span><span class="o">.</span><span class="na">toArray</span><span class="o">(</span><span class="n">proguardFileIterable</span><span class="o">,</span> <span class="n">Object</span><span class="o">.</span><span class="na">class</span><span class="o">));</span> <span class="k">return</span> <span class="k">this</span><span class="o">;</span> <span class="o">}</span></code></pre></figure> <p>This is exactly how I originally assumed the <code class="highlighter-rouge">proguardFiles</code> method worked! Any existing configuration files are explicitly <strong>replaced</strong> by those contained in the argument passed to <code class="highlighter-rouge">setProguardFiles</code>. So, here’s a fixed version of our build.gradle file:</p> <figure class="highlight"><pre><code class="language-groovy" data-lang="groovy"><span class="n">buildTypes</span> <span class="o">{</span> <span class="n">debug</span> <span class="o">{</span> <span class="c1">// ...</span> <span class="n">proguardFiles</span> <span class="nf">getDefaultProguardFile</span><span class="o">(</span><span class="s1">'proguard-android.txt'</span><span class="o">),</span> <span class="s1">'proguard-rules.pro'</span><span class="o">,</span> <span class="s1">'proguard-debug.pro'</span> <span class="o">}</span> <span class="n">beta</span> <span class="o">{</span> <span class="n">initWith</span><span class="o">(</span><span class="n">buildTypes</span><span class="o">.</span><span class="na">debug</span><span class="o">)</span> <span class="c1">// ...</span> <span class="c1">// New!</span> <span class="n">setProguardFiles</span><span class="o">([</span><span class="n">getDefaultProguardFile</span><span class="o">(</span><span class="s1">'proguard-android.txt'</span><span class="o">),</span> <span class="s1">'proguard-rules.pro'</span><span class="o">])</span> <span class="o">}</span> <span class="n">release</span> <span class="o">{</span> <span class="c1">// ...</span> <span class="n">proguardFiles</span> <span class="nf">getDefaultProguardFile</span><span class="o">(</span><span class="s1">'proguard-android.txt'</span><span class="o">),</span> <span class="s1">'proguard-rules.pro'</span> <span class="o">}</span> <span class="o">}</span></code></pre></figure> <p>To check that obfuscation was now properly enabled for <code class="highlighter-rouge">beta</code> builds, I assembled a new APK and navigated to our <code class="highlighter-rouge">Parcelable</code> utility class using ClassyShark:</p> <div class="image-container"> <img src="/assets/images/proguardfiles-a-cautionary-tale-obfuscation.png" width="100%" /> </div> <p>Much better!</p> <h1 id="takeaways">Takeaways</h1> <ul> <li> <p>The Android Gradle plugin source code is accessible - utilize it;</p> </li> <li> <p>Proactively seek ways to validate your assumptions, no matter how obviously-correct they may seem/feel;</p> </li> <li> <p>Double-triple-check changes that potentially impact application security.</p> </li> </ul>Stuart KentThis week, an assumption I made about the Android Gradle plugin method proguardFiles nearly resulted in a minor security slip. Let’s all learn from my mistake!Capturing Nougat screenshots using adb shell2016-08-28T00:00:00-04:002016-08-28T00:00:00-04:00http://stkent.github.io/2016/08/28/capturing-Nougat-screenshots-using-adb-shell<p>Prior to Android Nougat, I used the following bash function to speedily save screenshots to my local machine:</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="k">function </span>screenshot<span class="o">()</span> <span class="o">{</span> adb shell screencap -p | perl -pe <span class="s1">'s/\x0D\x0A/\x0A/g'</span> &gt; <span class="s2">"</span><span class="k">${</span><span class="nv">HOME</span><span class="k">}</span><span class="s2">/Desktop/screenshot.png"</span> <span class="o">}</span></code></pre></figure> <p>However, executing this function when using a device running Android Nougat results in a corrupted output file. What gives?</p> <!--more--> <h1 id="background">Background</h1> <p>The output of the adb shell’s screencap utility is known to be somewhat funky on older (read: pre-Nougat) versions of Android. In particular, “adb shell” performs an automatic <a href="http://stackoverflow.com/a/13593914/2911458">line feed (LF) to {carriage return (CR) + line feed (LF)}</a> conversion. This can be observed by capturing a “naive” screenshot (no <a href="http://blog.shvetsov.com/2013/02/grab-android-screenshot-to-computer-via.html">perl sanitization</a>):</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash">adb shell screencap -p &gt; <span class="s2">"screenshot.png"</span></code></pre></figure> <p>and then inspecting the corresponding <a href="https://en.wikipedia.org/wiki/Hex_dump">hex dump</a>:</p> <figure class="highlight"><pre><code class="language-text" data-lang="text">$ hexdump -C screenshot.png | head 00000000 89 50 4e 47 0d 0d 0a 1a 0d 0a 00 00 00 0d 49 48 |.PNG..........IH| 00000010 44 52 00 00 02 d0 00 00 05 00 08 06 00 00 00 6e |DR.............n| 00000020 ce 65 3d 00 00 00 04 73 42 49 54 08 08 08 08 7c |.e=....sBIT....|| 00000030 08 64 88 00 00 20 00 49 44 41 54 78 9c ec bd 79 |.d... .IDATx...y| 00000040 9c 1d 55 9d f7 ff 3e 55 75 f7 de b7 74 77 d2 d9 |..U...&gt;Uu...tw..| 00000050 bb b3 27 10 48 42 16 c0 20 01 86 5d 14 04 11 dc |..'.HB.. ..]....| 00000060 78 44 9d c7 d1 d1 11 78 70 7e 23 33 8e 1b 38 33 |xD.....xp~#3..83| 00000070 ea 2c 8c 8e 0d 0a 08 a8 23 2a 0e 10 82 ac c1 40 |.,......#*.....@| 00000080 12 02 81 24 64 ef ec 5b ef fb 5d 6b 3b bf 3f ea |...$d..[..]k;.?.| 00000090 de db dd 49 27 e9 ee 74 77 3a e3 79 bf 5e 37 e7 |...I'..tw:.y.^7.|</code></pre></figure> <p>All valid PNG files start with the following <a href="https://en.wikipedia.org/wiki/Portable_Network_Graphics#File_header">8-byte header</a>:</p> <figure class="highlight"><pre><code class="language-text" data-lang="text">89 50 4e 47 0d 0a 1a 0a</code></pre></figure> <p>But look at the first 10 bytes of our “naively”-captured pre-Nougat screenshot:</p> <figure class="highlight"><pre><code class="language-text" data-lang="text">89 50 4e 47 0d 0d 0a 1a 0d 0a</code></pre></figure> <p>They don’t match! Each occurrence of the byte “0a” in the correct byte sequence has been replaced with a pair of bytes, “0d” “0a” in the incorrect byte sequencing. This is precisely the line feed (LF) to {carriage return (CR) + line feed (LF)} conversion mentioned earlier. The global perl search-and-replace in the original script reverts this heavy-handed manipulation and results in a valid PNG file.</p> <h1 id="nougat">Nougat</h1> <p>Here’s the hex dump of a “naively”-captured Nougat screenshot:</p> <figure class="highlight"><pre><code class="language-text" data-lang="text">$ adb shell screencap -p &gt; "screenshot.png" $ hexdump -C screenshot.png | head 00000000 89 50 4e 47 0d 0a 1a 0a 00 00 00 0d 49 48 44 52 |.PNG........IHDR| 00000010 00 00 05 a0 00 00 0a 00 08 06 00 00 00 47 f4 85 |.............G..| 00000020 cf 00 00 00 04 73 42 49 54 08 08 08 08 7c 08 64 |.....sBIT....|.d| 00000030 88 00 00 20 00 49 44 41 54 78 9c ec dd 79 78 54 |... .IDATx...yxT| 00000040 f5 f9 fe f1 7b 96 4c b6 c9 1e f6 9d 20 06 65 09 |....{.L..... .e.| 00000050 a0 b2 c8 aa 20 b8 07 6d d5 aa 55 b4 ae b5 56 d4 |.... ..m..U...V.| 00000060 5f ad 7c ad 56 d4 6a 37 04 6c d5 2a 2a d8 82 5b |_.|.V.j7.l.**..[| 00000070 55 c0 1d 50 d9 41 ac ec c8 be 86 2d 40 42 12 b2 |U..P.A.....-@B..| 00000080 4f 66 f9 fd 11 83 42 66 26 db 4c e6 4c f2 7e 5d |Of....Bf&amp;.L.L.~]| 00000090 17 57 cb 99 33 e7 3c 33 cc 04 bc cf e7 3c 8f 49 |.W..3.&lt;3.....&lt;.I|</code></pre></figure> <p>The first 8 bytes <strong>do</strong> match the expected PNG file header, so we can deduce that “adb shell” has <strong>not</strong> performed the line feed (LF) to {carriage return (CR) + line feed (LF)} conversion in this case! This also explains why my original bash function <strong>always</strong> produces invalid Nougat screenshots. By attempting to undo a conversion that was never applied, the perl search-and-replace mutilates the PNG file header, replacing the required “0d” “0a” sequence with a single “0a”.</p> <h1 id="solution">Solution</h1> <p>For now, I’ve defined separate pre-Nougat and post-Nougat screenshot bash functions to deal with this change in behavior:</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="k">function </span>screenshot<span class="o">()</span> <span class="o">{</span> adb shell screencap -p | perl -pe <span class="s1">'s/\x0D\x0A/\x0A/g'</span> &gt; <span class="s2">"</span><span class="k">${</span><span class="nv">HOME</span><span class="k">}</span><span class="s2">/Desktop/screenshot.png"</span> <span class="o">}</span> <span class="k">function </span>screenshot-n<span class="o">()</span> <span class="o">{</span> adb shell screencap -p &gt; <span class="s2">"</span><span class="k">${</span><span class="nv">HOME</span><span class="k">}</span><span class="s2">/Desktop/screenshot.png"</span> <span class="o">}</span></code></pre></figure> <p>There’s probably a slick way to unify these functions by inspecting the incoming bytes and conditionally applying the global perl search-and-replace. Sadly, my bash-fu is not at that level - a challenge for the future!</p>Stuart KentPrior to Android Nougat, I used the following bash function to speedily save screenshots to my local machine: function screenshot() { adb shell screencap -p | perl -pe 's/\x0D\x0A/\x0A/g' &gt; "${HOME}/Desktop/screenshot.png" } However, executing this function when using a device running Android Nougat results in a corrupted output file. What gives?Adventures with Javadocs, part 32016-06-10T00:00:00-04:002016-06-10T00:00:00-04:00http://stkent.github.io/2016/06/10/adventures-with-javadocs-part-3<p>This is the follow-up to Adventures with Javadocs parts <a href="/2016/01/28/adventures-with-javadocs-part-1.html">1</a> and <a href="/2016/02/05/adventures-with-javadocs-part-2.html">2</a>. If you haven’t already, please go read those - this post continues to build on the sample project constructed there, and explores the extra configuration needed to properly handle classes supplied by third party dependencies.</p> <!--more--> <h1 id="introducing-classes-from-third-party-dependencies">Introducing Classes From Third Party Dependencies</h1> <p>Let’s add Google’s <a href="https://github.com/google/gson">Gson</a> as a third party dependency of our library:</p> <figure class="highlight"><pre><code class="language-groovy" data-lang="groovy"><span class="n">dependencies</span> <span class="o">{</span> <span class="n">compile</span> <span class="s1">'com.google.code.gson:gson:2.6.2'</span> <span class="o">}</span></code></pre></figure> <p>We also introduce a fourth test class to our project, again in a separate package, that depends only on Gson classes:</p> <figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kn">package</span> <span class="n">com</span><span class="o">.</span><span class="na">github</span><span class="o">.</span><span class="na">stkent</span><span class="o">.</span><span class="na">javadoctests</span><span class="o">.</span><span class="na">package4</span><span class="o">;</span> <span class="kn">import</span> <span class="nn">com.google.gson.Gson</span><span class="o">;</span> <span class="cm">/** * This class depends on a third party type only! */</span> <span class="kd">public</span> <span class="kd">class</span> <span class="nc">TestClassFour</span> <span class="o">{</span> <span class="kd">private</span> <span class="kd">final</span> <span class="n">Gson</span> <span class="n">gson</span><span class="o">;</span> <span class="kd">public</span> <span class="nf">TestClassFour</span><span class="o">(</span><span class="kd">final</span> <span class="n">Gson</span> <span class="n">gson</span><span class="o">)</span> <span class="o">{</span> <span class="k">this</span><span class="o">.</span><span class="na">gson</span> <span class="o">=</span> <span class="n">gson</span><span class="o">;</span> <span class="o">}</span> <span class="kd">public</span> <span class="n">Gson</span> <span class="nf">getGson</span><span class="o">()</span> <span class="o">{</span> <span class="k">return</span> <span class="n">gson</span><span class="o">;</span> <span class="o">}</span> <span class="o">}</span></code></pre></figure> <!--more--> <p>Our <code class="highlighter-rouge">docs</code> task is still configured as follows:</p> <figure class="highlight"><pre><code class="language-groovy" data-lang="groovy"><span class="n">task</span> <span class="nf">docs</span><span class="o">(</span><span class="nl">type:</span> <span class="n">Javadoc</span><span class="o">)</span> <span class="o">{</span> <span class="n">source</span> <span class="o">=</span> <span class="n">android</span><span class="o">.</span><span class="na">sourceSets</span><span class="o">.</span><span class="na">main</span><span class="o">.</span><span class="na">java</span><span class="o">.</span><span class="na">srcDirs</span> <span class="n">classpath</span> <span class="o">=</span> <span class="n">files</span><span class="o">(((</span><span class="n">Object</span><span class="o">)</span> <span class="n">android</span><span class="o">.</span><span class="na">bootClasspath</span><span class="o">.</span><span class="na">join</span><span class="o">(</span><span class="n">File</span><span class="o">.</span><span class="na">pathSeparator</span><span class="o">)))</span> <span class="o">}</span></code></pre></figure> <p>and running it produces all output from before plus the expected additions for our new package/class:</p> <div class="image-container"> <img src="/assets/images/javadoc-tool-task-extra-output-package4.png" /> </div> <p>However, similarly to when we first included Android classes in our library project, the command-line output of the <code class="highlighter-rouge">docs</code> task includes new warnings associated with our new class:</p> <figure class="highlight"><pre><code class="language-text" data-lang="text">/Users/stkent/dev/personal/libraries/javadoc-tests/library/src/main/java/com/github/stkent/javadoctests/package4/TestClassFour.java:3: error: package com.google.gson does not exist import com.google.gson.Gson; ^ /Users/stkent/dev/personal/libraries/javadoc-tests/library/src/main/java/com/github/stkent/javadoctests/package4/TestClassFour.java:10: error: cannot find symbol private final Gson gson; ^ symbol: class Gson location: class TestClassFour /Users/stkent/dev/personal/libraries/javadoc-tests/library/src/main/java/com/github/stkent/javadoctests/package4/TestClassFour.java:12: error: cannot find symbol public TestClassFour(final Gson gson) { ^ symbol: class Gson location: class TestClassFour /Users/stkent/dev/personal/libraries/javadoc-tests/library/src/main/java/com/github/stkent/javadoctests/package4/TestClassFour.java:16: error: cannot find symbol public Gson getGson() { ^ symbol: class Gson location: class TestClassFour 4 warnings BUILD SUCCESSFUL</code></pre></figure> <p>This should not be at all surprising. Based on our investigations in the last post, we know that <code class="highlighter-rouge">com.google.gson.Gson</code> is a newly-added <em>referenced class</em> whose definition is currently <em>not</em> included in the classpath we are supplying to the <code class="highlighter-rouge">javadoc</code> tool. Let’s fix that.</p> <p>The code for this portion of the post is available <a href="https://github.com/stkent/javadoc-tests/tree/528fd7f">here</a>.</p> <h1 id="adding-third-party-classes-to-the-classpath">Adding Third Party Classes To The Classpath</h1> <p>Since third party dependencies are, by definition, not bundled with our Android SDK install, we will need to do a little bit more work to locate their compiled class files. The simplest way to achieve this is to leverage the <code class="highlighter-rouge">libraryVariants</code> method exposed by the Android library project plugin. This allows us to redefine our <code class="highlighter-rouge">docs</code> task as follows:</p> <figure class="highlight"><pre><code class="language-groovy" data-lang="groovy"><span class="n">android</span><span class="o">.</span><span class="na">libraryVariants</span><span class="o">.</span><span class="na">all</span> <span class="o">{</span> <span class="n">variant</span> <span class="o">-&gt;</span> <span class="k">if</span> <span class="o">(</span><span class="n">variant</span><span class="o">.</span><span class="na">name</span> <span class="o">==</span> <span class="s1">'release'</span><span class="o">)</span> <span class="o">{</span> <span class="n">task</span> <span class="nf">docs</span><span class="o">(</span><span class="nl">type:</span> <span class="n">Javadoc</span><span class="o">)</span> <span class="o">{</span> <span class="n">source</span> <span class="o">=</span> <span class="n">variant</span><span class="o">.</span><span class="na">javaCompile</span><span class="o">.</span><span class="na">source</span> <span class="n">classpath</span> <span class="o">=</span> <span class="n">files</span><span class="o">(((</span><span class="n">Object</span><span class="o">)</span> <span class="n">android</span><span class="o">.</span><span class="na">bootClasspath</span><span class="o">.</span><span class="na">join</span><span class="o">(</span><span class="n">File</span><span class="o">.</span><span class="na">pathSeparator</span><span class="o">)))</span> <span class="n">classpath</span> <span class="o">+=</span> <span class="n">files</span><span class="o">(</span><span class="n">variant</span><span class="o">.</span><span class="na">javaCompile</span><span class="o">.</span><span class="na">classpath</span><span class="o">.</span><span class="na">files</span><span class="o">)</span> <span class="o">}</span> <span class="o">}</span> <span class="o">}</span></code></pre></figure> <p>There are a few new things going on here, so let’s break this code down a bit.</p> <ul> <li> <p>The <code class="highlighter-rouge">android.libraryVariants.all</code> method accepts a closure that will be applied to each variant of our library. (If you need a refresher on what a variant is, see <a href="http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Type-Product-Flavor-Build-Variant">the Android Gradle Plugin documentation</a>.);</p> </li> <li> <p>We define our docs task based on the variant with name ‘release’ only. This is fine for our simple test library, since we aren’t leveraging product flavors at all. For more complex library project structures, it may be desirable to define a separate Javadoc-generating task per-variant[^1];</p> </li> <li> <p>We have updated how we locate source files. Rather than explicitly linking to a single source set (<code class="highlighter-rouge">android.sourceSets.main.java.srcDirs</code>), we now reference the possibly-aggregated source set <code class="highlighter-rouge">variant.javaCompile.source</code>. This makes no difference in our simplified case, but again may be required if you are leveraging product flavors;</p> </li> <li> <p>We have added the files located at <code class="highlighter-rouge">variant.javaCompile.classpath.files</code> to the <code class="highlighter-rouge">javadoc</code> task’s classpath. These files include the compiled versions of all our dependencies, which is exactly what we were looking for! Super-convenient.</p> </li> </ul> <p>To confirm I’m not lying about that last point, here’s the content of the javadoc.options file generated by executing this redefined <code class="highlighter-rouge">docs</code> task:</p> <figure class="highlight"><pre><code class="language-text" data-lang="text">-classpath '/Users/stkent/Library/Android/sdk/platforms/android-23/android.jar:/Users/stkent/.gradle/caches/modules-2/files-2.1/com.google.code.gson/gson/2.6.2/f1bc476cc167b18e66c297df599b2377131a8947/gson-2.6.2.jar' -d '/Users/stkent/dev/personal/libraries/javadoc-tests/library/build/docs/javadoc' -doctitle 'library API' -quiet -windowtitle 'library API' '/Users/stkent/dev/personal/libraries/javadoc-tests/library/src/main/java/com/github/stkent/javadoctests/package1/TestClassOne.java' '/Users/stkent/dev/personal/libraries/javadoc-tests/library/src/main/java/com/github/stkent/javadoctests/package2/TestClassTwo.java' '/Users/stkent/dev/personal/libraries/javadoc-tests/library/src/main/java/com/github/stkent/javadoctests/package3/TestClassThree.java' '/Users/stkent/dev/personal/libraries/javadoc-tests/library/src/main/java/com/github/stkent/javadoctests/package4/TestClassFour.java'</code></pre></figure> <p>That long first line confirms that the <code class="highlighter-rouge">javadoc</code> tool will search for referenced classes within the Android platform and Gson JARs!</p> <h1 id="results">Results</h1> <p>Let’s check our actual Javadoc output and verify that it includes the full package names for classes in third-party dependencies:</p> <div class="image-container"> <img src="/assets/images/javadoc-tool-generated-testclassfour.png" /> </div> <p>Perfect :-)</p> <p>The code for this portion of the post is available <a href="https://github.com/stkent/javadoc-tests/tree/2c8a42c">here</a>.</p> <h1 id="generalizing">Generalizing</h1> <p>I mentioned in a couple places that the <code class="highlighter-rouge">docs</code> task we defined would not necessarily be suitable for a library that utilizes product flavors. For completeness, here’s how we could declare a separate Javadoc-generating task for each library variant:</p> <figure class="highlight"><pre><code class="language-groovy" data-lang="groovy"><span class="n">android</span><span class="o">.</span><span class="na">libraryVariants</span><span class="o">.</span><span class="na">all</span> <span class="o">{</span> <span class="n">variant</span> <span class="o">-&gt;</span> <span class="n">task</span><span class="o">(</span><span class="s2">"${variant.name}Docs"</span><span class="o">,</span> <span class="nl">type:</span> <span class="n">Javadoc</span><span class="o">)</span> <span class="o">{</span> <span class="n">source</span> <span class="o">=</span> <span class="n">variant</span><span class="o">.</span><span class="na">javaCompile</span><span class="o">.</span><span class="na">source</span> <span class="n">classpath</span> <span class="o">=</span> <span class="n">files</span><span class="o">(((</span><span class="n">Object</span><span class="o">)</span> <span class="n">android</span><span class="o">.</span><span class="na">bootClasspath</span><span class="o">.</span><span class="na">join</span><span class="o">(</span><span class="n">File</span><span class="o">.</span><span class="na">pathSeparator</span><span class="o">)))</span> <span class="n">classpath</span> <span class="o">+=</span> <span class="n">files</span><span class="o">(</span><span class="n">variant</span><span class="o">.</span><span class="na">javaCompile</span><span class="o">.</span><span class="na">classpath</span><span class="o">.</span><span class="na">files</span><span class="o">)</span> <span class="o">}</span> <span class="o">}</span></code></pre></figure> <h1 id="next-time">Next Time</h1> <p>Linking to referenced classes, and more!</p>Stuart KentThis is the follow-up to Adventures with Javadocs parts 1 and 2. If you haven’t already, please go read those - this post continues to build on the sample project constructed there, and explores the extra configuration needed to properly handle classes supplied by third party dependencies.“The Human Context”: A Followup2016-06-03T00:00:00-04:002016-06-03T00:00:00-04:00http://stkent.github.io/2016/06/03/the-human-context-a-followup<p>One of the risks of presenting on nascent APIs is that significant changes are fair game. Luckily, the timing of my Self Conference talk <a href="/2016/05/21/self-conference-2016.html">The Human Context: Exploring Google’s Nearby APIs</a> a couple weeks back allowed me to quickly update my slides to incorporate <em>most</em> of the changes announced at Google I/O two days earlier. Google Nearby guy <a href="https://twitter.com/andrewbunner">Andrew Bunner</a> was kind enough to review the recording of my talk and pass along some notes!</p> <!--more--> <h1 id="feedback">Feedback</h1> <h2 id="authorization">Authorization</h2> <p>Google Play Services v9.0.0 introduced an authorization-request flow that is simpler than the “<code class="highlighter-rouge">startResolutionForResult</code> dance” I used in Calling Card. This simpler flow can be accessed by enabling auto-management of the <code class="highlighter-rouge">GoogleApiClient</code> used to connect to the Nearby APIs:</p> <figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="n">googleApiClient</span> <span class="o">=</span> <span class="k">new</span> <span class="n">GoogleApiClient</span><span class="o">.</span><span class="na">Builder</span><span class="o">(</span><span class="k">this</span><span class="o">)</span> <span class="o">.</span><span class="na">addApi</span><span class="o">(</span><span class="n">Nearby</span><span class="o">.</span><span class="na">MESSAGES_API</span><span class="o">)</span> <span class="o">.</span><span class="na">addConnectionCallbacks</span><span class="o">(</span><span class="k">this</span><span class="o">)</span> <span class="o">.</span><span class="na">enableAutoManage</span><span class="o">(</span><span class="k">this</span><span class="o">,</span> <span class="k">this</span><span class="o">)</span> <span class="o">.</span><span class="na">build</span><span class="o">();</span></code></pre></figure> <p>Now there’s no need to handle connection errors corresponding to permissions issues manually in <code class="highlighter-rouge">onConnectionFailed</code>!</p> <p>However, note this important caveat from the <code class="highlighter-rouge">enableAutoManage</code> documentation (my emphasis):</p> <blockquote> <p>This method can only be used if this GoogleApiClient will be the <strong>only</strong> auto-managed client in the containing activity.</p> </blockquote> <p>Since Calling Card also uses an auto-managed <code class="highlighter-rouge">GoogleApiClient</code> instance to handle authentication (via <a href="https://developers.google.com/identity/sign-in/android/">Google Sign-In</a>), this simpler Nearby API connection flow was unavailable to me. No big deal - the manual connection error handling is laid out nicely in the <a href="https://developers.google.com/nearby/messages/android/user-consent#manually_connect_to_googleapiclient">updated documentation</a> and is a lot nicer than the previous authorization flow from Google Play Services v8.4.0 (in which permission was requested later in the process, when a publish or subscribe action was initiated). My implementation matches this more verbose flow pretty closely.</p> <h2 id="publication">Publication</h2> <p>Contrary to my original claim, it is possible to publish more than 1 message simultaneously. Doing so is not recommended; instead, use a single consolidated message structure that is capable of carrying all the data you wish to communicate. This keeps subscriber code much cleaner and avoids issues with non-simultaneous receipt of messages from a single publisher.</p> <p>It might be possible to use multiple published messages to attempt to circumvent the 100KB cap on message size, but I wouldn’t recommend it! Don’t fight the framework.</p> <h2 id="transmission">Transmission</h2> <p>One of the questions asked at the end of my talk focused on the robustness of token transmission via audio. In particular, I knew that this topic had been addressed during <a href="https://www.youtube.com/watch?v=Acdu2ZdBaZE&amp;t=7m05s">Andrew’s Google I/O presentation</a> but could not recall the details of that implementation. Andrew linked to a Wikipedia page <a href="https://en.wikipedia.org/wiki/Direct-sequence_spread_spectrum">describing the technique used</a>. My math background does not include a lot of signal processing, so I’d need to dig in deeper to really understand what’s going on here. Still, it’s nice to have the information for future reference!</p> <h1 id="conclusions">Conclusions</h1> <p>I really appreciate Andrew taking the time to watch my presentation pretty closely. Being able to engage 1-on-1 with developers working on Google APIs is pretty awesome, and often a lot more fruitful than trying to reverse-engineer architectural decisions and intentions in isolation. Plus, the benefits go both ways - in updating my presentation to use the latest and greatest version of the Nearby APIs, I <a href="https://twitter.com/andrewbunner/status/734825573105565696">uncovered and reported a bug</a> that is now fixed and will be shipped soon (if it hasn’t been already)!</p> <p>Explore, have fun, and remember to <a href="http://blog.ieeemadc.org/2016/05/28/skate-to-where-the-puck-is-going/">skate to where the puck is going</a>… ;)</p>Stuart KentOne of the risks of presenting on nascent APIs is that significant changes are fair game. Luckily, the timing of my Self Conference talk The Human Context: Exploring Google’s Nearby APIs a couple weeks back allowed me to quickly update my slides to incorporate most of the changes announced at Google I/O two days earlier. Google Nearby guy Andrew Bunner was kind enough to review the recording of my talk and pass along some notes!Self Conference 20162016-05-21T00:00:00-04:002016-05-21T00:00:00-04:00http://stkent.github.io/2016/05/21/self-conference-2016<p><a href="http://selfconference.org/">Self Conference 2016</a> wrapped up today! Held here in Detroit, it’s an annual get-together where we talk about tech, people, and especially tech people. Having attended and enjoyed the conference immensely in 2014 and 2015, I was thrilled to be returning as a speaker this year! Check out the video (audio + slides) of my talk, “The Human Context: Exploring Google’s Nearby APIs” below!</p> <div class="video-container"> <iframe width="100%" src="//www.youtube.com/embed/fKSqMsCTDiI" style="display:block; float:center; margin: 0 auto" frameborder="0" allowfullscreen=""></iframe> </div> <!--more--> <p>I also took this opportunity to upload all my previous tech talks to <a href="https://speakerdeck.com/stkent">Speaker Deck</a>, and to revamp my <a href="/talks">Talks</a> page to show:</p> <ul> <li>an embedded video of the talk, if available;</li> <li>embedded talk slides otherwise.</li> </ul> <p>Here’s a quick before and after:</p> <h1 id="before">Before</h1> <div class="image-container" style="background-color:#EEE"> <img src="/assets/images/self-conference-2016-talks-page-before.png" /> </div> <h1 id="after">After</h1> <p>(Note: static image; not clickable!)</p> <div class="image-container" style="background-color:#EEE"> <img src="/assets/images/self-conference-2016-talks-page-after.png" /> </div>Stuart KentSelf Conference 2016 wrapped up today! Held here in Detroit, it’s an annual get-together where we talk about tech, people, and especially tech people. Having attended and enjoyed the conference immensely in 2014 and 2015, I was thrilled to be returning as a speaker this year! Check out the video (audio + slides) of my talk, “The Human Context: Exploring Google’s Nearby APIs” below!Feedback++2016-02-23T00:00:00-05:002016-02-23T00:00:00-05:00http://stkent.github.io/2016/02/23/feedback-plus-plus<p>Last week, I gave an internal talk at Detroit Labs about peer feedback. We discussed different types of feedback (appreciation, coaching, and evaluation), and how to get better at giving and guiding each kind. Check out the video (slides + audio) below!</p> <div class="video-container"> <iframe width="100%" src="//www.youtube.com/embed/SyR9wnJtwVc" style="display:block; float:center; margin: 0 auto" frameborder="0" allowfullscreen=""></iframe> </div> <!--more--> <p>This was a tricky talk to give, quite far outside my comfort zone (technical topics, hooray!).</p> <p>I’ll post PDF slides later this week. The book referenced throughout the talk is <a href="http://www.amazon.com/Thanks-Feedback-Science-Receiving-Well/dp/0670014664">Thanks For The Feedback</a>. Please feel free to leave <em>me</em> feedback at <a href="http://sayat.me/stuart">sayat.me</a>. Thanks!</p>Stuart KentLast week, I gave an internal talk at Detroit Labs about peer feedback. We discussed different types of feedback (appreciation, coaching, and evaluation), and how to get better at giving and guiding each kind. Check out the video (slides + audio) below!Adventures with Javadocs, part 22016-02-05T00:00:00-05:002016-02-05T00:00:00-05:00http://stkent.github.io/2016/02/05/adventures-with-javadocs-part-2<p>This is the follow-up to <a href="/2016/01/28/adventures-with-javadocs-part-1.html">Adventures with Javadocs, part 1</a>. If you haven’t already, please go read that - this post will build on the sample project constructed there, and explore the extra configuration needed to properly handle Android framework classes.</p> <!--more--> <h1 id="introducing-android-framework-classes">Introducing Android Framework Classes</h1> <p>Let’s add a third test class to our project, again in a separate package:</p> <figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kn">package</span> <span class="n">com</span><span class="o">.</span><span class="na">github</span><span class="o">.</span><span class="na">stkent</span><span class="o">.</span><span class="na">javadoctests</span><span class="o">.</span><span class="na">package3</span><span class="o">;</span> <span class="kn">import</span> <span class="nn">android.os.Bundle</span><span class="o">;</span> <span class="cm">/** * This class depends on an Android-specific type only! */</span> <span class="kd">public</span> <span class="kd">class</span> <span class="nc">TestClassThree</span> <span class="o">{</span> <span class="kd">private</span> <span class="n">Bundle</span> <span class="n">bundle</span><span class="o">;</span> <span class="kd">public</span> <span class="nf">TestClassThree</span><span class="o">(</span><span class="n">Bundle</span> <span class="n">bundle</span><span class="o">)</span> <span class="o">{</span> <span class="k">this</span><span class="o">.</span><span class="na">bundle</span> <span class="o">=</span> <span class="n">bundle</span><span class="o">;</span> <span class="o">}</span> <span class="kd">public</span> <span class="n">Bundle</span> <span class="nf">getBundle</span><span class="o">()</span> <span class="o">{</span> <span class="k">return</span> <span class="n">bundle</span><span class="o">;</span> <span class="o">}</span> <span class="o">}</span></code></pre></figure> <p>No other changes have been made yet; in particular, our <code class="highlighter-rouge">docs</code> task is still configured as follows:</p> <figure class="highlight"><pre><code class="language-groovy" data-lang="groovy"><span class="n">task</span> <span class="nf">docs</span><span class="o">(</span><span class="nl">type:</span> <span class="n">Javadoc</span><span class="o">)</span> <span class="o">{</span> <span class="n">source</span> <span class="o">=</span> <span class="n">android</span><span class="o">.</span><span class="na">sourceSets</span><span class="o">.</span><span class="na">main</span><span class="o">.</span><span class="na">java</span><span class="o">.</span><span class="na">srcDirs</span> <span class="o">}</span></code></pre></figure> <p>Generating Javadocs by executing this task produces all output files described in the last post, as well as these by-now-predictable additions:</p> <div class="image-container"> <img src="/assets/images/javadoc-tool-task-extra-output-package3.png" /> </div> <p>However, the command-line output of the <code class="highlighter-rouge">docs</code> Gradle task includes some new warnings we should understand and resolve:</p> <figure class="highlight"><pre><code class="language-text" data-lang="text">/Users/stuart/dev/personal/libraries/JavadocTests/library/src/main/java/com/github/stkent/javadoctests/package3/TestClassThree.java:3: error: package android.os does not exist import android.os.Bundle; ^ /Users/stuart/dev/personal/libraries/JavadocTests/library/src/main/java/com/github/stkent/javadoctests/package3/TestClassThree.java:10: error: cannot find symbol private final Bundle bundle; ^ symbol: class Bundle location: class TestClassThree /Users/stuart/dev/personal/libraries/JavadocTests/library/src/main/java/com/github/stkent/javadoctests/package3/TestClassThree.java:12: error: cannot find symbol public TestClassThree(final Bundle bundle) { ^ symbol: class Bundle location: class TestClassThree /Users/stuart/dev/personal/libraries/JavadocTests/library/src/main/java/com/github/stkent/javadoctests/package3/TestClassThree.java:16: error: cannot find symbol public Bundle getBundle() { ^ symbol: class Bundle location: class TestClassThree 4 warnings BUILD SUCCESSFUL</code></pre></figure> <p>The code for this portion of the post is available <a href="https://github.com/stkent/javadoc-tests/tree/9e9125850ba13b7988ac5105fe826cccd6a2f681">here</a>.</p> <h1 id="generated-documentation">Generated Documentation</h1> <p>Here’s the summary generated for <code class="highlighter-rouge">TestClassThree</code>:</p> <div class="image-container"> <img src="/assets/images/javadoc-tool-generated-testclassthree.png" /> </div> <p>The reported warnings did not prevent the generation of appropriate documentation. So, what <em>was</em> their impact?</p> <p>In the previous post, we observed that constructor parameters were documented in two different formats:</p> <ol start="1"> <li>as a hyperlink, without a fully qualified class name, when the parameter type was user-created;</li> <li>as plain text, with a fully qualified class name, when the parameter type was auto-imported.</li> </ol> <p>The generated documentation for <code class="highlighter-rouge">TestClassThree</code> introduces a third variation:</p> <ol start="3"> <li>as plain text, without a fully qualified class name, when the parameter type is not user-created <em>or</em> auto-imported.</li> </ol> <p>As per the last post, the lack of a hyperlink in format 3 is straightforward to understand - since the Android <code class="highlighter-rouge">Bundle</code> class is not part of the collection of source files for which we are generating documentation, there’s no way the <code class="highlighter-rouge">javadoc</code> tool could know where to link to!<sup id="fnref:1"><a href="#fn:1" class="footnote">1</a></sup> To help us understand the difference between text formats 2 and 3, let’s return to the <code class="highlighter-rouge">javadoc</code> documentation and try to understand the “package does not exist” and “symbol not found” errors we received above.</p> <h1 id="class-classifications">Class Classifications</h1> <p>The <code class="highlighter-rouge">javadoc</code> documentation <a href="http://docs.oracle.com/javase/6/docs/technotes/tools/windows/javadoc.html#terminology">introduces</a> the following terminology to describe the different roles that can be played by Java classes during a documentation-generating run:</p> <blockquote> <p><strong>documented/included classes:</strong> The classes and interfaces for which detailed documentation is generated during a javadoc run.</p> </blockquote> <blockquote> <p><strong>referenced classes:</strong> The classes and interfaces that are explicitly referred to in the definition (implementation) or doc comments of the documented classes and interfaces.</p> </blockquote> <p><code class="highlighter-rouge">TestClassOne</code>, <code class="highlighter-rouge">TestClassTwo</code> and <code class="highlighter-rouge">TestClassThree</code> are clearly examples of documented classes. What about <code class="highlighter-rouge">java.lang.String</code> and <code class="highlighter-rouge">android.os.Bundle</code>? Both are “explicitly referred to in the definition (implementation) or doc comments of the documented classes and interfaces”, so both are referenced classes. However, this additional commentary regarding referenced classes gives us a clue as to why <code class="highlighter-rouge">java.lang.String</code> and <code class="highlighter-rouge">android.os.Bundle</code> are handled differently by <code class="highlighter-rouge">javadoc</code>:</p> <blockquote> <p>When the Javadoc tool is run, it should load into memory all of the referenced classes in javadoc’s bootclasspath and classpath. […] The Javadoc tool can derive enough information from the .class files to determine their existence and the fully-qualified names of their members.</p> </blockquote> <p>The path stored in bootclasspath represents the location of Java’s Bootstrap classes (the classes that implement the <a href="https://en.wikipedia.org/wiki/Java_(software_platform)#Platform">Java platform</a>). This has default value <code class="highlighter-rouge">$JAVA_HOME/jre/lib</code>, which contains (among other things) compiled class files that collectively form Java’s standard library. In particular, the rt.jar JAR contains a compiled String.class file.<sup id="fnref:2"><a href="#fn:2" class="footnote">2</a></sup></p> <p>The path stored in classpath represents the location of all referenced classes that are not part of the Java platform. By default, this is an empty path (i.e. no locations are searched to locate additional referenced classes).</p> <h1 id="mystery-understood">Mystery Understood</h1> <p>We now have enough information to understand the differences between constructor parameter text formats 2 and 3. Recall that we are able to inspect the command-line options passed to the <code class="highlighter-rouge">javadoc</code> tool for each invocation of the <code class="highlighter-rouge">docs</code> task by peeking at the javadoc.options file. For the current codebase, this file has the following content:</p> <figure class="highlight"><pre><code class="language-text" data-lang="text">-d '/Users/stuart/dev/personal/libraries/JavadocTests/library/build/docs/javadoc' -doctitle 'library API' -quiet -windowtitle 'library API' '/Users/stuart/dev/personal/libraries/JavadocTests/library/src/main/java/com/github/stkent/javadoctests/package1/TestClassOne.java' '/Users/stuart/dev/personal/libraries/JavadocTests/library/src/main/java/com/github/stkent/javadoctests/package2/TestClassTwo.java' '/Users/stuart/dev/personal/libraries/JavadocTests/library/src/main/java/com/github/stkent/javadoctests/package3/TestClassThree.java'</code></pre></figure> <p>which indicates that we are utilizing the default bootclasspath and classpath values. Since <code class="highlighter-rouge">java.lang.String</code> is part of the Bootstrap classes, the <code class="highlighter-rouge">javadoc</code> tool is able to determine the fully-qualified class name and uses this in the documented method signature for <code class="highlighter-rouge">TestClassOne</code>:</p> <figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="n">TestClassOne</span><span class="o">(</span><span class="n">java</span><span class="o">.</span><span class="na">lang</span><span class="o">.</span><span class="na">String</span> <span class="n">string</span><span class="o">)</span></code></pre></figure> <p>However, <code class="highlighter-rouge">android.os.Bundle</code> is <em>not</em> part of the (Java) Bootstrap classes, so the <code class="highlighter-rouge">javadoc</code> tool cannot load the class into memory and determine its fully-qualified name. This leads to the warnings we saw in the output of our <code class="highlighter-rouge">docs</code> task, and to the text format of the documented method signature for <code class="highlighter-rouge">TestClassThree</code>:</p> <figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="n">TestClassThree</span><span class="o">(</span><span class="n">Bundle</span> <span class="n">bundle</span><span class="o">)</span></code></pre></figure> <h1 id="mystery-solved">Mystery Solved</h1> <p>Now that we know why the warnings occur, the solution is but a small step away. Our goal should be to modify the classpath used by the <code class="highlighter-rouge">javadoc</code> tool so that it includes the compiled Android framework classes. We can achieve this by modifying our task configuration as follows:</p> <figure class="highlight"><pre><code class="language-groovy" data-lang="groovy"><span class="n">task</span> <span class="nf">docs</span><span class="o">(</span><span class="nl">type:</span> <span class="n">Javadoc</span><span class="o">)</span> <span class="o">{</span> <span class="n">source</span> <span class="o">=</span> <span class="n">android</span><span class="o">.</span><span class="na">sourceSets</span><span class="o">.</span><span class="na">main</span><span class="o">.</span><span class="na">java</span><span class="o">.</span><span class="na">srcDirs</span> <span class="n">classpath</span> <span class="o">=</span> <span class="n">files</span><span class="o">(((</span><span class="n">Object</span><span class="o">)</span> <span class="n">android</span><span class="o">.</span><span class="na">bootClasspath</span><span class="o">.</span><span class="na">join</span><span class="o">(</span><span class="n">File</span><span class="o">.</span><span class="na">pathSeparator</span><span class="o">)))</span> <span class="o">}</span></code></pre></figure> <p>This addition uses the <code class="highlighter-rouge">bootClassPath</code> property that is conveniently exposed by the Android Gradle plugin to locate the appropriate Android classes. Re-running the <code class="highlighter-rouge">docs</code> task using this configuration results in no warnings, and a documented method signature for <code class="highlighter-rouge">TestClassThree</code> whose format now matches that of <code class="highlighter-rouge">TestClassOne</code>:</p> <figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="n">TestClassThree</span><span class="o">(</span><span class="n">android</span><span class="o">.</span><span class="na">os</span><span class="o">.</span><span class="na">Bundle</span> <span class="n">bundle</span><span class="o">)</span></code></pre></figure> <p>The code for this portion of the post is available <a href="https://github.com/stkent/javadoc-tests/tree/806e5bfcad8000949bfa5158ecc9b0a90f6b377a">here</a>.</p> <h1 id="next-time">Next Time</h1> <p>Up next: handling third-party dependencies!</p> <div class="footnotes"> <ol> <li id="fn:1"> <p>Note that it is possible to introduce links to the documentation hosted on d.android.com with a little more configuration work - we’ll address this in a later post in the series! <a href="#fnref:1" class="reversefootnote">&#8617;</a></p> </li> <li id="fn:2"> <p>You can verify this yourself by unarchiving the <code class="highlighter-rouge">$JAVA_HOME/jre/lib/rt.jar</code> JAR, and navigating to the <code class="highlighter-rouge">java/lang</code> subfolder! <a href="#fnref:2" class="reversefootnote">&#8617;</a></p> </li> </ol> </div>Stuart KentThis is the follow-up to Adventures with Javadocs, part 1. If you haven’t already, please go read that - this post will build on the sample project constructed there, and explore the extra configuration needed to properly handle Android framework classes.Adventures with Javadocs, part 12016-01-28T00:00:00-05:002016-01-28T00:00:00-05:00http://stkent.github.io/2016/01/28/adventures-with-javadocs-part-1<p>Hello! It’s been a while since I wrote a post. This is because I have been busy: first learning Swift and iOS development on a crunchy client project, and more lately working on some open source Android tools. I still have more to write about interpolators - fret not! - but right now it’s easier for me to write posts about my primary foci.</p> <p>Part of publishing high-quality libraries is providing high-quality documentation, which in Android-land means: high-quality Javadocs. There are tons of good resources around that explain proper Javadoc comment format and content, so in this series we’ll explore the actual generation of documentation using Gradle/Android Studio. Let’s begin!</p> <!--more--> <h1 id="javadoc-task-type-basics">Javadoc Task Type Basics</h1> <p>The Gradle Java plugin provides a <a href="https://docs.gradle.org/current/dsl/org.gradle.api.tasks.javadoc.Javadoc.html">template Javadoc task</a> with the following description:</p> <blockquote> <p>Generates HTML API documentation for Java classes.</p> <p>If you create your own Javadoc tasks remember to specify the ‘source’ property! Without source the Javadoc task will not create any documentation.</p> </blockquote> <p>For an Android library project, the simplest configuration of this task would therefore be:</p> <figure class="highlight"><pre><code class="language-groovy" data-lang="groovy"><span class="n">apply</span> <span class="nl">plugin:</span> <span class="s1">'com.android.library'</span> <span class="n">android</span> <span class="o">{</span> <span class="n">compileSdkVersion</span> <span class="mi">23</span> <span class="n">buildToolsVersion</span> <span class="s2">"23.0.2"</span> <span class="n">defaultConfig</span> <span class="o">{</span> <span class="n">minSdkVersion</span> <span class="mi">16</span> <span class="n">targetSdkVersion</span> <span class="mi">23</span> <span class="n">versionName</span> <span class="s2">"1.0.0"</span> <span class="o">}</span> <span class="o">}</span> <span class="n">task</span> <span class="nf">docs</span><span class="o">(</span><span class="nl">type:</span> <span class="n">Javadoc</span><span class="o">)</span> <span class="o">{</span> <span class="n">source</span> <span class="o">=</span> <span class="n">android</span><span class="o">.</span><span class="na">sourceSets</span><span class="o">.</span><span class="na">main</span><span class="o">.</span><span class="na">java</span><span class="o">.</span><span class="na">srcDirs</span> <span class="o">}</span></code></pre></figure> <p>[This assumes a project with no other dependencies, no flavor/type/variant-specific source files, etc.]</p> <p>Let’s add a super-simple pair of test classes to the project, each in their own package:</p> <figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kn">package</span> <span class="n">com</span><span class="o">.</span><span class="na">github</span><span class="o">.</span><span class="na">stkent</span><span class="o">.</span><span class="na">javadoctests</span><span class="o">.</span><span class="na">package1</span><span class="o">;</span> <span class="cm">/** * This class depends on an automatically-imported type only! */</span> <span class="kd">public</span> <span class="kd">class</span> <span class="nc">TestClassOne</span> <span class="o">{</span> <span class="kd">private</span> <span class="n">String</span> <span class="n">string</span><span class="o">;</span> <span class="kd">public</span> <span class="nf">TestClassOne</span><span class="o">(</span><span class="n">String</span> <span class="n">string</span><span class="o">)</span> <span class="o">{</span> <span class="k">this</span><span class="o">.</span><span class="na">string</span> <span class="o">=</span> <span class="n">string</span><span class="o">;</span> <span class="o">}</span> <span class="kd">public</span> <span class="n">String</span> <span class="nf">getString</span><span class="o">()</span> <span class="o">{</span> <span class="k">return</span> <span class="n">string</span><span class="o">;</span> <span class="o">}</span> <span class="o">}</span></code></pre></figure> <figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kn">package</span> <span class="n">com</span><span class="o">.</span><span class="na">github</span><span class="o">.</span><span class="na">stkent</span><span class="o">.</span><span class="na">javadoctests</span><span class="o">.</span><span class="na">package2</span><span class="o">;</span> <span class="kn">import</span> <span class="nn">com.github.stkent.javadoctests.package1.TestClassOne</span><span class="o">;</span> <span class="cm">/** * This class depends on a user-created and -imported type only! */</span> <span class="kd">public</span> <span class="kd">class</span> <span class="nc">TestClassTwo</span> <span class="o">{</span> <span class="kd">private</span> <span class="n">TestClassOne</span> <span class="n">testClassOne</span><span class="o">;</span> <span class="kd">public</span> <span class="nf">TestClassTwo</span><span class="o">(</span><span class="n">TestClassOne</span> <span class="n">testClassOne</span><span class="o">)</span> <span class="o">{</span> <span class="k">this</span><span class="o">.</span><span class="na">testClassOne</span> <span class="o">=</span> <span class="n">testClassOne</span><span class="o">;</span> <span class="o">}</span> <span class="kd">public</span> <span class="n">TestClassOne</span> <span class="nf">getTestClassOne</span><span class="o">()</span> <span class="o">{</span> <span class="k">return</span> <span class="n">testClassOne</span><span class="o">;</span> <span class="o">}</span> <span class="o">}</span></code></pre></figure> <p>and generate Javadocs by executing our new <code class="highlighter-rouge">docs</code> task:</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash">./gradlew library:clean library:docs</code></pre></figure> <p>which produces the following output files:</p> <div class="image-container"> <img src="/assets/images/javadoc-tool-task-output.png" /> </div> <p>Note in particular that the directory structure of the generated website matches the package structure of the original source files. For example, the class <code class="highlighter-rouge">TestClassOne</code> is part of the <code class="highlighter-rouge">com.github.stkent.javadoctests.package1</code> package, and its corresponding documentation page is inside the com/github/stkent/javadoctests/package1 subdirectory of the generated website.</p> <h1 id="generated-documentation">Generated Documentation</h1> <p>Before proceeding, I’d like to review a pair of sample pages from this output. This will provide some important context for the next post in this series.</p> <p>Here’s the summary generated for <code class="highlighter-rouge">TestClassOne</code>:</p> <div class="image-container"> <img src="/assets/images/javadoc-tool-generated-testclassone.png" /> </div> <p>and the corresponding summary generated for <code class="highlighter-rouge">TestClassTwo</code>:</p> <div class="image-container"> <img src="/assets/images/javadoc-tool-generated-testclasstwo.png" /> </div> <p>The <code class="highlighter-rouge">TestClassTwo</code> constructor signature includes a hyperlink to the generated documentation page for its lone parameter type (<code class="highlighter-rouge">TestClassOne</code>). Based on the correspondence between directory structure and package structure we identified earlier, I would postulate that the <code class="highlighter-rouge">javadoc</code> tool generates this link by parsing the following import statement in <code class="highlighter-rouge"quot;>TestClassTwo</code>:</p> <figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kn">import</span> <span class="nn">com.github.stkent.javadoctests.package1.TestClassOne</span><span class="o">;</span></code></pre></figure> <p>On the other hand, because the Java <code class="highlighter-rouge">String</code> type is not part of the source we provided to the <code class="highlighter-rouge">docs</code> task, the <code class="highlighter-rouge">javadoc</code> tool has no way of determining an equivalent hyperlink target to use for the <code class="highlighter-rouge">TestClassOne</code> constructor parameter type.</p> <h1 id="under-the-hood">Under The Hood</h1> <p>Gradle’s Javadoc task type acts as a wrapper around the command-line <code class="highlighter-rouge">javadoc</code> tool included with every JDK. To locate yours, run <code class="highlighter-rouge">which javadoc</code> from the command line (the path to this file should match your <code class="highlighter-rouge">JAVA_HOME</code> environment variable, if set):</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="gp">$ </span>which javadoc /Library/Java/JavaVirtualMachines/jdk1.8.0_65.jdk/Contents/Home/bin/javadoc</code></pre></figure> <p>The temporary javadoc.options file generated by our Gradle task contains options and arguments that are forwarded to this command-line tool. For our example, the javadocs.options file contains these lines:</p> <figure class="highlight"><pre><code class="language-text" data-lang="text">-d '/Users/stuart/dev/personal/libraries/JavadocTests/library/build/docs/javadoc' -doctitle 'library API' -quiet -windowtitle 'library API' '/Users/stuart/dev/personal/libraries/JavadocTests/library/src/main/java/com/github/stkent/javadoctests/package1/TestClassOne.java' '/Users/stuart/dev/personal/libraries/JavadocTests/library/src/main/java/com/github/stkent/javadoctests/package2/TestClassTwo.java'</code></pre></figure> <p>This is pretty minimal configuration - we are setting the output directory, titles to use for the generated website, and providing the collection of source files for which we would like documentation to be generated. To confirm the claim that Gradle’s Javadoc task calls <code class="highlighter-rouge">javadoc</code> with these options and arguments, we can run:</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash">./gradlew library:clean</code></pre></figure> <p>followed by:</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash">javadoc -d <span class="s1">'/Users/stuart/dev/personal/libraries/JavadocTests/library/build/docs/javadoc'</span> -doctitle <span class="s1">'library API'</span> -quiet -windowtitle <span class="s1">'library API'</span> <span class="s1">'/Users/stuart/dev/personal/libraries/JavadocTests/library/src/main/java/com/github/stkent/javadoctests/package1/TestClassOne.java'</span> <span class="s1">'/Users/stuart/dev/personal/libraries/JavadocTests/library/src/main/java/com/github/stkent/javadoctests/package2/TestClassTwo.java'</span></code></pre></figure> <p>which produces the output files shown below (viewed within Android Studio):</p> <div class="image-container"> <img src="/assets/images/javadoc-tool-cli-output.png" /> </div> <p>This is identical to the output of the Gradle task itself, minus the temporary javadoc.options file! Which is neat, because it means we can leverage all the existing <code class="highlighter-rouge">javadoc</code> tool documentation to help us overcome some of the challenges we’ll be facing in the next post, in which we add classes from the Android framework and third-party dependencies to our sample project.</p> <p>The code for this post is available <a href="https://github.com/stkent/javadoc-tests/tree/a3f27d5904648f6a32c55399c1f2bdeb265f99d6">here</a>.</p>Stuart KentHello! It’s been a while since I wrote a post. This is because I have been busy: first learning Swift and iOS development on a crunchy client project, and more lately working on some open source Android tools. I still have more to write about interpolators - fret not! - but right now it’s easier for me to write posts about my primary foci. Part of publishing high-quality libraries is providing high-quality documentation, which in Android-land means: high-quality Javadocs. There are tons of good resources around that explain proper Javadoc comment format and content, so in this series we’ll explore the actual generation of documentation using Gradle/Android Studio. Let’s begin!Droidcon NYC 2015 Debrief2015-08-31T00:00:00-04:002015-08-31T00:00:00-04:00http://stkent.github.io/2015/08/31/droidcon-NYC-2015-debrief<p>It was great to see so many prominent members of the Android community attending and presenting at Droidcon NYC 2015. Let’s review some of my favorite talks! If you only have time to watch 6 of the ~65 talks recorded, I’d suggest starting here. (Note the diversity of the topics represented in this list - a tribute to the all-encompassing scope of Droidcon NYC!)</p> <!--more--> <h2 id="simple-http-with-retrofit-2---jake-whartonhttpstwittercomjakewharton-slideshttpsspeakerdeckcomjakewhartonsimple-http-with-retrofit-2-droidcon-nyc-2015--videohttpswwwyoutubecomwatchvkiaoqbau3ea">Simple HTTP with Retrofit 2 - <a href="https://twitter.com/JakeWharton">Jake Wharton</a> (<a href="https://speakerdeck.com/jakewharton/simple-http-with-retrofit-2-droidcon-nyc-2015">Slides</a> / <a href="https://www.youtube.com/watch?v=KIAoQbAu3eA">Video</a>)</h2> <p>Though this talk didn’t contain many surprises if you’ve been following the <a href="https://github.com/square/retrofit/issues/297">proposed Retrofit 2 spec</a> and <a href="https://github.com/square/retrofit/pull/845">recent progress</a>, Jake presented an exceptionally clear discussion of both the limitations of Retrofit 1, and their solutions as implemented in Retrofit 2. At a high level, the most significant changes may be summarized as follows:</p> <ul> <li> <p>a new <code class="highlighter-rouge">Call</code> type is introduced to represent a <em>single</em> network request and response. Each instance encapsulates both raw and processed (e.g. deserialized) request and response information. A <code class="highlighter-rouge">Call</code> object may be used to execute a network request either synchronously <em>or</em> asynchronously - no more need to include duplicate entries in our annotated services to achieve this!</p> </li> <li> <p>OkHttp is now the only supported HTTP client. This permits the removal of several public classes that existed solely to support alternative HTTP client implementations, as well as adding the ability to safely expose OkHttp types in the public API of Retrofit (e.g. to allow direct inspection or customization of the request/response);</p> </li> <li> <p>deserialization can now be attempted by multiple prioritized type converters. Semantically-related network requests that happen to return data in different formats (e.g. json vs xml) can now be grouped in a single annotated service;</p> </li> <li> <p>request cancelation is now supported.</p> </li> </ul> <p>As icing on the cake, Jake <a href="https://github.com/square/retrofit/blob/master/CHANGELOG.md">released Retrofit 2.0.0-beta1</a> right after his talk. The API is supposed to be close to final, and the library fairly stable. Have at it!</p> <div class="image-container"> <img src="/assets/images/droidcon-nyc-2015-debrief-jake-wharton.jpg" width="50%" /> </div> <h2 id="gradle-from-user-to-addict---jake-ouellettehttpstwittercomjakeout-slides--videohttpswwwyoutubecomwatchv-c7ttnpj7ms">Gradle: From User to Addict - <a href="https://twitter.com/jakeout">Jake Ouellette</a> (<a href="">Slides</a> / <a href="https://www.youtube.com/watch?v=-C7TtnPJ7ms">Video</a>)</h2> <p>This talk was thrilling - fast-paced and packed with great information. If you are looking to become more experienced with Gradle, I’d recommend this talk as a follow-up to Dan Lew’s <a href="https://www.youtube.com/watch?v=fHhf1xG0pIA">excellent introductory video</a>. Dan focused more on basic Groovy syntax and Gradle task sequencing, and Jake extends these ideas with detailed discussions of Groovy’s unusual scope resolution and Gradle task sequencing nuances. Fair warning: you’ll definitely need to pause and rewind in a couple spots to give yourself time to properly parse what Jake says.</p> <h2 id="data-binding-techniques---jacob-tabakhttpstwittercomjacobtabak-slideshttpsspeakerdeckcomjacobtabakdata-binding-techniques-at-droidcon-nyc-2015--videohttpswwwyoutubecomwatchvwdubxwztkny">Data Binding Techniques - <a href="https://twitter.com/JacobTabak">Jacob Tabak</a> (<a href="https://speakerdeck.com/jacobtabak/data-binding-techniques-at-droidcon-nyc-2015">Slides</a> / <a href="https://www.youtube.com/watch?v=WdUbXWztKNY">Video</a>)</h2> <p>Most of the usage information presented here was familiar from Google I/O videos. However, Jacob provided some interesting context around data binding in general, and Google’s implementation of data binding in particular:</p> <ul> <li> <p>originally slated for release as part of Android Lollipop, the ballooning scope of that OS release meant that data binding support was temporarily shelved last year;</p> </li> <li> <p>data binding implementations have historically suffered from performance issues. However, in some cases, the Android implementation provided is more performant than equivalent hand-written code. For example, manually calling <code class="highlighter-rouge">findViewById</code> 5 times in your <code class="highlighter-rouge">Activity</code>’s <code class="highlighter-rouge">onCreate</code> method triggers five separate traversals of the view hierarchy; with data binding, all 5 view references can be initialized in a single pass.</p> </li> </ul> <p>Despite vaguely knowing about almost all of the presented material already, I left this talk feeling super-motivated to begin exploring data binding immediately. The current API is pretty much finalized, so it’s safe to begin using; however, note that IDE support remains very limited. If you’re still skeptical, consider the awesome implications of this slide alone:</p> <div class="image-container"> <img src="/assets/images/droidcon-nyc-2015-debrief-binding-adapter.png" width="75%" /> </div> <h2 id="using-styles-and-themes-without-going-crazy---dan-lewhttpstwittercomdanlew42-slideshttpsspeakerdeckcomdlewusing-styles-and-themes-without-going-crazy-1--videohttpswwwyoutubecomwatchvjr8hjdvghak">Using Styles and Themes Without Going Crazy - <a href="https://twitter.com/danlew42">Dan Lew</a> (<a href="https://speakerdeck.com/dlew/using-styles-and-themes-without-going-crazy-1">Slides</a> / <a href="https://www.youtube.com/watch?v=Jr8hJdVGHAk">Video</a>)</h2> <p>The beginning of this talk seemed somewhat familiar, and some rapid Googling turned up Dan’s <a href="http://blog.danlew.net/2014/11/19/styles-on-android/">late-2014 article</a> on the same topic. I read that article when it was published, and remember being excited that someone had finally formalized my half-formed thoughts regarding semantically-identical vs visually-identical view styling.</p> <p>This talk expands upon that original post, and includes</p> <ul> <li> <p>more details on how to discover available attributes and create custom attributes;</p> </li> <li> <p>tips for determining which attributes are influencing which on-screen elements (using a debug theme, or hierarchy viewer’s “dump theme” button).</p> </li> </ul> <div class="image-container"> <img src="/assets/images/droidcon-nyc-2015-debrief-just-deduce-it.jpg" width="50%" /> </div> <h2 id="why-hello-there-camera-2-api---huyen-daohttpstwittercomqueencodemonkey-slideshttpsspeakerdeckcomrandomlytypingandroid-camera-2-api--videohttpswwwyoutubecomwatchvblsckjkopy8">Why, Hello There, Camera 2 API - <a href="https://twitter.com/queencodemonkey">Huyen Dao</a> (<a href="https://speakerdeck.com/randomlytyping/android-camera-2-api">Slides</a> / <a href="https://www.youtube.com/watch?v=BLScKJkOpy8">Video</a>)</h2> <p>The new Camera 2 API is a radical departure from the original Camera API. Initially, the magnitude of this change can seem surprisingly. The new API is a little more powerful (still shot burst mode is easier to implement, for example), but it’s far from obvious that this increased flexibility necessitated a total API overhaul. Huyen’s talk focused on the conceptual shift in low-level camera architecture introduced in version 3 of Android’s camera Hardware Abstraction Layer - in particular, the change in how requests for camera data are represented - and explained how we can interpret the changes in the high-level Camera 2 API in terms of these low-level details.</p> <p>A great example of a talk that promotes understanding, rather than mere knowledge, of an API; the why, not just the what.</p> <h2 id="android-is-the-world-phone---corey-leigh-latislawhttpstwittercomcoreylatislaw-slideshttpsspeakerdeckcomcolabugandroid-is-the-world-phone--videohttpswwwyoutubecomwatchvsyuhdpxr8mm">Android is the World Phone - <a href="https://twitter.com/corey_latislaw">Corey Leigh Latislaw</a> (<a href="https://speakerdeck.com/colabug/android-is-the-world-phone">Slides</a> / <a href="https://www.youtube.com/watch?v=sYUHdPXR8MM">Video</a>)</h2> <p>Corey described the challenges developers face when building apps for customers in emerging and developing markets. Unlike most of the talks I’ve seen on this topic, Corey’s managed to address both philosophical and practical issues. Especially thought-provoking were the literacy statistics for these populations - could you build an app that is intuitive enough to be used by someone who cannot read?</p>Stuart KentIt was great to see so many prominent members of the Android community attending and presenting at Droidcon NYC 2015. Let’s review some of my favorite talks! If you only have time to watch 6 of the ~65 talks recorded, I’d suggest starting here. (Note the diversity of the topics represented in this list - a tribute to the all-encompassing scope of Droidcon NYC!)Building Smooth Paths using Bézier Curves2015-07-03T00:00:00-04:002015-07-03T00:00:00-04:00http://stkent.github.io/2015/07/03/building-smooth-paths-using-bezier-curves<p><a href="/2015/06/07/an-intro-to-pathinterpolatorcompat.html">Last post</a>, we built a super-simple <code class="highlighter-rouge">Path</code>-based interpolator using straight line segments. To produce smoother interpolators, without corners - typically preferred for animating motion - we’ll need correspondingly smooth generating Paths. Our primary goal in this post, then, will be:</p> <ul> <li>given a sequence of $n$ points in the cartesian plane, calculate a smooth <code class="highlighter-rouge">Path</code> passing through all points in order.</li> </ul> <!--more--> <p>We’ll start with the simplest possible case, and generalize from there.</p> <h1 id="n--2">$n$ = 2</h1> <p>This one’s a gimme - we can use <code class="highlighter-rouge">Path.lineTo(...)</code> to connect the two given points with a straight line. While not very exciting, this curve does satisfy the smoothness condition since there are definitely no corners!</p> <div class="image-container"> <img src="/assets/images/building-smooth-paths-using-bezier-curves-2-point.png" width="30%" /> </div> <h1 id="n--2-1">$n$ &gt; 2</h1> <p>When given more than two distinct points, it’s no longer possible to connect them smoothly using straight lines only<sup id="fnref:1"><a href="#fn:1" class="footnote">1</a></sup>:</p> <div class="image-container"> <img src="/assets/images/building-smooth-paths-using-bezier-curves-3-point-linear.png" width="30%" /> </div> <p>We instead need to build our <code class="highlighter-rouge">Path</code> from components with a greater number of degrees of freedom. In particular, we need to be able to specify both the end point positions <strong>and</strong> one or more derivatives of each component at those end points. That way, we can be sure that the composite curve created by joining together all our components will be smooth at the joins.</p> <h2 id="enter-cubic-beacutezier-curves">Enter Cubic Bézier Curves</h2> <p>Most of you will have played with Bézier curves before in consumer graphics programs. They are usually constructed in two steps:</p> <ol> <li>fix the locations of the two end points ($P_0$ and $P_3$ in the diagram below);</li> <li>position two <em>control points</em> ($P_1$ and $P_2$ in the diagram below) that completely determine the shape of the curve connecting the two end points.</li> </ol> <div class="image-container"> <img src="/assets/images/building-smooth-paths-using-bezier-curves-wiki.png" width="50%" /> </div> <p>We can append a single cubic Bézier curve to an existing <code class="highlighter-rouge">Path</code> using the <code class="highlighter-rouge">Path.cubicTo(...)</code> method. But how do we make sure that we choose control points that yield a smooth composite curve? In other words, how do we ensure that our composite curve looks like this:</p> <div class="image-container"> <img src="/assets/images/building-smooth-paths-using-bezier-curves-3-point-bezier-smooth.png" width="30%" /> </div> <p>and not like this:</p> <div class="image-container"> <img src="/assets/images/building-smooth-paths-using-bezier-curves-3-point-bezier-corner.png" width="34%" /> </div> <p>Most of the rest of this post is dedicated to figuring out how to choose the ‘right’ control points for arbitrary given end points (aka <em>knots</em> in Bézier curve lingo). It’s not too hairy - there’s a lot of algebra, sure, but because cubic Bézier curves can be represented as polynomials<sup id="fnref:2"><a href="#fn:2" class="footnote">2</a></sup>, the numbers work out nicely :). The language is fairly formal so that I can refer back to the derivations with confidence in the future. If you’re really not keen on math, you can skip ahead to the <a href="#results">results section</a> now.</p> <h1 id="notation">Notation</h1> <p>Let $\lbrace k_i \in \mathbb{R}^m : i \in 0,\ldots,n \rbrace$ represent a collection of $n+1$ knots.</p> <p>Let $\Gamma_i$ represent any cubic Bézier curve connecting $k_i$ to $k_{i+1}$ for $i \in 0,\ldots,n-1$. Each $\Gamma_i$ may then be represented by a parametric equation of the form</p> <script type="math/tex; mode=display">\Gamma_i(t) = (1-t)^3 k_i + 3(1-t)^2 t c_{i,0} + 3(1-t) t^2 c_{i,1} + t^3 k_{i+1}</script> <p>where $t$ ranges between $0$ and $1$, and $c_{i,0} \in \mathbb{R}^m$ and $c_{i,1} \in \mathbb{R}^m$ are the intermediate control points that determine the curvature of $\Gamma_i$.</p> <h1 id="formal-goal">Formal Goal</h1> <p>For any given collection of knots, we aim to compute control points that guarantee the composite curve $\Gamma$ formed by connecting all the individual Bézier curves $\Gamma_i$ satisfies the following conditions:</p> <ul> <li>$\Gamma$ is twice-differentiable everywhere;</li> <li>$\Gamma$ satisfies natural boundary conditions (i.e. $\Gamma’’ = 0$ at each end).</li> </ul> <p>Each $\Gamma_i$ is clearly $ C^\infty $ away from the endpoints $k_i$ and $k_{i+1}$, so the first condition above is equivalent to requiring that $\Gamma$ be twice-differentiable at every knot.</p> <p>The second condition is applied to fully specify the problem, leading to a unique solution and making calculations simpler.</p> <h1 id="derivation">Derivation</h1> <p>Note that</p> <script type="math/tex; mode=display">\Gamma_i^{\prime}(t) = 3 \left[ - (1-t)^2 k_i + (3t-1)(t-1) c_{i,0} - t(3t-2) c_{i,1} + t^2 k_{i+1} \right]</script> <p>and</p> <script type="math/tex; mode=display">\Gamma_i^{\prime\prime}(t) = 6 \left[ (1-t) k_i + (3t-2) c_{i,0} - (3t-1) c_{i,1} + t k_{i+1} \right].</script> <p>For $\Gamma$ to be $C^2$ at each interior knot, we require that</p> <script type="math/tex; mode=display">\left.\Gamma_{i-1}^{\prime}\right\vert_{k_{i}} = \left.\Gamma_{i}^{\prime}\right\vert_{k_{i}} \hspace{0.2in} \text{ and } \hspace{0.2in} \left.\Gamma_{i-1}^{\prime\prime}\right\vert_{k_{i}} = \left.\Gamma_{i}^{\prime\prime}\right\vert_{k_{i}}</script> <p>for $i \in \lbrace 1,\ldots,n-1 \rbrace$. Substituting the derivative expressions computed above, we see that these equalities are equivalent to choosing control points that satisfy</p> <script type="math/tex; mode=display">c_{i-1,1} + c_{i,0} = 2k_{i} \text{ for } i \in \lbrace 1,\ldots,n-1 \rbrace</script> <p>and</p> <script type="math/tex; mode=display">c_{i-1,0} - 2c_{i-1,1} = c_{i,1} - 2c_{i,0} \text{ for } i \in \lbrace 1,\ldots,n-1 \rbrace.</script> <p>So far, we have $2(n-1)$ constraints for $2n$ control points. The final constraints that will uniquely determine the locations of all control points are the boundary conditions</p> <script type="math/tex; mode=display">\left.\Gamma_0^{\prime\prime}\right\vert_{k_0} = 0 \hspace{0.2in} \text{ and } \hspace{0.2in} \left.\Gamma_{n-1}^{\prime\prime}\right\vert_{k_n} = 0.</script> <p>Equivalently,</p> <script type="math/tex; mode=display">k_0 - 2c_{0,0} + c_{0,1} = 0</script> <p>and</p> <script type="math/tex; mode=display">c_{n-1,0} - 2c_{n-1,1} + k_n = 0.</script> <p>Eliminating $c_{i,1}$ from all these equations gives a system of $n$ equations for $\lbrace c_{i,0} : i \in 0,\ldots,n-1 \rbrace$:</p> <script type="math/tex; mode=display">c_{i-1,0} + 4 c_{i,0} + c_{i+1,0} = 2(2k_{i} + k_{i+1}) \text{ for } i \in \lbrace 1,\ldots,n-2 \rbrace,</script> <script type="math/tex; mode=display">2c_{0,0} + c_{1,0} = k_0 + 2k_1,</script> <script type="math/tex; mode=display">2c_{n-2,0} + 7c_{n-1,0} = 8 k_{n-1} + k_n</script> <p>Writing these equations in matrix form:</p> <script type="math/tex; mode=display">% <![CDATA[ \begin{bmatrix} 2 & 1 & 0 & 0 & 0 & \dots & 0 \\ 1 & 4 & 1 & 0 & 0 & \dots & 0 \\ 0 & 1 & 4 & 1 & 0 & \dots & 0 \\ \vdots & \ddots & \ddots & \ddots & \ddots & \ddots & \vdots \\ 0 & \dots & 0 & 1 & 4 & 1 & 0 \\ 0 & \dots & 0 & 0 & 1 & 4 & 1 \\ 0 & \dots & 0 & 0 & 0 & 2 & 7 \end{bmatrix} \begin{bmatrix} c_{0,0} \\ c_{1,0} \\ c_{2,0} \\ \vdots \\ c_{n-3,0} \\ c_{n-2,0} \\ c_{n-1,0} \end{bmatrix} = \begin{bmatrix} k_0 + 2k_1 \\ 2(2k_{1} + k_{2}) \\ 2(2k_{2} + k_{3}) \\ \vdots \\ 2(2k_{n-3} + k_{n-2}) \\ 2(2k_{n-2} + k_{n-1}) \\ 8k_{n-1} + k_{n} \end{bmatrix} %]]></script> <p>This tridiagonal system can be solved in linear time using <a href="http://en.wikipedia.org/wiki/Tridiagonal_matrix_algorithm">Thomas’ Algorithm</a>, which in this case is guaranteed to be stable since the tridiagonal matrix is diagonally dominant. Once all $c_{i,0}$ are calculated, the remaining control points $\lbrace c_{i,1} : i \in 0,\ldots,n-1 \rbrace$ are given by the following formulae:</p> <script type="math/tex; mode=display">c_{i,1} = 2k_{i+1} - c_{i+1,0} \text{ for } i \in \lbrace 0,\ldots,n-2 \rbrace,</script> <script type="math/tex; mode=display">c_{n-1,1} = \frac{1}{2}\left[ k_n + c_{n-1,0} \right].</script> <h1 id="implementation">Implementation</h1> <p>The following Android/Java code uses Thomas’ Algorithm to compute appropriate control points and accomplish our original goal:</p> <blockquote> <p>given a sequence of $n$ points in the cartesian plane, calculate a smooth <code class="highlighter-rouge">Path</code> passing through all points in order.</p> </blockquote> <p>Note that the code was written with readability, rather than performance, in mind. <code class="highlighter-rouge">EPointF</code> is a simple 2D point representation that provides some convenient <a href="http://en.wikipedia.org/wiki/Pointwise#Componentwise_operations">componentwise operations</a>; the definition is given below the main block of code.</p> <figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kn">package</span> <span class="n">com</span><span class="o">.</span><span class="na">example</span><span class="o">;</span> <span class="kn">import</span> <span class="nn">android.graphics.Path</span><span class="o">;</span> <span class="kn">import</span> <span class="nn">java.util.Collection</span><span class="o">;</span> <span class="kn">import</span> <span class="nn">java.util.List</span><span class="o">;</span> <span class="kd">public</span> <span class="kd">class</span> <span class="nc">PolyBezierPathUtil</span> <span class="o">{</span> <span class="cm">/** * Computes a Poly-Bezier curve passing through a given list of knots. * The curve will be twice-differentiable everywhere and satisfy natural * boundary conditions at both ends. * * @param knots a list of knots * @return a Path representing the twice-differentiable curve * passing through all the given knots */</span> <span class="kd">public</span> <span class="n">Path</span> <span class="nf">computePathThroughKnots</span><span class="o">(</span><span class="n">List</span><span class="o">&lt;</span><span class="n">EPointF</span><span class="o">&gt;</span> <span class="n">knots</span><span class="o">)</span> <span class="o">{</span> <span class="n">throwExceptionIfInputIsInvalid</span><span class="o">(</span><span class="n">knots</span><span class="o">);</span> <span class="kd">final</span> <span class="n">Path</span> <span class="n">polyBezierPath</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Path</span><span class="o">();</span> <span class="kd">final</span> <span class="n">EPointF</span> <span class="n">firstKnot</span> <span class="o">=</span> <span class="n">knots</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="mi">0</span><span class="o">);</span> <span class="n">polyBezierPath</span><span class="o">.</span><span class="na">moveTo</span><span class="o">(</span><span class="n">firstKnot</span><span class="o">.</span><span class="na">getX</span><span class="o&ququot;>(),</span> <span class="n">firstKnot</span><span class="o">.</span><span class="na">getY</span><span class="o">());</span> <span class="cm">/* * variable representing the number of Bezier curves we will join * together */</span> <span class="kd">final</span> <span class="kt">int</span> <span class="n">n</span> <span class="o">=</span> <span class="n">knots</span><span class="o">.</span><span class="na">size</span><span class="o">()</span> <span class="o">-</span> <span class="mi">1</span><span class="o">;</span> <span class="k">if</span> <span class="o">(</span><span class="n">n</span> <span class="o">==</span> <span class="mi">1</span><span class="o">)</span> <span class="o">{</span> <span class="kd">final</span> <span class="n">EPointF</span> <span class="n">lastKnot</span> <span class="o">=</span> <span class="n">knots</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="mi">1</span><span class="o">);</span> <span class="n">polyBezierPath</span><span class="o">.</span><span class="na">lineTo</span><span class="o">(</span><span class="n">lastKnot</span><span class="o">.</span><span class="na">getX</span><span class="o">(),</span> <span class="n">lastKnot</span><span class="o">.</span><span class="na">getY</span><span class="o">());</span> <span class="o">}</span> <span class="k">else</span> <span class="o">{</span> <span class="kd">final</span> <span class="n">EPointF</span><span class="o">[]</span> <span class="n">controlPoints</span> <span class="o">=</span> <span class="n">computeControlPoints</span><span class="o">(</span><span class="n">n</span><span class="o">,</span> <span class="n">knots</span><span class="o">);</span> <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">n</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span> <span class="kd">final</span> <span class="n">EPointF</span> <span class="n">targetKnot</span> <span class="o">=</span> <span class="n">knots</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">i</span> <span class="o">+</span> <span class="mi">1</span><span class="o">);</span> <span class="n">appendCurveToPath</span><span class="o">(</span><span class="n">polyBezierPath</span><span class="o">,</span> <span class="n">controlPoints</span><span class="o">[</span><span class="n">i</span><span class="o">],</span> <span class="n">controlPoints</span><span class="o">[</span><span class="n">n</span> <span class="o">+</span> <span class="n">i</span><span class="o">],</span> <span class="n">targetKnot</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> <span class="k">return</span> <span class="n">polyBezierPath</span><span class="o">;</span> <span class="o">}</span> <span class="kd">private</span> <span class="n">EPointF</span><span class="o">[]</span> <span class="nf">computeControlPoints</span><span class="o">(</span><span class="kt">int</span> <span class="n">n</span><span class="o">,</span> <span class="n">List</span><span class="o">&lt;</span><span class="n">EPointF</span><span class="o">&gt;</span> <span class="n">knots</span><span class="o">)</span> <span class="o">{</span> <span class="kd">final</span> <span class="n">EPointF</span><span class="o">[]</span> <span class="n">result</span> <span class="o">=</span> <span class="k">new</span> <span class="n">EPointF</span><span class="o">[</span><span class="mi">2</span> <span class="o">*</span> <span class="n">n</span><span class="o">];</span> <span class="kd">final</span> <span class="n">EPointF</span><span class="o">[]</span> <span class="n">target</span> <span class="o">=</span> <span class="n">constructTargetVector</span><span class="o">(</span><span class="n">n</span><span class="o">,</span> <span class="n">knots</span><span class="o">);</span> <span class="kd">final</span> <span class="n">Float</span><span class="o">[]</span> <span class="n">lowerDiag</span> <span class="o">=</span> <span class="n">constructLowerDiagonalVector</span><span class="o">(</span><span class="n">n</span> <span class="o">-</span> <span class="mi">1</span><span class="o">);</span> <span class="kd">final</span> <span class="n">Float</span><span class="o">[]</span> <span class="n">mainDiag</span> <span class="o">=</span> <span class="n">constructMainDiagonalVector</span><span class="o">(</span><span class="n">n</span><span class="o">);</span> <span class="kd">final</span> <span class="n">Float</span><span class="o">[]</span> <span class="n">upperDiag</span> <span class="o">=</span> <span class="n">constructUpperDiagonalVector</span><span class="o">(</span><span class="n">n</span> <span class="o">-</span> <span class="mi">1</span><span class="o">);</span> <span class="kd">final</span> <span class="n">EPointF</span><span class="o">[]</span> <span class="n">newTarget</span> <span class="o">=</span> <span class="k">new</span> <span class="n">EPointF</span><span class="o">[</span><span class="n">n</span><span class="o">];</span> <span class="kd">final</span> <span class="n">Float</span><span class="o">[]</span> <span class="n">newUpperDiag</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Float</span><span class="o">[</span><span class="n">n</span> <span class="o">-</span> <span class="mi">1</span><span class="o">];</span> <span class="c1">// forward sweep for control points c_i,0:</span> <span class="n">newUpperDiag</span><span class="o">[</span><span class="mi">0</span><span class="o">]</span> <span class="o">=</span> <span class="n">upperDiag</span><span class="o">[</span><span class="mi">0</span><span class="o">]</span> <span class="o">/</span> <span class="n">mainDiag</span><span class="o">[</span><span class="mi">0</span><span class="o">];</span> <span class="n">newTarget</span><span class="o">[</span><span class="mi">0</span><span class="o">]</span> <span class="o">=</span> <span class="n">target</span><span class="o">[</span><span class="mi">0</span><span class="o">].</span><span class="na">scaleBy</span><span class="o">(</span><span class="mi">1</span> <span class="o">/</span> <span class="n">mainDiag</span><span class="o">[</span><span class="mi">0</span><span class="o">]);</span> <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">1</span><span class="o">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">n</span> <span class="o">-</span> <span class="mi">1</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span> <span class="n">newUpperDiag</span><span class="o">[</span><span class="n">i</span><span class="o">]</span> <span class="o">=</span> <span class="n">upperDiag</span><span class="o">[</span><span class="n">i</span><span class="o">]</span> <span class="o">/</span> <span class="o">(</span><span class="n">mainDiag</span><span class="o">[</span><span class="n">i</span><span class="o">]</span> <span class="o">-</span> <span class="n">lowerDiag</span><span class="o">[</span><span class="n">i</span> <span class="o">-</span> <span class="mi">1</span><span class="o">]</span> <span class="o">*</span> <span class="n">newUpperDiag</span><span class="o">[</span><span class="n">i</span> <span class="o">-</span> <span class="mi">1</span><span class="o">]);</span> <span class="o">}</span> <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">1</span><span class="o">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">n</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span> <span class="kd">final</span> <span class="kt">float</span> <span class="n">targetScale</span> <span class="o">=</span> <span class="mi">1</span> <span class="o">/</span> <span class="o">(</span><span class="n">mainDiag</span><span class="o">[</span><span class="n">i</span><span class="o">]</span> <span class="o">-</span> <span class="n">lowerDiag</span><span class="o">[</span><span class="n">i</span> <span class="o">-</span> <span class="mi">1</span><span class="o">]</span> <span class="o">*</span> <span class="n">newUpperDiag</span><span class="o">[</span><span class="n">i</span> <span class="o">-</span> <span class="mi">1</span><span class="o">]);</span> <span class="n">newTarget</span><span class="o">[</span><span class="n">i</span><span class="o">]</span> <span class="o">=</span> <span class="o">(</span><span class="n">target</span><span class="o">[</span><span class="n">i</span><span class="o">].</span><span class="na">minus</span><span class="o">(</span><span class="n">newTarget</span><span class="o">[</span><span class="n">i</span> <span class="o">-</span> <span class="mi">1</span><span class="o">].</span><span class="na">scaleBy</span><span class="o">(</span><span class="n">lowerDiag</span><span class="o">[</span><span class="n">i</span> <span class="o">-</span> <span class="mi">1</span><span class="o">]))).</span><span class="na">scaleBy</span><span class="o">(</span><span class="n">targetScale</span><span class="o">);</span> <span class="o">}</span> <span class="c1">// backward sweep for control points c_i,0:</span> <span class="n">result</span><span class="o">[</span><span class="n">n</span> <span class="o">-</span> <span class="mi">1</span><span class="o">]</span> <span class="o">=</span> <span class="n">newTarget</span><span class="o">[</span><span class="n">n</span> <span class="o">-</span> <span class="mi">1</span><span class="o">];</span> <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="n">n</span> <span class="o">-</span> <span class="mi">2</span><span class="o">;</span> <span class="n">i</span> <span class="o">&gt;=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</span><span class="o">--)</span> <span class="o">{</span> <span class="n">result</span><span class="o">[</span><span class="n">i</span><span class="o">]</span> <span class="o">=</span> <span class="n">newTarget</span><span class="o">[</span><span class="n">i</span><span class="o">].</span><span class="na">minus</span><span class="o">(</span><span class="n">newUpperDiag</span><span class="o">[</span><span class="n">i</span><span class="o">],</span> <span class="n">result</span><span class="o">[</span><span class="n">i</span> <span class="o">+</span> <span class="mi">1</span><span class="o">]);</span> <span class="o">}</span> <span class="c1">// calculate remaining control points c_i,1 directly:</span> <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">n</span> <span class="o">-</span> <span class="mi">1</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span> <span class="n">result</span><span class="o">[</span><span class="n">n</span> <span class="o">+</span> <span class="n">i</span><span class="o">]</span> <span class="o">=</span> <span class="n">knots</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">i</span> <span class="o">+</span> <span class="mi">1</span><span class="o">).</span><span class="na">scaleBy</span><span class="o">(</span><span class="mi">2</span><span class="o">).</span><span class="na">minus</span><span class="o">(</span><span class="n">result</span><span class="o">[</span><span class="n">i</span> <span class="o">+</span> <span class="mi">1</span><span class="o">]);</span> <span class="o">}</span> <span class="n">result</span><span class="o">[</span><span class="mi">2</span> <span class="o">*</span> <span class="n">n</span> <span class="o">-</span> <span class="mi">1</span><span class="o">]</span> <span class="o">=</span> <span class="n">knots</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">n</span><span class="o">).</span><span class="na">plus</span><span class="o">(</span><span class="n">result</span><span class="o">[</span><span class="n">n</span> <span class="o">-</span> <span class="mi">1</span><span class="o">]).</span><span class="na">scaleBy</span><span class="o">(</span><span class="mf">0.5f</span><span class="o">);</span> <span class="k">return</span> <span class="n">result</span><span class="o">;</span> <span class="o">}</span> <span class="kd">private</span> <span class="n">EPointF</span><span class="o">[]</span> <span class="nf">constructTargetVector</span><span class="o">(</span><span class="kt">int</span> <span class="n">n</span><span class="o">,</span> <span class="n">List</span><span class="o">&lt;</span><span class="n">EPointF</span><span class="o">&gt;</span> <span class="n">knots</span><span class="o">)</span> <span class="o">{</span> <span class="kd">final</span> <span class="n">EPointF</span><span class="o">[]</span> <span class="n">result</span> <span class="o">=</span> <span class="k">new</span> <span class="n">EPointF</span><span class="o">[</span><span class="n">n</span><span class="o">];</span> <span class="n">result</span><span class="o">[</span><span class="mi">0</span><span class="o">]</span> <span class="o">=</span> <span class="n">knots</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="mi">0</span><span class="o">).</span><span class="na">plus</span><span class="o">(</span><span class="mi">2</span><span class="o">,</span> <span class="n">knots</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="mi">1</span><span class="o">));</span> <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">1</span><span class="o">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">n</span> <span class="o">-</span> <span class="mi">1</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span> <span class="n">result</span><span class="o">[</span><span class="n">i</span><span class="o">]</span> <span class="o">=</span> <span class="o">(</span><span class="n">knots</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">i</span><span class="o">).</span><span class="na">scaleBy</span><span class="o">(</span><span class="mi">2</span><span class="o">).</span><span class="na">plus</span><span class="o">(</span><span class="n">knots</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">i</span> <span class="o">+</span> <span class="mi">1</span><span class="o">))).</span><span class="na">scaleBy</span><span class="o">(</span><span class="mi">2</span><span class="o">);</span> <span class="o">}</span> <span class="n">result</span><span class="o">[</span><span class="n">result</span><span class="o">.</span><span class="na">length</span> <span class="o">-</span> <span class="mi">1</span><span class="o">]</span> <span class="o">=</span> <span class="n">knots</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">n</span> <span class="o">-</span> <span class="mi">1</span><span class="o">).</span><span class="na">scaleBy</span><span class="o">(</span><span class="mi">8</span><span class="o">).</span><span class="na">plus</span><span class="o">(</span><span class="n">knots</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">n</span><span class="o">));</span> <span class="k">return</span> <span class="n">result</span><span class="o">;</span> <span class="o">}</span> <span class="kd">private</span> <span class="n">Float</span><span class="o">[]</span> <span class="nf">constructLowerDiagonalVector</span><span class="o">(</span><span class="kt">int</span> <span class="n">length</span><span class="o">)</span> <span class="o">{</span> <span class="kd">final</span> <span class="n">Float</span><span class="o">[]</span> <span class="n">result</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Float</span><span class="o">[</span><span class="n">length</span><span class="o">];</span> <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">result</span><span class="o">.</span><span class="na">length</span> <span class="o">-</span> <span class="mi">1</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span> <span class="n">result</span><span class="o">[</span><span class="n">i</span><span class="o">]</span> <span class="o">=</span> <span class="mi">1</span><span class="n">f</span><span class="o">;</span> <span class="o">}</span> <span class="n">result</span><span class="o">[</span><span class="n">result</span><span class="o">.</span><span class="na">length</span> <span class="o">-</span> <span class="mi">1</span><span class="o">]</span> <span class="o">=</span> <span class="mi">2</span><span class="n">f</span><span class="o">;</span> <span class="k">return</span> <span class="n">result</span><span class="o">;</span> <span class="o">}<lt;/span> <span class="kd">private</span> <span class="n">Float</span><span class="o">[]</span> <span class="nf">constructMainDiagonalVector</span><span class="o">(</span><span class="kt">int</span> <span class="n">n</span><span class="o">)</span> <span class="o">{</span> <span class="kd">final</span> <span class="n">Float</span><span class="o">[]</span> <span class="n">result</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Float</span><span class="o">[</span><span class="n">n</span><span class="o">];</span> <span class="n">result</span><span class="o">[</span><span class="mi">0</span><span class="o">]</span> <span class="o">=</span> <span class="mi">2</span><span class="n">f</span><span class="o">;</span> <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">1</span><span class="o">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">result</span><span class="o">.</span><span class="na">length</span> <span class="o">-</span> <span class="mi">1</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span> <span class="n">result</span><span class="o">[</span><span class="n">i</span><span class="o">]</span> <span class="o">=</span> <span class="mi">4</span><span class="n">f</span><span class="o">;</span> <span class="o">}</span> <span class="n">result</span><span class="o">[</span><span class="n">result</span><span class="o">.</span><span class="na">length</span> <span class="o">-</span> <span class="mi">1</span><span class="o">]</span> <span class="o">=</span> <span class="mi">7</span><span class="n">f</span><span class="o">;</span> <span class="k">return</span> <span class="n">result</span><span class="o">;</span> <span class="o">}</span> <span class="kd">private</span> <span class="n">Float</span><span class="o">[]</span> <span class="nf">constructUpperDiagonalVector</span><span class="o">(</span><span class="kt">int</span> <span class="n">length</span><span class="o">)</span> <span class="o">{</span> <span class="kd">final</span> <span class="n">Float</span><span class="o">[]</span> <span class="n">result</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Float</span><span class="o">[</span><span class="n">length</span><span class="o">];</span> <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">result</span><span class="o">.</span><span class="na">length</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span> <span class="n">result</span><span class="o">[</span><span class="n">i</span><span class="o">]</span> <span class="o">=</span> <span class="mi">1</span><span class="n">f</span><span class="o">;</span> <span class="o">}</span> <span class="k">return</span> <span class="n">result</span><span class="o">;</span> <span class="o">}</span> <span class="kd">private</span> <span class="kt">void</span> <span class="nf">appendCurveToPath</span><span class="o">(</span><span class="n">Path</span> <span class="n">path</span><span class="o">,</span> <span class="n">EPointF</span> <span class="n">control1</span><span class="o">,</span> <span class="n">EPointF</span> <span class="n">control2</span><span class="o">,</span> <span class="n">EPointF</span> <span class="n">targetKnot</span><span class="o">)</span> <span class="o">{</span> <span class="n">path</span><span class="o">.</span><span class="na">cubicTo</span><span class="o">(</span> <span class="n">control1</span><span class="o">.</span><span class="na">getX</span><span class="o">(),</span> <span class="n">control1</span><span class="o">.</span><span class="na">getY</span><span class="o">(),</span> <span class="n">control2</span><span class="o">.</span><span class="na">getX</span><span class="o">(),</span> <span class="n">control2</span><span class="o">.</span><span class="na">getY</span><span class="o">(),</span> <span class="n">targetKnot</span><span class="o">.</span><span class="na">getX</span><span class="o">(),</span> <span class="n">targetKnot</span><span class="o">.</span><span class="na">getY</span><span class="o">()</span> <span class="o">);</span> <span class="o">}</span> <span class="kd">private</span> <span class="kt">void</span> <span class="nf">throwExceptionIfInputIsInvalid</span><span class="o">(</span><span class="n">Collection</span><span class="o">&lt;</span><span class="n">EPointF</span><span class="o">&gt;</span> <span class="n">knots</span><span class="o">)</span> <span class="o">{</span> <span class="k">if</span> <span class="o">(</span><span class="n">knots</span><span class="o">.</span><span class="na">size</span><span class="o">()</span> <span class="o">&lt;</span> <span class="mi">2</span><span class="o">)</span> <span class="o">{</span> <span class="k">throw</span> <span class="k">new</span> <span class="nf">IllegalArgumentException</span><span class="o">(</span> <span class="s">"Collection must contain at least two knots"</span> <span class="o">);</span> <span class="o">}</span> <span class="o">}</span> <span class="o">}</span></code></pre></figure> <figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kn">package</span> <span class="n">com</span><span class="o">.</span><span class="na">example</span><span class="o">;</span> <span class="cm">/** * API inspired by the Apache Commons Math Vector2D class. */</span> <span class="kd">public</span> <span class="kd">class</span> <span class="nc">EPointF</span> <span class="o">{</span> <span class="kd">private</span> <span class="kd">final</span> <span class="kt">float</span> <span class="n">x</span><span class="o">;</span> <span class="kd">private</span> <span class="kd">final</span> <span class="kt">float</span> <span class="n">y</span><span class="o">;</span> <span class="kd">public</span> <span class="nf">EPointF</span><span class="o">(</span><span class="kd">final</span> <span class="kt">float</span> <span class="n">x</span><span class="o">,</span> <span class="kd">final</span> <span class="kt">float</span> <span class="n">y</span><span class="o">)</span> <span class="o">{</span> <span class="k">this</span><span class="o">.</span><span class="na">x</span> <span class="o">=</span> <span class="n">x</span><span class="o">;</span> <span class="k">this</span><span class="o">.</span><span class="na">y</span> <span class="o">=</span> <span class="n">y</span><span class="o">;</span> <span class="o">}</span> <span class="kd">public</span> <span class="kt">float</span> <span class="nf">getX</span><span class="o">()</span> <span class="o">{</span> <span class="k">return</span> <span class="n">x</span><span class="o">;</span> <span class="o">}</span> <span class="kd">public</span> <span class="kt">float</span> <span class="nf">getY</span><span class="o">()</span> <span class="o">{</span> <span class="k">return</span> <span class="n">y</span><span class="o">;</span> <span class="o">}</span> <span class="kd">public</span> <span class="n">EPointF</span> <span class="nf">plus</span><span class="o">(</span><span class="kt">float</span> <span class="n">factor</span><span class="o">,</span> <span class="n">EPointF</span> <span class="n">ePointF</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span> <span class="k">new</span> <span class="nf">EPointF</span><span class="o">(</span><span class="n">x</span> <span class="o">+</span> <span class="n">factor</span> <span class="o">*</span> <span class="n">ePointF</span><span class="o">.</span><span class="na">x</span><span class="o">,</span> <span class="n">y</span> <span class="o">+</span> <span class="n">factor</span> <span class="o">*</span> <span class="n">ePointF</span><span class="o">.</span><span class="na">y</span><span class="o">);</span> <span class="o">}</span> <span class="kd">public</span> <span class="n">EPointF</span> <span class="nf">plus</span><span class="o">(</span><span class="n">EPointF</span> <span class="n">ePointF</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span> <span class="nf">plus</span><span class="o">(</span><span class="mf">1.0f</span><span class="o">,</span> <span class="n">ePointF</span><span class="o">);</span> <span class="o">}</span> <span class="kd">public</span> <span class="n">EPointF</span> <span class="nf">minus</span><span class="o">(</span><span class="kt">float</span> <span class="n">factor</span><span class="o">,</span> <span class="n">EPointF</span> <span class="n">ePointF</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span> <span class="k">new</span> <span class="nf">EPointF</span><span class="o">(</span><span class="n">x</span> <span class="o">-</span> <span class="n">factor</span> <span class="o">*</span> <span class="n">ePointF</span><span class="o">.</span><span class="na">x</span><span class="o">,</span> <span class="n">y</span> <span class="o">-</span> <span class="n">factor</span> <span class="o">*</span> <span class="n">ePointF</span><span class="o">.</span><span class="na">y</span><span class="o">);</span> <span class="o">}</span> <span class="kd">public</span> <span class="n">EPointF</span> <span class="nf">minus</span><span class="o">(</span><span class="n">EPointF</span> <span class="n">ePointF</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span> <span class="nf">minus</span><span class="o">(</span><span class="mf">1.0f</span><span class="o">,</span> <span class="n">ePointF</span><span class="o">);</span> <span class="o">}</span> <span class="kd">public</span> <span class="n">EPointF</span> <span class="nf">scaleBy</span><span class="o">(</span><span class="kt">float</span> <span class="n">factor</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span> <span class="k">new</span> <span class="nf">EPointF</span><span class="o">(</span><span class="n">factor</span> <span class="o">*</span> <span class="n">x</span><span class="o">,</span> <span class="n">factor</span> <span class="o">*</span> <span class="n">y</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span></code></pre></figure> <h1 id="results">Results</h1> <p>To test this implementation, I generated random points inside the square $[0,1]\times[0,1]$ and plotted the corresponding path returned by <code class="highlighter-rouge">PolyBezierPathUtil. computePathThroughKnots(...)</code>. Here are a couple of examples with $n$ = 6 to convince you that everything works as expected:</p> <div class="image-container"> <img src="/assets/images/building-smooth-paths-using-bezier-curves-6-point-1.png" width="30%" /> <img src="/assets/images/building-smooth-paths-using-bezier-curves-6-point-2.png" width="30%" /> </div> <p>The calculations and code in this post are not particularly groundbreaking. However, both are necessary foundations for the next post in this series, in which we will use the smooth Paths calculated above to make some slick custom interpolators. Stay tuned…</p> <h1 id="further-reading">Further Reading</h1> <p>A pleasing geometrical presentation of composite Bézier curves is provided by <a href="/assets/pdfs/UCLA-Math-149-Mathematics-of-Computer-Graphics-lecture-notes.pdf">these lecture notes</a> from UCLA’s Math 149: Mathematics of Computer Graphics course.</p> <p>For an interesting application of Bézier curves, see the following technical articles on Square’s blog: <a href="https://corner.squareup.com/2010/07/smooth-signatures.html">Smooth Signatures</a> and <a href="https://corner.squareup.com/2012/07/smoother-signatures.html">Smoother Signatures</a>. I just noticed that the latter post references the UCLA lecture notes I linked above - great minds, etc. Given that written letters often contain sharp corners, I would be interested to know whether Square’s algorithms could generate even better signatures if they were to switch back from cubic interpolation to linear interpolation near high-curvature regions. Perhaps something I will investigate in the future!</p> <div class="footnotes"> <ol> <li id="fn:1"> <p>Excepting the degenerate case in which the $n$ &gt; 2 provided points are colinear. <a href="#fnref:1" class="reversefootnote">&#8617;</a></p> </li> <li id="fn:2"> <p>The general (parametric) form of a cubic Bézier curve can be found in <a href="http://en.wikipedia.org/wiki/B%C3%A9zier_curve#Cubic_B.C3.A9zier_curves">the Wikipedia entry on Bézier Curves</a>. <a href="#fnref:2" class="reversefootnote">&#8617;</a></p> </li> </ol> </div>Stuart KentLast post, we built a super-simple Path-based interpolator using straight line segments. To produce smoother interpolators, without corners - typically preferred for animating motion - we’ll need correspondingly smooth generating Paths. Our primary goal in this post, then, will be: given a sequence of $n$ points in the cartesian plane, calculate a smooth Path passing through all points in order. ```

I'm not very familiar with feed syntax; what am I doing wrong here? Thanks in advance for any help you are able to offer.

pathawks commented 7 years ago

Results:

The other warnings are things that I'm not sure we can do much about, like the content containing <iframe> tags and such.

stkent commented 7 years ago

Ah, yes, the iframe stuff is definitely something I need to address on my side. Thanks for picking this up and investigating :)

pathawks commented 7 years ago
  • link should not contain HTML: &#58;

This one doesn't make a lot of sense to me; might be a problem with the validator. It is complaining about an entity number, and suggests using an entity number instead.

The second one seems like more of a problem, and will require a bit more digging. I will look into it when I get a chance :+1:


Edit: Here are the results of the feed validator from the generated feed

pathawks commented 7 years ago

Let's be clear, it appears the feed does pass validation. The validator has recommendations, but still calls the generated feed valid Atom. :+1:

stkent commented 7 years ago

Ah, thanks for clarifying! Will update the title.

pathawks commented 7 years ago

I would encourage you to take our feed template and see if you can modify it to suit your use case. If you can, I would love to review a pull request for this :+1:

stkent commented 7 years ago

I'll likely stick with my existing manual feed for now, so I probably won't circle back and try fixing. Feel free to either close the issue, or leave it open for someone else to explore. Thanks!