<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Phil Bayfield]]></title><description><![CDATA[Open source software engineer]]></description><link>https://philio.me/</link><image><url>https://philio.me/favicon.png</url><title>Phil Bayfield</title><link>https://philio.me/</link></image><generator>Ghost 2.20</generator><lastBuildDate>Sat, 28 Mar 2026 13:21:29 GMT</lastBuildDate><atom:link href="https://philio.me/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Using Android Data Binding adapters with Dagger 2]]></title><description><![CDATA[<!--kg-card-begin: markdown--><!--kg-card-begin: markdown--><p><em>Updated 22/04/19 to include Kotlin code samples and use latest version of Dagger Android.</em></p>
<p>The typical method of implementing a binding adapter using the Android <a href="https://developer.android.com/topic/libraries/data-binding/index.html">Data Binding Library</a> is to create a static method using the <code>@BindingAdapter</code> annotation, such as this commonly quoted example:</p>
<script src="https://gist.github.com/Philio/a7a9dc06d9135917975660702dbb70b7.js"></script>
<p>This has been widely</p>]]></description><link>https://philio.me/using-android-data-binding-adapters-with-dagger-2/</link><guid isPermaLink="false">5cbcfe159a425e3b7d07cf01</guid><category><![CDATA[Android]]></category><dc:creator><![CDATA[Phil Bayfield]]></dc:creator><pubDate>Tue, 03 Oct 2017 12:30:00 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><!--kg-card-begin: markdown--><p><em>Updated 22/04/19 to include Kotlin code samples and use latest version of Dagger Android.</em></p>
<p>The typical method of implementing a binding adapter using the Android <a href="https://developer.android.com/topic/libraries/data-binding/index.html">Data Binding Library</a> is to create a static method using the <code>@BindingAdapter</code> annotation, such as this commonly quoted example:</p>
<script src="https://gist.github.com/Philio/a7a9dc06d9135917975660702dbb70b7.js"></script>
<p>This has been widely documented and discussed <a href="https://www.google.com/search?q=Android+data+binding">on the Internet</a> already and fits many use cases.</p>
<p>But, what if your adapter has external dependancies, or you don't want to define it in a global scope?</p>
<p>Binding adapters don't have to be static...</p>
<h2 id="creatinganonstaticbindingadapter">Creating a non-static binding adapter</h2>
<p>This functionality of the data binding library is completely lacking from the official documentation, but there are a few examples floating around on Stack Overflow.</p>
<p>Let's create a simple adapter similar to the above example:</p>
<script src="https://gist.github.com/Philio/f54595304e2503a2d84da90828a37e8f.js"></script>
<p>Rather than creating a new Picasso instance each time, Picasso is now passed to the constructor as a dependancy. For Picasso in particular this could be useful if you want to use the builder to create a custom instance and avoid having multiple instances of Picasso.</p>
<p>When creating a binding adapter in this way, the data binding library generates the <code>DataBindingComponent</code> interface<sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup>. This interface contains a getter for each separate adapter defined:</p>
<script src="https://gist.github.com/Philio/1d79a1f1970a0b383171e1612ef7f73c.js"></script>
<p>Looking at the <code>DataBindingUtil</code> <a href="https://developer.android.com/reference/android/databinding/DataBindingUtil.html">docs</a>  we can see there are a number of methods that utilise the <code>DataBindingComponent</code>, such as <code>setDefaultComponent</code>, <code>inflate</code> and <code>setContentView</code> (more on this later).</p>
<p>One way to use the adapter is to create a concrete implementation of the <code>DataBindingComponent</code>, this could be done manually or with Dagger 2 to also utilise the graph to provide any external dependancies.</p>
<h2 id="buildingthedatabindingcomponentwithdagger">Building the DataBindingComponent with Dagger</h2>
<p>It should be possible to create either a component or a sub-component, in this example I chose to use a component as it's not really necessary to expose the entire graph to the binding adapters.</p>
<p>A component requires a scope so I created <code>@DataBinding</code> for this.</p>
<p>Here's the module which provides the <code>ImageBindingAdapter</code>:</p>
<script src="https://gist.github.com/Philio/c469ffea114cf1bed6223965999d09cf.js"></script>
<p>And here's the component:</p>
<script src="https://gist.github.com/Philio/0306af6029bff443a23d0d78dcd7371d.js"></script>
<p>In order to satisfy the dependancy for Picasso in <code>BindingModule</code>, it also has to be exposed by the <code>AppComponent</code> (this step would not be required if using a sub-component):</p>
<script src="https://gist.github.com/Philio/9430818047f366295093f357129b0584.js"></script>
<p>Dagger automatically implements all the methods from the <code>DataBindingComponent</code>. Here's a snippet of what gets generated under the hood:</p>
<script src="https://gist.github.com/Philio/77fe65a2cc20e79e796c8aa124a3245f.js"></script>
<h2 id="usingthecomponent">Using the component</h2>
<p>The easiest way to use the new component is to create it in the <code>Application</code> class, particularly if using Android injection in Dagger 2.10+ as you no longer need to expose the main component to activities. It's now a standard Dagger component so it simply needs to be built in the usual way and then passed to <code>DataBindingUtil.setDefaultComponent()</code>.</p>
<p>Here's a sample <code>Application</code>:</p>
<script src="https://gist.github.com/Philio/c9084bf4798c7a3e0a81a4dd5bc0e34d.js"></script>
<p>This of course is effectively still in the global scope, the component can also be used with a specific layout:</p>
<p>In an Activity:</p>
<script src="https://gist.github.com/Philio/b62f8df12a67280a888016292e8af37c.js"></script>
<p>In a Fragment:</p>
<script src="https://gist.github.com/Philio/408d697e7e84e9ae57cbb69b148e50f2.js"></script>
<hr class="footnotes-sep">
<section class="footnotes">
<ol class="footnotes-list">
<li id="fn1" class="footnote-item"><p><a href="https://developer.android.com/reference/android/databinding/DataBindingComponent.html">https://developer.android.com/reference/android/databinding/DataBindingComponent.html</a> <a href="#fnref1" class="footnote-backref">↩︎</a></p>
</li>
</ol>
</section>
<!--kg-card-end: markdown--><!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Setting up DKIM with Sendmail on Ubuntu 14.04]]></title><description><![CDATA[<!--kg-card-begin: markdown--><!--kg-card-begin: markdown--><h1 id="whatisdkim">What is DKIM?</h1>
<p>DKIM or DomainKeys Identified Mail is an email authentication system that can help prevent spoofing by signing outgoing mail. More information can be found on <a href="https://en.wikipedia.org/wiki/DomainKeys_Identified_Mail">Wikipedia</a> or <a href="https://www.google.com/search?q=DKIM">Google</a>.</p>
<p>DKIM uses a private and public key pair for signing, the public key is stored in a TXT record</p>]]></description><link>https://philio.me/setting-up-dkim-with-sendmail-on-ubuntu-14-04/</link><guid isPermaLink="false">5cbcfe159a425e3b7d07ceff</guid><dc:creator><![CDATA[Phil Bayfield]]></dc:creator><pubDate>Wed, 16 Mar 2016 09:33:16 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><!--kg-card-begin: markdown--><h1 id="whatisdkim">What is DKIM?</h1>
<p>DKIM or DomainKeys Identified Mail is an email authentication system that can help prevent spoofing by signing outgoing mail. More information can be found on <a href="https://en.wikipedia.org/wiki/DomainKeys_Identified_Mail">Wikipedia</a> or <a href="https://www.google.com/search?q=DKIM">Google</a>.</p>
<p>DKIM uses a private and public key pair for signing, the public key is stored in a TXT record in the DNS zone, similar to <a href="https://en.wikipedia.org/wiki/Sender_Policy_Framework">SPF</a>.</p>
<h1 id="simplesetuponubuntu1404">Simple setup on Ubuntu 14.04</h1>
<h2 id="installopendkim">Install OpenDKIM</h2>
<pre><code>sudo apt-get install opendkim opendkim-tools
</code></pre>
<h2 id="generateaprivateandpublickeypair">Generate a private and public key pair</h2>
<pre><code>opendkim-genkey -t -s default -d example.com
</code></pre>
<p>The -s switch defines the name of a selector, it's used later in both configuration on the server and the TXT record.</p>
<p>This generates 2 files, based on the name of the selector, e.g. default.private and default.txt</p>
<h2 id="addthetxtrecord">Add the TXT record</h2>
<p>The txt file created by <code>opendkim-genkey</code> uses a <code>bind</code> record format and looks something like this:</p>
<pre><code>default._domainkey	IN	TXT	( &quot;v=DKIM1; k=rsa; t=y; &quot;
  &quot;p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDbpMiQR1EMmOmCYzzuHYlXho97NnX1/nFCftJLjY2NVkGlCdxMpnnoVI3oGeM1DmzdbC9eySW+GbFnPs0FrPB5Tqod1XXYI3gwrMihIyMJfsIMAU0EPJKvJjwsELNYj4UHWBhWIjusGw0AeDQuUH0sCYOkdPkOSvM2wpZsvHkDZwIDAQAB&quot; )  ; ----- DKIM key default for example.com
</code></pre>
<p>You can extract the necessary information from here to use with most registrars or DNS services.</p>
<h3 id="recordname">Record name</h3>
<p>The record name should be <code>._domainkey</code> prefixed with the selector name:</p>
<pre><code>default._domainkey.
</code></pre>
<p>Any sub domain information should be appended, which <code>opendkim-genkey</code> doesn't do. for <code>sub.domain.example.com</code> you should use:</p>
<pre><code>default._domainkey.sub.domain
</code></pre>
<h3 id="txtvalue">TXT value</h3>
<p>The value should include a minimum of the DKIM version, key and key type:</p>
<pre><code>v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDbpMiQR1EMmOmCYzzuHYlXho97NnX1/nFCftJLjY2NVkGlCdxMpnnoVI3oGeM1DmzdbC9eySW+GbFnPs0FrPB5Tqod1XXYI3gwrMihIyMJfsIMAU0EPJKvJjwsELNYj4UHWBhWIjusGw0AeDQuUH0sCYOkdPkOSvM2wpZsvHkDZwIDAQAB
</code></pre>
<h2 id="configureopendkim">Configure OpenDKIM</h2>
<p>On Ubuntu the config files OpenDKIM uses are:</p>
<pre><code>/etc/opendkim.conf
/etc/default/opendkim
</code></pre>
<h3 id="copytheprivatekey">Copy the private key</h3>
<p>Many guides suggest copying files into <code>/etc/mail</code>, but OpenDKIM complains about permissions and won't start so it was easier to create a new folder in <code>/etc</code> and copy it there:</p>
<pre><code>sudo mkdir /etc/opendkim
sudo cp default.private /etc/opendkim
</code></pre>
<h3 id="updateetcopendkimconf">Update /etc/opendkim.conf</h3>
<p>As a minimum the domain, key path and selector are required:</p>
<pre><code>Domain                  example.com
KeyFile                 /etc/opendkim/default.private
Selector                default
</code></pre>
<h3 id="updateetcdefaultopendkim">Update /etc/default/opendkim</h3>
<p>Ubuntu uses a socket by default, but Sendmail complains that this is insecure so it's necessary to use a port instead, the following can be appended to the end of the file:</p>
<pre><code>SOCKET=&quot;inet:8891@localhost&quot; # listen on loopback on port 8891
</code></pre>
<h3 id="starttheservice">Start the service</h3>
<pre><code>sudo service opendkim start
</code></pre>
<h2 id="configuresendmail">Configure Sendmail</h2>
<h3 id="updatetheconfiguration">Update the configuration</h3>
<p>Append the following to <code>/etc/mail/sendmail.mc</code>:</p>
<pre><code>INPUT_MAIL_FILTER(`opendkim', `S=inet:8891@localhost')
</code></pre>
<p>Regenerate the Sendmail config file as root:</p>
<pre><code>sudo -i
m4 /etc/mail/sendmail.mc &gt; /etc/mail/sendmail.cf
</code></pre>
<h3 id="restarttheservice">Restart the service</h3>
<pre><code>sudo service sendmail restart
</code></pre>
<h1 id="checkingeverythingworks">Checking everything works</h1>
<h2 id="fromserverlogs">From server logs</h2>
<p>By default OpenDKIM logs to the syslog, so you can tail the log to see if signing is successful using:</p>
<pre><code>sudo tail -f /var/log/syslog | grep -i dkim
</code></pre>
<p>You should see something like this example taken from one of the web servers that host this blog:</p>
<pre><code>Mar 16 09:25:02 web2 sm-mta[7705]: u2G9P2Dp007705: Milter insert (1): header: DKIM-Signature:  v=1; a=rsa-sha256; c=simple/simple; d=web2.lon.codacity.net;\n\ts=linode; t=1458120302;\n\tbh=C8sxHQz0QeFsCiNzhPYF8u2GVHRax8cSsYISckkpuEk=;\n\th=Date:From:Subject:To:From;\n\tb=ymr4SQ67DqXBMkVPPfjTJUEWBPFpO4jix7oZXsranp6MQrzcXg8ysbwkL0+6VdcqA\n\t DrzTrz3O6SfVh9Aok6H+tGcPIb9jMGTn1ceLlAZhy18O5qmjkZOTHr2MWtKeaf1u2M\n\t FfBBOID4M9vef7FZBJaUa0j+Zg9LarLaYW518TEo=
</code></pre>
<h2 id="fromyourmailclient">From your mail client</h2>
<p>View the source of an email sent from your server and look for the <code>Authentication-Results</code> header. It should contain <code>dkim=pass</code> if everything is working. There is also a <code>DKIM-Signature</code> header which contains the signing data.</p>
<h1 id="furthersteps">Further steps</h1>
<p>This guide will get you up and running for a single domain, for multiple domains a few extra steps are necessary. I will update this post at a later date to include this.</p>
<!--kg-card-end: markdown--><!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[O2 Wifi app sends authentication and personal data over insecure connections]]></title><description><![CDATA[<!--kg-card-begin: markdown--><!--kg-card-begin: markdown--><h1 id="background">Background</h1>
<p>I recently purchased a Nexus 6P and being on O2 it's a requirement to download the <a href="https://play.google.com/store/apps/details?id=uk.co.o2.android.wifi">O2 Wifi app</a> and register it to get access to the <a href="https://tfl.gov.uk/campaign/station-wifi">London Underground wifi</a>. I downloaded the app on my new phone and entered my phone number, only to get an error message</p>]]></description><link>https://philio.me/o2-wifi-app-sends-authentication-and-personal-data-over-insecure-connections/</link><guid isPermaLink="false">5cbcfe159a425e3b7d07cefe</guid><dc:creator><![CDATA[Phil Bayfield]]></dc:creator><pubDate>Mon, 09 Nov 2015 19:46:14 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><!--kg-card-begin: markdown--><h1 id="background">Background</h1>
<p>I recently purchased a Nexus 6P and being on O2 it's a requirement to download the <a href="https://play.google.com/store/apps/details?id=uk.co.o2.android.wifi">O2 Wifi app</a> and register it to get access to the <a href="https://tfl.gov.uk/campaign/station-wifi">London Underground wifi</a>. I downloaded the app on my new phone and entered my phone number, only to get an error message saying, &quot;Please download the latest version of the O2 Wifi App from the App Store&quot;. I called O2 technical support and spoke to a somewhat confused guy who didn't really understand the problem. I got a vague &quot;Have you tried turning it off and on again&quot; type of response but he said that he would report the issue.</p>
<h1 id="charlesproxy">Charles Proxy</h1>
<p>I use <a href="http://www.charlesproxy.com/">Charles</a> a lot when building mobile apps, often to simply monitor network requests coming from an app and less often to intercept and modify requests and responses to test abnormal and hard to reproduce situations. I happened to have it setup for some testing when it occurred to me to test the O2 app to see if I can determine and even fix the problem myself with some creative use of Charles. I was surprised with what I found next...</p>
<h1 id="summaryofrequests">Summary of requests</h1>
<p><em>I reinstalled the app on my Nexus 5 to log these requests, the process for a clean install (e.g. new device, new number) is a little different. New registrations prompt for additional details, they are sent to the O2 API in a similar way.</em></p>
<h2 id="step1enteryourphonenumber">Step 1: Enter your phone number</h2>
<p><img src="https://philio.me/content/images/2015/11/enter_number.png" alt=""></p>
<h5 id="request1">Request 1</h5>
<pre><code>GET http://api.o2wifi.net.uk/v1?client_id=momac&amp;method=customer.mobile_login&amp;mac_address=f8-a9-**-**-**-**&amp;sig=aef5daa90057d5674bc56b65ac80c052
</code></pre>
<h5 id="response1">Response 1</h5>
<pre><code>{
    &quot;data&quot;: {
    &quot;name&quot;: &quot;Phil&quot;,
    &quot;device&quot;: {
        &quot;id&quot;: &quot;79******&quot;,
        &quot;last_sent&quot;: null,
        &quot;mac_address&quot;: &quot;f8-a9-**-**-**-**&quot;,
        &quot;verified&quot;: true
        }
    },
    &quot;state&quot;: &quot;loaded&quot;
}
</code></pre>
<p>OK, this one isn't so bad, but my MAC address has just been sent twice over the insecure connection.</p>
<h5 id="request2">Request 2</h5>
<pre><code>GET http://api.o2wifi.net.uk/v1?client_id=momac&amp;countrycode=183&amp;device_id=79******&amp;method=sms.send_challenge&amp;mpn=7*********&amp;sig=359902eff609a14701b2ad1d5bb88e6c
</code></pre>
<h5 id="response2">Response 2</h5>
<pre><code>{
    &quot;data&quot;: {
        &quot;sent&quot;: true,
        &quot;device_mpn&quot;: &quot;unchanged&quot;
    },
    &quot;state&quot;: &quot;saved&quot;
}
</code></pre>
<p>There goes my phone number!</p>
<h2 id="step2enterthetextconfirmationcode">Step 2: Enter the text confirmation code</h2>
<p><img src="https://philio.me/content/images/2015/11/sms_code.png" alt=""></p>
<p><img src="https://philio.me/content/images/2015/11/validate_code.png" alt=""></p>
<h5 id="request1">Request 1</h5>
<pre><code>GET http://api.o2wifi.net.uk/oauth2/token?client_id=momac&amp;grant_type=sms_challenge&amp;device_id=79******&amp;verification_code=065168&amp;scope=offline
</code></pre>
<h5 id="response1">Response 1</h5>
<pre><code>{
    &quot;token_type&quot;: &quot;Bearer&quot;,
    &quot;access_token&quot;: &quot;7524b243-****-****-****-************&quot;
}
</code></pre>
<p>This is a custom OAuth2 grant request, although it somewhat resembles a password grant (if you consider the device_id and verification_code the username and password). Nothing wrong with that as such, but again it's sent insecurely over HTTP. Also, for what it's worth, I'm sure OAuth2 spec says to use POST for these kind of requests.</p>
<h5 id="request2">Request 2</h5>
<pre><code>GET http://api.o2wifi.net.uk/v1?access_token=7524b243-****-****-****-************&amp;client_id=momac&amp;method=customer_detail.secure_get&amp;sig=1eeeb293ffe7a1a6744525b659564f3a
</code></pre>
<h5 id="response2">Response 2</h5>
<pre><code>{
    &quot;data&quot;: {
        &quot;customer_id&quot;: &quot;5*******&quot;,
        &quot;title&quot;: &quot;Mr&quot;,
        &quot;forename&quot;: &quot;Phil&quot;,
        &quot;surname&quot;: &quot;Bayfield&quot;,
        &quot;email&quot;: &quot;phil@***************&quot;,
        &quot;cust_type&quot;: &quot;&quot;,
        &quot;post_code&quot;: &quot;ME10***&quot;,
        &quot;dob&quot;: &quot;1981-**-**&quot;,
        &quot;signup_origin&quot;: &quot;momac&quot;,
        &quot;password&quot;: null,
        &quot;signup_ssid&quot;: &quot;&quot;,
        &quot;opt_in_partner&quot;: &quot;0&quot;,
        &quot;opt_in_o2&quot;: &quot;1&quot;,
        &quot;created&quot;: &quot;2015-05-07 07:55:22&quot;,
        &quot;modified&quot;: &quot;2015-05-07 07:56:59&quot;
    },
    &quot;state&quot;: &quot;loaded&quot;
}
</code></pre>
<p>There goes my personal details! Name, email, date of birth, postcode...</p>
<h5 id="request3">Request 3</h5>
<pre><code>GET http://api.o2wifi.net.uk/v1?access_token=7524b243-****-****-****-************&amp;client_id=momac&amp;method=device.secure_activate&amp;sig=8bd7973a3253e276722512a0b77d197a
</code></pre>
<h5 id="response3">Response 3</h5>
<pre><code>{
    &quot;data&quot;: [],
    &quot;state&quot;: &quot;updated&quot;
}
</code></pre>
<p>Nothing to see here, although amusingly the request methods on the last two were <code>secure_get</code> and <code>secure_activate</code>.</p>
<h2 id="step3werein">Step 3: We're in!</h2>
<p><img src="https://philio.me/content/images/2015/11/complete.png" alt=""></p>
<p>O2 seem to be using <a href="http://momac.net/">http://momac.net/</a> to measure customer engagement. I've never heard of it, but at least they are using SSL to send my MAC address this time:</p>
<pre><code>GET https://wapsbts.momac.net/pl/app/o2devid?devid=f5c948d702cdf629&amp;msisdn=447557856876&amp;devtype=Android&amp;mac=f8-**-**-**-**-**
</code></pre>
<p>If you search for a hotspot, off go my device id, MAC address and phone number, merrily along their way over HTTP:</p>
<pre><code>GET http://api.o2wifi.net.uk/v1?client_id=momac&amp;id=7*******&amp;limit=500&amp;mac_address=f8-**-**-**-**-**&amp;method=venue.find_all&amp;mpn=7*********&amp;offset=0&amp;sig=105dc752929a71d60ace8801a6f0a0b4
</code></pre>
<h1 id="conclusion">Conclusion</h1>
<p>A company the size of O2 should know better. If they are going to be transmitting your personal data, they should not be sending it insecurely in plain text. If they're going to use OAuth 2, they should stick to the guidelines and not completely ignore them.</p>
<p>If you use this app over your phone's data connection or a secure network, you probably don't have too much to worry about. Public access point, forget it!</p>
<!--kg-card-end: markdown--><!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Bootstrap Default theme released for Piwigo]]></title><description><![CDATA[<!--kg-card-begin: markdown--><!--kg-card-begin: markdown--><p>After a long time in the making and a number of public preview releases, the first official release of my <a href="https://philio.me/bootstrapdefault">Bootstrap Default</a> theme for <a href="http://piwigo.org">Piwigo</a> is available for download.</p>
<p>It can be downloaded from the themes section in the Piwigo admin area, or directly from the <a href="http://piwigo.org/ext/extension_view.php?eid=796">Piwigo website</a>.</p>
<p>Check out</p>]]></description><link>https://philio.me/bootstrap-default-theme-released-for-piwigo/</link><guid isPermaLink="false">5cbcfe159a425e3b7d07cefd</guid><dc:creator><![CDATA[Phil Bayfield]]></dc:creator><pubDate>Fri, 21 Aug 2015 19:05:55 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><!--kg-card-begin: markdown--><p>After a long time in the making and a number of public preview releases, the first official release of my <a href="https://philio.me/bootstrapdefault">Bootstrap Default</a> theme for <a href="http://piwigo.org">Piwigo</a> is available for download.</p>
<p>It can be downloaded from the themes section in the Piwigo admin area, or directly from the <a href="http://piwigo.org/ext/extension_view.php?eid=796">Piwigo website</a>.</p>
<p>Check out my own <a href="https://phil.si/">photo site</a> for a demo.</p>
<!--kg-card-end: markdown--><!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Android Data Binding with Robolectric 3]]></title><description><![CDATA[<!--kg-card-begin: markdown--><!--kg-card-begin: markdown--><p>Just over a week ago at <a href="https://events.google.com/io2015/">Google I/O</a> Google announced the new <a href="https://developer.android.com/tools/data-binding/guide.html">Data Binding</a> library.</p>
<p>If, like me, you were keen to give it a try and dropped it into an existing project with <a href="http://robolectric.org/">Robolectric</a>, upon runing your test suite you will find that every single unit test fails</p>]]></description><link>https://philio.me/android-data-binding-with-robolectric-3/</link><guid isPermaLink="false">5cbcfe159a425e3b7d07cefb</guid><category><![CDATA[Android]]></category><category><![CDATA[Robolectric]]></category><category><![CDATA[Data Binding]]></category><dc:creator><![CDATA[Phil Bayfield]]></dc:creator><pubDate>Sun, 07 Jun 2015 19:59:26 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><!--kg-card-begin: markdown--><p>Just over a week ago at <a href="https://events.google.com/io2015/">Google I/O</a> Google announced the new <a href="https://developer.android.com/tools/data-binding/guide.html">Data Binding</a> library.</p>
<p>If, like me, you were keen to give it a try and dropped it into an existing project with <a href="http://robolectric.org/">Robolectric</a>, upon runing your test suite you will find that every single unit test fails with an exception:</p>
<pre><code>java.lang.RuntimeException: build/intermediates/res/debug/values is not a directory
</code></pre>
<p>There seems to be a trivial change in the file structure of the <code>build/intermediates/res</code> folder when the Data Binding library is included - The <code>res/buildtype</code> folder becomes <code>res/merged/buildtype</code>.</p>
<p>Fortunately, there is an equally trivial fix. The <code>RobolectricGradleTestRunner</code> class has a line which defines where the resources are:</p>
<pre><code>final FileFsFile res = FileFsFile.from(BUILD_OUTPUT, &quot;res&quot;, flavor, type);
</code></pre>
<p>With a simple modification to the test runner, you can test to make sure the directory exists and fall back to the <code>merged</code> directory so that everything works nicely with the Data Binding library:</p>
<pre><code>final FileFsFile res;
if (FileFsFile.from(BUILD_OUTPUT, &quot;res&quot;, flavor, type).exists()) {
	res = FileFsFile.from(BUILD_OUTPUT, &quot;res&quot;, flavor, type);
} else {
	// Use res/merged if the output directory doesn't exist for Data Binding compatibility
    res = FileFsFile.from(BUILD_OUTPUT, &quot;res/merged&quot;, flavor, type);
}
</code></pre>
<p>I created a custom runner called <code>RobolectricDataBindingTestRunner</code> and used it in place of the <code>RobolectricGradleTestRunner</code> and everything worked great after this.</p>
<p>Full source:</p>
<pre><code>public class RobolectricDataBindingTestRunner extends RobolectricTestRunner {

    private static final String BUILD_OUTPUT = &quot;build/intermediates&quot;;

    public RobolectricDataBindingTestRunner(Class&lt;?&gt; klass) throws InitializationError {
        super(klass);
    }

    @Override
    protected AndroidManifest getAppManifest(Config config) {
        if (config.constants() == Void.class) {
            Logger.error(&quot;Field 'constants' not specified in @Config annotation&quot;);
            Logger.error(&quot;This is required when using RobolectricGradleTestRunner!&quot;);
            throw new RuntimeException(&quot;No 'constants' field in @Config annotation!&quot;);
        }

        final String type = getType(config);
        final String flavor = getFlavor(config);
        final String applicationId = getApplicationId(config);

        final FileFsFile res;
        if (FileFsFile.from(BUILD_OUTPUT, &quot;res&quot;, flavor, type).exists()) {
            res = FileFsFile.from(BUILD_OUTPUT, &quot;res&quot;, flavor, type);
        } else {
            // Use res/merged if the output directory doesn't exist for Data Binding compatibility
            res = FileFsFile.from(BUILD_OUTPUT, &quot;res/merged&quot;, flavor, type);
        }
        final FileFsFile assets = FileFsFile.from(BUILD_OUTPUT, &quot;assets&quot;, flavor, type);

        final FileFsFile manifest;
        if (FileFsFile.from(BUILD_OUTPUT, &quot;manifests&quot;).exists()) {
            manifest = FileFsFile.from(BUILD_OUTPUT, &quot;manifests&quot;, &quot;full&quot;, flavor, type, &quot;AndroidManifest.xml&quot;);
        } else {
            // Fallback to the location for library manifests
            manifest = FileFsFile.from(BUILD_OUTPUT, &quot;bundles&quot;, flavor, type, &quot;AndroidManifest.xml&quot;);
        }

        Logger.debug(&quot;Robolectric assets directory: &quot; + assets.getPath());
        Logger.debug(&quot;   Robolectric res directory: &quot; + res.getPath());
        Logger.debug(&quot;   Robolectric manifest path: &quot; + manifest.getPath());
        Logger.debug(&quot;    Robolectric package name: &quot; + applicationId);
        return new AndroidManifest(manifest, res, assets, applicationId);
    }

    private String getType(Config config) {
        try {
            return ReflectionHelpers.getStaticField(config.constants(), &quot;BUILD_TYPE&quot;);
        } catch (Throwable e) {
            return null;
        }
    }

    private String getFlavor(Config config) {
        try {
            return ReflectionHelpers.getStaticField(config.constants(), &quot;FLAVOR&quot;);
        } catch (Throwable e) {
            return null;
        }
    }

    private String getApplicationId(Config config) {
        try {
            return ReflectionHelpers.getStaticField(config.constants(), &quot;APPLICATION_ID&quot;);
        } catch (Throwable e) {
            return null;
        }
    }
}
</code></pre>
<h2 id="update">Update</h2>
<p>A similar change has been merged into the master branch of Robolectric 3, it's not incuded in the latest RC but will  make the final 3.0 release.</p>
<!--kg-card-end: markdown--><!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Fixing Google Drive (and other app) icons in OS X Yosemite]]></title><description><![CDATA[<!--kg-card-begin: markdown--><!--kg-card-begin: markdown--><p>I much prefer the look of OS X Yosemite over Mavericks and earlier versions, but unfortunately it's taking some apps a while to catch up, Google Drive is a prime example:</p>
<p><img src="https://philio.me/content/images/2015/01/Google-Drive-wrong-icon.png" alt=""></p>
<p>In some cases it might be possible to drag and drop a replacement icon into the info screen (via</p>]]></description><link>https://philio.me/fixing-google-drive-and-other-app-icons-in-os-x-yosemite/</link><guid isPermaLink="false">5cbcfe159a425e3b7d07cef8</guid><category><![CDATA[OS X]]></category><category><![CDATA[Google Drive]]></category><category><![CDATA[Yosemite]]></category><dc:creator><![CDATA[Phil Bayfield]]></dc:creator><pubDate>Mon, 12 Jan 2015 20:50:00 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><!--kg-card-begin: markdown--><p>I much prefer the look of OS X Yosemite over Mavericks and earlier versions, but unfortunately it's taking some apps a while to catch up, Google Drive is a prime example:</p>
<p><img src="https://philio.me/content/images/2015/01/Google-Drive-wrong-icon.png" alt=""></p>
<p>In some cases it might be possible to drag and drop a replacement icon into the info screen (via &quot;Get Info&quot;) but this doesn't work for Google Drive as it refreshes it's icon for the root shared folder every time the app starts.</p>
<p>Fortunately it's quite easy to change the icons over permanently and someone has even gone to the trouble of creating new icon files for a number of popular apps and <a href="https://drive.google.com/folderview?id=0B1tkH06wKzoXQ3NwWWhKMC1XWGc">shared them on Google Drive</a>.</p>
<p>OS X uses files with the extension icns for folder and app icons, you simply need to find the offending file within the app package and replace it.</p>
<h1 id="changinggoogledriveicons">Changing Google Drive icons</h1>
<ol>
<li>Go to your Applications folder and find the Google Drive app.</li>
<li>Right click and choose &quot;Show Package Contents&quot;.</li>
<li>Browse to Contents/Resources/lib/python2.7/resources/images.</li>
<li>Replace the files &quot;folder-mac.icns&quot; and &quot;sharedfolder-mac.icns&quot; with the icns file from the link above or another icon of your choice.</li>
<li>Quit and restart Google Drive.</li>
</ol>
<p>After a short delay, you should see the updated icon:</p>
<p><img src="https://philio.me/content/images/2015/01/Google-Drive-icon-fixed.png" alt=""></p>
<!--kg-card-end: markdown--><!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Offline editing app for Ghost gets some material sweetness]]></title><description><![CDATA[<!--kg-card-begin: markdown--><!--kg-card-begin: markdown--><p><img src="https://philio.me/content/images/2014/12/device-2014-12-13-164728-small.png" alt=""></p>
<p>I started work on this back in September but it stalled due to other commitments and sat on the hard drive of my Mac for a few weeks.</p>
<p>The release of Android 5.0 came and went which left my code already outdated before it even saw the light of</p>]]></description><link>https://philio.me/offline-editing-app-for-ghost-material-design/</link><guid isPermaLink="false">5cbcfe159a425e3b7d07cef7</guid><category><![CDATA[Ghost]]></category><category><![CDATA[Android]]></category><category><![CDATA[Material Design]]></category><dc:creator><![CDATA[Phil Bayfield]]></dc:creator><pubDate>Sat, 13 Dec 2014 17:36:17 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><!--kg-card-begin: markdown--><p><img src="https://philio.me/content/images/2014/12/device-2014-12-13-164728-small.png" alt=""></p>
<p>I started work on this back in September but it stalled due to other commitments and sat on the hard drive of my Mac for a few weeks.</p>
<p>The release of Android 5.0 came and went which left my code already outdated before it even saw the light of day, time to start over.</p>
<p>During my train journeys over the last couple of weeks I've made some good progress and the new version features Lollipop material styles (plus backwards compatibility of course). I'm hopeful it'll be ready to hit the Play Store in early January.</p>
<p>There is more information about this project on my <a href="https://philio.me/projects">Projects</a> page.</p>
<!--kg-card-end: markdown--><!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Styling the SearchView with AppCompat v21 (material theme)]]></title><description><![CDATA[<!--kg-card-begin: markdown--><!--kg-card-begin: markdown--><p>Earlier versions of the AppCompat-v7 library conveniently exposed a huge number of styling attributes that were hidden in the standard holo themes. A number of these attributes allowed customisation of the SearchView widget on the ActionBar.</p>
<p>I'll quickly summarise the way to style the SearchView with AppCompat v20 for completeness,</p>]]></description><link>https://philio.me/styling-the-searchview-with-appcompat-v21/</link><guid isPermaLink="false">5cbcfe159a425e3b7d07cef5</guid><category><![CDATA[Android]]></category><category><![CDATA[Material Theme]]></category><category><![CDATA[AppCompat]]></category><category><![CDATA[SearchView]]></category><dc:creator><![CDATA[Phil Bayfield]]></dc:creator><pubDate>Sun, 23 Nov 2014 22:38:53 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><!--kg-card-begin: markdown--><p>Earlier versions of the AppCompat-v7 library conveniently exposed a huge number of styling attributes that were hidden in the standard holo themes. A number of these attributes allowed customisation of the SearchView widget on the ActionBar.</p>
<p>I'll quickly summarise the way to style the SearchView with AppCompat v20 for completeness, then discuss the differences with v21.</p>
<h1 id="theoldway">The old way</h1>
<p>In v20 there are a number of attributes defined which apply to the SearchView widget:</p>
<pre><code>&lt;attr name=&quot;searchResultListItemHeight&quot; format=&quot;dimension&quot; /&gt;
&lt;attr name=&quot;searchViewAutoCompleteTextView&quot; format=&quot;reference&quot; /&gt;
&lt;attr name=&quot;searchViewCloseIcon&quot; format=&quot;reference&quot; /&gt;
&lt;attr name=&quot;searchViewEditQuery&quot; format=&quot;reference&quot; /&gt;
&lt;attr name=&quot;searchViewEditQueryBackground&quot; format=&quot;reference&quot; /&gt;
&lt;attr name=&quot;searchViewGoIcon&quot; format=&quot;reference&quot; /&gt;
&lt;attr name=&quot;searchViewSearchIcon&quot; format=&quot;reference&quot; /&gt;
&lt;attr name=&quot;searchViewTextField&quot; format=&quot;reference&quot; /&gt;
&lt;attr name=&quot;searchViewTextFieldRight&quot; format=&quot;reference&quot; /&gt;
&lt;attr name=&quot;searchViewVoiceIcon&quot; format=&quot;reference&quot; /&gt;
</code></pre>
<p>These are defined within the various AppCompat themes, including <code>Theme.AppCompat</code>, <code>Theme.AppCompat.Light</code> and <code>Theme.AppCompat.Light.DarkActionBar</code>.</p>
<p>You can customise the SearchView styles in your theme as follows:</p>
<pre><code>&lt;style name=&quot;AppTheme&quot; parent=&quot;Theme.AppCompat.Light.DarkActionBar&quot;&gt;
    &lt;item name=&quot;actionBarWidgetTheme&quot;&gt;@style/AppTheme.WidgetTheme&lt;/item&gt;
&lt;/style&gt;

&lt;style name=&quot;AppTheme.WidgetTheme&quot; parent=&quot;Theme.AppCompat.Light.DarkActionBar&quot;&gt;
    &lt;item name=&quot;searchViewSearchIcon&quot;&gt;@drawable/ic_action_search&lt;/item&gt;
    &lt;item name=&quot;searchViewCloseIcon&quot;&gt;@drawable/ic_action_close&lt;/item&gt;
&lt;/style&gt;
</code></pre>
<h1 id="thenewway">The new way</h1>
<p>In v21 the attributes have been re-organised and found their way into the <code>declare-styleable</code> section for SearchView:</p>
<pre><code>&lt;declare-styleable name=&quot;SearchView&quot;&gt;
    &lt;!-- The layout to use for the search view. --&gt;
    &lt;attr name=&quot;layout&quot; format=&quot;reference&quot; /&gt;
    &lt;!--
         The default state of the SearchView. If true, it will be iconified when not in
         use and expanded when clicked.
    --&gt;
    &lt;attr name=&quot;iconifiedByDefault&quot; format=&quot;boolean&quot; /&gt;
    &lt;!-- An optional maximum width of the SearchView. --&gt;
    &lt;attr name=&quot;android:maxWidth&quot; /&gt;
    &lt;!-- An optional query hint string to be displayed in the empty query field. --&gt;
    &lt;attr name=&quot;queryHint&quot; format=&quot;string&quot; /&gt;
    &lt;!-- The IME options to set on the query text field. --&gt;
    &lt;attr name=&quot;android:imeOptions&quot; /&gt;
    &lt;!-- The input type to set on the query text field. --&gt;
    &lt;attr name=&quot;android:inputType&quot; /&gt;
    &lt;!-- Close button icon --&gt;
    &lt;attr name=&quot;closeIcon&quot; format=&quot;reference&quot; /&gt;
    &lt;!-- Go button icon --&gt;
    &lt;attr name=&quot;goIcon&quot; format=&quot;reference&quot; /&gt;
    &lt;!-- Search icon --&gt;
    &lt;attr name=&quot;searchIcon&quot; format=&quot;reference&quot; /&gt;
    &lt;!-- Voice button icon --&gt;
    &lt;attr name=&quot;voiceIcon&quot; format=&quot;reference&quot; /&gt;
    &lt;!-- Commit icon shown in the query suggestion row --&gt;
    &lt;attr name=&quot;commitIcon&quot; format=&quot;reference&quot; /&gt;
    &lt;!-- Layout for query suggestion rows --&gt;
    &lt;attr name=&quot;suggestionRowLayout&quot; format=&quot;reference&quot; /&gt;
    &lt;!-- Background for the section containing the search query --&gt;
    &lt;attr name=&quot;queryBackground&quot; format=&quot;reference&quot; /&gt;
    &lt;!-- Background for the section containing the action (e.g. voice search) --&gt;
    &lt;attr name=&quot;submitBackground&quot; format=&quot;reference&quot; /&gt;
    &lt;attr name=&quot;android:focusable&quot; /&gt;
&lt;/declare-styleable&gt;
</code></pre>
<p>Theme has also gained a new styleable attribute specifically for the SearchView:</p>
<pre><code>&lt;!-- Style for the search query widget. --&gt;
&lt;attr name=&quot;searchViewStyle&quot; format=&quot;reference&quot; /&gt;
</code></pre>
<p>There are some new styles defined specifically for the SearchView such as <code>Widget.AppCompat.SearchView</code> and <code>Widget.AppCompat.Light.SearchView</code>.</p>
<p>So, to implement the v20 example above in v21 we would end up with something like this:</p>
<pre><code>&lt;style name=&quot;AppTheme&quot; parent=&quot;Theme.AppCompat.Light.DarkActionBar&quot;&gt;
    &lt;item name=&quot;searchViewStyle&quot;&gt;@style/AppTheme.SearchView&lt;/item&gt;
&lt;/style&gt;

&lt;style name=&quot;AppTheme.SearchView&quot; parent=&quot;Widget.AppCompat.SearchView&quot;&gt;
    &lt;item name=&quot;searchIcon&quot;&gt;@drawable/ic_action_search&lt;/item&gt;
    &lt;item name=&quot;closeIcon&quot;&gt;@drawable/ic_action_close&lt;/item&gt;
&lt;/style&gt;
</code></pre>
<h1 id="finalthoughts">Final thoughts</h1>
<p>Theming in Android has not always been the easiest thing to do and it looks like things have been greatly improved with Lollipop. The simplicity of the material theme is a huge time saver and it's good to see that this extends down into common use widgets too. The new SearchView attributes and themes are a much more logical and organised approach than we've had in the previous versions.</p>
<!--kg-card-end: markdown--><!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Moto X]]></title><description><![CDATA[<!--kg-card-begin: markdown--><!--kg-card-begin: markdown--><p><img src="https://philio.me/content/images/2014/11/Moto-X-2.jpg" alt=""></p>
<p>Finally, after a 20,000 km trip starting in China and stopping of in Hong Kong and the USA, my Moto X has arrived!</p>
<p>This is the walnut back with silver trim.</p>
<!--kg-card-end: markdown--><!--kg-card-end: markdown-->]]></description><link>https://philio.me/moto-x/</link><guid isPermaLink="false">5cbcfe159a425e3b7d07cef3</guid><category><![CDATA[Android]]></category><category><![CDATA[Moto-X]]></category><dc:creator><![CDATA[Phil Bayfield]]></dc:creator><pubDate>Thu, 20 Nov 2014 00:54:35 GMT</pubDate><media:content url="https://philio.me/content/images/2014/11/Moto-X-Front.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><!--kg-card-begin: markdown--><img src="https://philio.me/content/images/2014/11/Moto-X-Front.jpg" alt="Moto X"><p><img src="https://philio.me/content/images/2014/11/Moto-X-2.jpg" alt="Moto X"></p>
<p>Finally, after a 20,000 km trip starting in China and stopping of in Hong Kong and the USA, my Moto X has arrived!</p>
<p>This is the walnut back with silver trim.</p>
<!--kg-card-end: markdown--><!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Android PIN entry widget]]></title><description><![CDATA[A PIN entry view widget for Android with support for Android 5 Material Theme features via the AppCompat v7 support library.]]></description><link>https://philio.me/android-pin-entry-widget/</link><guid isPermaLink="false">5cbcfe159a425e3b7d07cef2</guid><category><![CDATA[Android]]></category><category><![CDATA[PIN]]></category><category><![CDATA[Widget]]></category><dc:creator><![CDATA[Phil Bayfield]]></dc:creator><pubDate>Wed, 19 Nov 2014 08:53:24 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><!--kg-card-begin: markdown--><p>In a number of Android projects I've worked on we've had a requirement for a PIN code entry screen, something which is sadly lacking from the Android SDK. I started working on this a while ago but when the requirement came up again recently it prompted me to give it a quick Android 5 revamp and publish it on <a href="https://github.com/Philio/PinEntryView">Github</a> and <a href="http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22me.philio%22%20AND%20a%3A%22pinentryview%22">Maven Central</a>.</p>
<p>Here is a demo of the current version:</p>
<img src="https://philio.me/content/images/2014/11/PinEntryView-Demo.gif" width="360" height="640">
<p>There are a number of ways that this kind of widget can be created, ranging from a complex custom editable view to a number of separate <code>EditText</code> views with a mass of event listeners. I went for something somewhere in the middle - A custom <code>ViewGroup</code> that uses an &quot;invisible&quot; <code>EditText</code> to delegate input to.</p>
<p>This is a surprisingly simple and effective way to create a view that has all the features of an <code>EditText</code> but looks completely different. Provided the <code>EditText</code> is visible and of at least 1px in size it can receive focus, then with a single <code>TextWatcher</code> the user input can be displayed however you want.</p>
<p>Details and updates to this project can be found on my <a href="https://philio.me/projects">Projects</a> page.</p>
<!--kg-card-end: markdown--><!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Updating navigation drawer icon to the material theme]]></title><description><![CDATA[<!--kg-card-begin: markdown--><!--kg-card-begin: markdown--><p>Over the last couple of weeks many of the Google Android apps have been updated to support the Lollypop material theme. One of the noticeable changes is the new spinning hamburger for apps that use a navigation drawer such as the Play Store and Google Music.</p>
<p>As I mentioned in</p>]]></description><link>https://philio.me/updating-navigation-drawer-icon-to-the-material-theme/</link><guid isPermaLink="false">5cbcfe159a425e3b7d07cef1</guid><category><![CDATA[Android]]></category><category><![CDATA[Material Theme]]></category><dc:creator><![CDATA[Phil Bayfield]]></dc:creator><pubDate>Sat, 25 Oct 2014 10:29:08 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><!--kg-card-begin: markdown--><p>Over the last couple of weeks many of the Google Android apps have been updated to support the Lollypop material theme. One of the noticeable changes is the new spinning hamburger for apps that use a navigation drawer such as the Play Store and Google Music.</p>
<p>As I mentioned in my <a href="https://philio.me/updating-android-apps-to-use-the-material-theme/">previous post</a> it's very easy to update to the new compatibility libraries which support the new material theme and the new navigation drawer style is included, again with a very simple modification.</p>
<h3 id="updateimport">Update import</h3>
<p>The new <code>ActionBarDrawerToggle</code> is found in the v7 support library, so you need to update your imports.</p>
<p>The old style:</p>
<pre><code>import android.support.v4.app.ActionBarDrawerToggle;
</code></pre>
<p>The new style:</p>
<pre><code>import android.support.v7.app.ActionBarDrawerToggle;
</code></pre>
<h3 id="updateconstructor">Update constructor</h3>
<p>The new <code>ActionBarDrawerToggle</code> also has a slightly different constructor, you no longer need to provide a drawable resource for the hamburger.</p>
<p>The old style:</p>
<pre><code>ActionBarDrawerToggle(Activity activity, DrawerLayout drawerLayout, int drawerImageRes, int openDrawerContentDescRes, int closeDrawerContentDescRes)
</code></pre>
<p>The new style:</p>
<pre><code>ActionBarDrawerToggle(Activity activity, DrawerLayout drawerLayout, int openDrawerContentDescRes, int closeDrawerContentDescRes)</code></pre>
<!--kg-card-end: markdown--><!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Updating Android apps to use the material theme]]></title><description><![CDATA[<!--kg-card-begin: markdown--><!--kg-card-begin: markdown--><p>With the release of the Android Lollypop SDK, Google have also updated the support libraries. The good news is that the AppCompat library already contains support for the new material theme so you can update your existing apps with very little effort.</p>
<p>It would probably be a good idea to</p>]]></description><link>https://philio.me/updating-android-apps-to-use-the-material-theme/</link><guid isPermaLink="false">5cbcfe159a425e3b7d07cef0</guid><category><![CDATA[Android]]></category><category><![CDATA[Material Theme]]></category><dc:creator><![CDATA[Phil Bayfield]]></dc:creator><pubDate>Sat, 25 Oct 2014 09:09:04 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><!--kg-card-begin: markdown--><p>With the release of the Android Lollypop SDK, Google have also updated the support libraries. The good news is that the AppCompat library already contains support for the new material theme so you can update your existing apps with very little effort.</p>
<p>It would probably be a good idea to read about <a href="https://developer.android.com/training/material/theme.html">using the material theme</a> and <a href="https://developer.android.com/training/material/compatibility.html">using the new support library</a> before proceeding.</p>
<h3 id="java7">Java 7+</h3>
<p>The Lollypop build tools require Java 7 upwards, just download <a href="http://www.oracle.com/technetwork/java/javase/downloads/jdk7-downloads-1880260.html">JDK 7</a> or <a href="http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html">JDK 8</a> and install it, if you haven't already done so.</p>
<p>Note: The Android Studio application plist still requires Java 1.6, if you don't want to have multiple versions of Java floating around it seems to work without issue if you edit the plist.</p>
<h3 id="updatebuildgradle">Update build.gradle</h3>
<p>Simply update all the SDK versions below 21 to 21 throughout your build.gradle, typically this will be as follows:</p>
<pre><code>android {
    compileSdkVersion 21
    buildToolsVersion &quot;21.0.2&quot;
    
    defaultConfig {
        targetSdkVersion 21
    }
}
</code></pre>
<p>Then update any support library dependencies:</p>
<pre><code>dependencies {
    compile 'com.android.support:support-v4:21.0.0'
    compile 'com.android.support:appcompat-v7:21.0.0'
}
</code></pre>
<p>Build and run and you should now have the material theme!</p>
<p>If you run into a gradle error about the Java version, you will need to point Android Studio at the correct JDK.</p>
<h3 id="implementthematherialthemeforv21">Implement the matherial theme for v21</h3>
<p>Now that you have updated the support libraries, you should proceed with implementing the v21 material theme for Lollypop devices.</p>
<!--kg-card-end: markdown--><!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[View source of any JAR in Android Studio]]></title><description><![CDATA[<!--kg-card-begin: markdown--><!--kg-card-begin: markdown--><p>Although most of the good Android libraries are open source, occasionally you get stuck with a third party JAR or two that doesn't have source available, or good documentation, or both.</p>
<p>Music streaming services in particular seem to prefer the closed source route. Many of them provide SDKs for Android</p>]]></description><link>https://philio.me/view-source-of-any-jar-in-android-studio/</link><guid isPermaLink="false">5cbcfe159a425e3b7d07ceed</guid><category><![CDATA[Android]]></category><category><![CDATA[Android Studio]]></category><dc:creator><![CDATA[Phil Bayfield]]></dc:creator><pubDate>Mon, 06 Oct 2014 12:59:22 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><!--kg-card-begin: markdown--><p>Although most of the good Android libraries are open source, occasionally you get stuck with a third party JAR or two that doesn't have source available, or good documentation, or both.</p>
<p>Music streaming services in particular seem to prefer the closed source route. Many of them provide SDKs for Android such as <a href="https://developer.spotify.com/technologies/spotify-android-sdk/">Spotify</a>, <a href="http://developers.deezer.com/sdk/android">Deezer</a> and <a href="http://www.rdio.com/developers/docs/libraries/android/">Rdio</a> but they're all in various pre-compiled formats.</p>
<p>Fortunately, there is a fantastic IDEA plugin to solve this problem called <a href="http://plugins.jetbrains.com/plugin/7100">JD-IntelliJ</a>. Android Studio appears to be fully compatible with IDEA plugins and this one works very well. It's not faultless and a small number of methods are output as &quot;described byte code&quot; but this itself isn't too tricky to follow.</p>
<p>Here is a quick example taken from Deezer's library:</p>
<p><img src="https://philio.me/content/images/2014/Oct/Screen-Shot-2014-10-06-at-02-01-20-pm.png" alt=""></p>
<!--kg-card-end: markdown--><!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[An engineering project with a difference]]></title><description><![CDATA[<!--kg-card-begin: markdown--><!--kg-card-begin: markdown--><p><img src="https://philio.me/content/images/2014/Sep/IMG_8174.JPG" alt="Custom Nukeproof Mega TR"></p>
<p>Usually most of my projects involve many long days in front of a computer screen. Actually, this one was no exception!</p>
<p>I was becoming increasing frustrated with my 6 year old <a href="http://www.konabikeworld.com/08_coilairdeluxe_w.htm">Kona Coilair</a> for both it's excessive weight (around 36 lbs) and the lack of travel on the semi-broken front</p>]]></description><link>https://philio.me/an-engineering-project-with-a-difference/</link><guid isPermaLink="false">5cbcfe159a425e3b7d07ceec</guid><dc:creator><![CDATA[Phil Bayfield]]></dc:creator><pubDate>Thu, 18 Sep 2014 14:33:33 GMT</pubDate><media:content url="https://philio.me/content/images/2014/11/IMG_8174.JPG" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><!--kg-card-begin: markdown--><img src="https://philio.me/content/images/2014/11/IMG_8174.JPG" alt="An engineering project with a difference"><p><img src="https://philio.me/content/images/2014/Sep/IMG_8174.JPG" alt="An engineering project with a difference"></p>
<p>Usually most of my projects involve many long days in front of a computer screen. Actually, this one was no exception!</p>
<p>I was becoming increasing frustrated with my 6 year old <a href="http://www.konabikeworld.com/08_coilairdeluxe_w.htm">Kona Coilair</a> for both it's excessive weight (around 36 lbs) and the lack of travel on the semi-broken front fork (parts discontinued). In fact the last time I rode it on Sunday down a short downhill in the local woods turned out to be the final nail in the coffin of my Marchozzi 55's as they lost all remaining travel and transformed into the heaviest rigid forks ever made.</p>
<p>With the current trends having moved away from 1 1/8&quot; to tapered steerers, 20mm to 15mm axles for all but downhill forks and 650b/29er rapidly gaining popularity the selection of suitable replacement forks was rather limited without having to change other parts. Hence, I decided to take advantage of the end of season clearance and build a whole new bike, from scratch... Well at least from a frame with a pre-fitted headset.</p>
<p>I spent a long time browsing websites and forums to figure out what the best parts I could buy that were lightweight and affordable (ideally on clearance) and this is what I came up with:</p>
<p>Frame: Nukeproof Mega TR 275 130mm<br>
Forks: RockShox Pike 150mm<br>
Headset: Hope<br>
Wheels: Spank Oozy 295, Hope Pro 2 Evo, Maxxis Ardent (tubeless)<br>
Bottom Bracket + Cranks: Race Face Cinch<br>
Brakes: Shimano XT, Ice-Tec 180mm front/160mm rear<br>
Drivetrain: Shimano XT, Zee mech, Saint shifter<br>
Eveything else: Nukeproof Warhead</p>
<p>Besides the headset (which requires a very expensive headset press tool) I fully assembled everything myself. This was actually fairly straight forward and requires little more than a torque wrench, set of allen key sockets, a torx 25 socket, cassette tool, a few spanners and cable cutters. I did run into a small issue with the cranks that a very helpful guy called Sam from <a href="http://www.raceface.com/">Race Face</a> helped me out with over email.</p>
<p>It weighs in at 29 lbs and is such a huge improvement over my old Kona in just about every way imaginable! So far I've had a fairly uneventful exploratory ride to a fairly tame woodland area but there will be lots more to come!</p>
<p>For some reason Garmin's embedded iframe won't load, so instead here is a <a href="http://connect.garmin.com/modern/activity/592715006">link to the maiden voyage</a>.</p>
<!--kg-card-end: markdown--><!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[M-5: Hardline water cooled gaming PC - Intro]]></title><description><![CDATA[<!--kg-card-begin: markdown--><!--kg-card-begin: markdown--><h3 id="m5">M-5</h3>
<p>The name M-5 comes from an episode of the original Star Trek series titled &quot;<a href="http://en.wikipedia.org/wiki/The_Ultimate_Computer">The Ultimate Computer</a>&quot;. Admittedly, I've never actually seen the episode, but I felt the title of the episode was quite fitting for this project. In the episode the computer actually attacks two other</p>]]></description><link>https://philio.me/m-5-hardline-water-cooled-gaming-pc/</link><guid isPermaLink="false">5cbcfe159a425e3b7d07ceeb</guid><category><![CDATA[Water cooling]]></category><category><![CDATA[M-5]]></category><category><![CDATA[Gaming]]></category><category><![CDATA[PC]]></category><category><![CDATA[Hardline]]></category><dc:creator><![CDATA[Phil Bayfield]]></dc:creator><pubDate>Tue, 26 Aug 2014 22:48:05 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><!--kg-card-begin: markdown--><h3 id="m5">M-5</h3>
<p>The name M-5 comes from an episode of the original Star Trek series titled &quot;<a href="http://en.wikipedia.org/wiki/The_Ultimate_Computer">The Ultimate Computer</a>&quot;. Admittedly, I've never actually seen the episode, but I felt the title of the episode was quite fitting for this project. In the episode the computer actually attacks two other starships and kills tonnes of people before eventually shutting itself down. As modern PC technology is yet to incorporate human <a href="http://en.wikipedia.org/wiki/Engram_(neuropsychology)">engrams</a> and it'll be installed on my desk rather than a starship, I'm not too worried about a repeat occurrence.</p>
<h3 id="newparts">New parts</h3>
<p>My previous build log is already documented on this blog <a href="http://philio.me/pc-rebuild-part-1/">here</a> and <a href="http://philio.me/pc-rebuild-part-2/">here</a> and it's only a couple of years old so my main goal was to move to a cube shaped case and move from X79 to Z97. This may sound like a downgrade for some, but I personally don't think the Intel enthusiast platform is really the best choice for gaming. The majority of the games that I play are CPU bound so 6 or 8 cores provides zero benefit, however a higher speed single core will maximise my graphics performance. Hence, I went with the quad core Devil's Canyon 4790K along with the ASUS Maximus VII Hero board.</p>
<p><img src="https://philio.me/content/images/2014/Aug/IMG_5912.JPG" alt=""></p>
<p>The choice of cube shaped cases is quite limited and after considering a few different options I happened to come across this video on YouTube of the Lian Li D600 case at Computex:</p>
<iframe width="853" height="480" src="//www.youtube.com/embed/opBTqPIsQ-w" frameborder="0" style="display:block; margin: 0px auto 40px auto;" allowfullscreen></iframe>
<p>This case is ideal for water cooling as it's possible to fit  multiple radiators, with maximum sizes of up to 420 mm in the front left, 480 mm in the back right and 240 mm in the top left. There is even room in the right hand side for an 80cm wide radiator in push pull.</p>
<p>There are 10 5 1/4&quot; bays, giving tonnes of room for expansion as well as various mountings for up to 6 3.5&quot; and 4 2.5&quot; drives - way more than I would ever consider installing.</p>
<p>I was very impressed with the build quality and accessories that Lian Li provided. The D600 is second to none in comparison with the previous cases I've owned, although the Corsair and Silverstone were not far behind. Here it is, just out of the box:</p>
<p><img src="https://philio.me/content/images/2014/Aug/IMG_5904.JPG" alt=""></p>
<p>So this covers the main changes I'm making to my PC, in the next part of the build log I'll cover the cooling components.</p>
<!--kg-card-end: markdown--><!--kg-card-end: markdown-->]]></content:encoded></item></channel></rss>