Android: Enable alpha/beta testing APK releases for your app on the Google Play Store

A great feature I finally got to play with is the alpha/beta testing channel for selected users. Users wishing to take part of your beta must be part of either a Google Group or Google+ Community of your picking. You can set up a new group or community for each app if you wish.

The general idea is you add the group or community into a "list of testers" and it'll give participants of those groups special access to test builds.

Setting up your special access list

Firstly, set up a Group or Community. I chose to use a Google Group in this example (I may update instructions for G+ Community later). To create a Google Group or Google+ Community, log into Google Groups, click on "Create Group" and follow the instructions.

image

Once created, go to Settings > Permissions > Basic permissions to select who can join.

Once you're done setting up the privacy and access to the group, copy the email/URL from said group.

Setting up your app

In the Play Store Dashboard:

  • For newly created apps, ensure the sections "APK", "Store Listing" and "Pricing & Distribution" don't have any marks against them by filling in the correct information, otherwise you can't publish. You can upload the APK last, it's just easier that way.
  • Under the APK tab, click on "Beta testing". Upload a new APK there. You can't publish without an APK in either production, beta or alpha.

 

image

The APK section has multiple tabs for different stages of testing

image

 

  • Change the app mode to "published". Changes will annoyingly take a few hours for the app to appear on the Play Store. You don't need to have a production APK ready, it'll publish fine with only alpha/beta but your app won't show up on searches.
image

 

  • A new heading under the "Beta testing" tab should have appeared called "Current APK". You should have the option to "Manage list of testers".

 

image

 

  • Click on "Manage list of testers".
  • Under "Add Google Groups or Google+ Communities", enter in the email address of your Google Group or Google+ Community.
  • Click add.
  • You should now see "Share the following link with your testers". Copy that link and save it somewhere.
  • When your app is finally visible on the Play Store (to you), share a message on the group/community board containing that testing link as either a topic on the group board or as a G+ post.
  • The user should click on the given link, giving them the option to opt into the beta.
  • Once they opt in, they can download the APK from the play store.

 

Screenshot_2013-07-26-11-43-51

Other than that, the update and beta testing procedure doesn't look much different to the standard process of installing from the Play Store. Happy testing!

XBMC: Scraping anime show information correctly

If there's one thing I found troublesome about XBMC, it's the poor support for anime shows out of box. I get that anime are done a little differently and that there's a separate scraper for anime information, but even that is a tad out of date.

The majority of the shows I had weren't scraped. Either due to different naming conventions or they just weren't matched at all.

Filenames

First of all, unlike TV shows, anime is treated like one big season. Your episodes should follow the naming convention of "Show Name - ep001.mkv". The important bit is "ep123" which lets XBMC know the episodic order.

Specifying source for metadata

In comes "AniDB.net Scraper Mods for Anime TV shows and Movies" by scudlee, which is a well maintained scraper primarily for anime. There are also a bunch of improvements to help with matching the titles to the right show.

The naming of this mod could have been a bit better, but it gets the job done well.

If your show still doesn't appear, you can tell it to scrape from a specific URL on aniDB.net by creating a file called "tvshow.nfo" in the show folder and pasting a link to the information.

For example Gintama (season 1), Gintama` (season 2) and Gintama Enchousen (season 3) are an absolute bitch to get right. Have no fear, just put the episodes into the right folders and place "tvshow.nfo" in each one.

For each file, paste in the direct link to each show accordingly and it'll scan right into XBMC fine. You may have to remove the show first in order for it to re-scan.

So in "Gintama\tvshow.nfo" (Season 1):

http://anidb.net/perl-bin/animedb.pl?show=anime&aid=3468

For "Gintama`\tvshow.nfo" (Season 2):

http://anidb.net/perl-bin/animedb.pl?show=anime&aid=8126

And lastly, "Gintama` Enchousen\tvshow.nfo" (Season 3) I have:

http://anidb.net/perl-bin/animedb.pl?show=anime&aid=9427

If you want to customise the Season 3 name a little, you can do this.

<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<tvshow>
<title>Gintama` Rush</title>
</tvshow>
http://anidb.net/perl-bin/animedb.pl?show=anime&aid=9427

This way you can customise the name but specify the URL to fetch information from.

This is the only way to truly enjoy Gintama and the pure Jet Armstrong Cannon insanity that comes with it!

127309_v0_600x JustawayneoCRY3Ql 158066_v0_600x  tumblr_m52m2lX0171rv62t0o1_500 

Source

A new coat of paint for Twig's Tech Tips!

The good old "classic" layout from blogger.com has certainly served me well since my first post on the 25th of June, 2008. Over the past 5 years, I eventually wanted more space for content as my posts and code snippets became lengthier.

After searching around for a nice free theme, I stumbled upon Ultra Theme from bloggerthemes.net. Applying a few tweaks and updates of my own to the theme, I finally think it's ready!

The main changes are:

  • Obviously the colour has changed
  • Wider body column
  • Wider right column
  • Updated blocks on RHS column
  • Moved donation blurb to RHS column & semi-fixed it
  • Ads actually fit in the given space now
  • H4 headings are now actually different to "bolded" text
  • Cleaned up some markup so it's quicker to load

I'll probably make more tweaks as I go along, but for now you can compare them side by side below.

screenshot_old screenshot_new

ps. LOL wow, still works on IE6 (mostly)

Android: How to create a background service

It is often handy to keep a service running in the background to manage certain tasks. They can be started from an Activity or a broadcast receiver. Managing the service lifecycle can be tricky unless you follow some key points, as I learned the hard way when creating Air Waves.

First off, services can either be;

  • a) Bound by the activity lifecycle (created and destroyed along with activity). Normally services will be destroyed once they have no more work to do and no remaining activities are binded to them.
  • b) Running along side with activity and only stops when it's done (or told to)

I'll show you how to start and manage the latter, a service that's running independently from an any binded Activities.

Defining

First you have to add some definition about the service in the AndroidManifest.xml file. Under application, simply add:

<service android:name=".ContentCheckService"></service>

That's it for the manifest!

Servicing

The service itself is a rather simple class.

public class AirWavesService extends Service {
// LocalBinder, mBinder and onBind() allow other Activities to bind to this service.
public class LocalBinder extends Binder {
public AirWavesService getService() {
return AirWavesService.this;
}
}

private final LocalBinder mBinder = new LocalBinder();

@Override
public IBinder onBind(Intent intent) {
return mBinder;
}


// Variables
protected Handler handler;
protected Toast mToast;


@Override
public void onCreate() {
super.onCreate();

Log.i("Service", "onCreate");

// Initialise UI elements
handler = new Handler();
mToast = Toast.makeText(this, "", Toast.LENGTH_SHORT);

// ...
}


@Override
public void onDestroy() {
Log.w("Service", "onDestroy");
// ...

// Clean up UI
mToast = null;

super.onDestroy();
}


@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i("Service", "onStartCommand");
return android.app.Service.START_STICKY;
}


/**
* Example function.
* @throws AirWavesException
*/
public void doSomethingOnService() throws AirWavesException {
if (!isWiFiEnabled()) {
throw new AirWavesException("No WiFi connection available.");
}

handler.post(new Runnable() {
@Override
public void run() {
mToast.setText("do something");
mToast.show();
}
});
Log.i("Service", "doSomethingOnService() called");
}
}

The value Service.START_STICKY returned by onStartCommand() controls the Service lifecycle. Basically it means that the service will be resurrected if it's killed by Android.

The documentation will explain this far better than I can.

Key functions

Shown above is the important parts of it. The main points you'll need are:

  • onCreate() and onDestroy(): Use this to initialise and clean up your variables, much like in Activity.
  • onStartCommand(): If you were writing a service which is bound to the activity lifecycle, then this is the fun one where you write your service logic. Since ours isn't, then we can write our functions anywhere, like in doSomethingOnService().

 

Note: if you're starting the service from a broadcast receiver then onStartCommand() will be called often, whereas onCreate() won't. You'll need to keep track of this yourself.

Starting the service

Now there's no point having a fancy service if you can't use it. To start it up, you'll need some simple yet (hopefully) familiar code:

Intent i = new Intent(context, ContentCheckService.class);
context.startService(i);

This is the key point you have to remember. If a service is started by startService(), then it will stay alive until you manually terminate it.

Connecting to the service

Once it's up and running, you can connect to the service and use it like a normal object.

The term you're looking for here is "binding" to a service. Services often provide functions which are needed by an Activity, and in order to access those functions easily you need to provide some "binding glue" between the two Activity and Service classes.

vJ17R
These two have been binded.

Within the Service class, notice the definition for LocalBinder. That allows your Activity to connect to the service via a ServiceConnection.

Here's a "trimmed down" version of the Activity class (I'm not kidding!)

public class MainActivity extends Activity {
protected AirWavesService service;
protected AirWavesServiceConnection serviceConnection;


@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

Log.i("Activity", "onCreate");
// ...
}

@Override
protected void onResume() {
super.onResume();
Log.i("Activity", "onResume");
connectToService();
}

@Override
protected void onPause() {
super.onPause();

if (serviceConnection != null) {
unbindService(serviceConnection);
serviceConnection = null;
}
}


// Helper function for connecting to AirWavesService.
private void connectToService() {
// Calling startService() first prevents it from being killed on unbind()
startService(new Intent(this, AirWavesService.class));

// Now connect to it
serviceConnection = new AirWavesServiceConnection();

boolean result = bindService(
new Intent(this, AirWavesService.class),
serviceConnection,
BIND_AUTO_CREATE
);

if (!result) {
throw new RuntimeException("Unable to bind with service.");
}
}


protected class AirWavesServiceConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName className, IBinder binder) {
Log.i("Activity", "onServiceConnected AirWavesService");
service = ((AirWavesService.LocalBinder) binder).getService();
}

@Override
public void onServiceDisconnected(ComponentName className) {
Log.e("Activity", "onServiceDisconnected AirWavesService");
service = null;
}
}


protected void callServiceFunction() throws AirWavesException {
service.doSomethingOnService();
}


@Override
protected void onDestroy() {
Log.e("Activity", "onDestroy");

// If we no longer need it, kill the service
if (!G.isListening && !G.isSpeaking) {
stopService(new Intent(this, AirWavesService.class));
}

super.onDestroy();
}
}

Taking a closer look

484047_10151308073271923_1041064310_n

Alright working our way from top to bottom, you'll see "service" and "serviceConnection" declared at the top. They're important as they bind your Activity to the Service.

Nothing interesting is happening at onCreate().

However, just below is onResume() and onPause() which control the life of the binding connection.

Whenever your Activity is destroyed, the bind is no longer valid and needs to be undone. Rule of thumb is if the number of bindService() and unbindService() calls don't match up, you'll get debug error logs:

Activity has leaked ServiceConnection <X> that was originally bound here. android.app.ServiceConnectionLeaked

With connectToService(), the startService() call will kick-start it into a persistent service. By the time we call bindService(), we can be sure that the service is already up and running, ready for connection.

AirWavesServiceConnection is simply an implementation of ServiceConnection so we can get a handle to the service. In onServiceConnected(), we save the reference to the service and during onServiceDisconnected() we delete that reference.

This isn't really necessary, but callServiceFunction() shows you how to call functions from the service. Pretty easy eh?

And of course, onDestroy() stops the service using stopService() when we no longer need it.

And there you have it, a long life service on Android!

Just wait until you start reading into stuff like foreground services and wake-locks in order!

Sources

A mix of information from my previous post "Android: Detect when internet connectivity is connected" and documentation from the Cling library.

Big thanks to Mark Murphy the CommonsWare guy for hanging around StackOverflow and sharing so much of his knowledge.

Android: Detect when internet connectivity is connected

Intents are often used to trigger the start of an activity, but you can also set up BroadcastReceivers to listen to global intents which are somewhat like global events.

Examples of such events can be user connecting/disconnecting the headphone or, as the title of this blog, detect when an internet connection is established.

In this example, I start a service when the internet connection is connected. This service does the heavy lifting of logic since broadcast receivers are meant to be very quick, otherwise you'll get system lockups.

Also, services are handy because users would definitely find it annoying if you started a full-screen Activity every time they connected to the internet.

In your manifest file, under the application element add in:

<receiver android:name=".InternetConnectionReceiver" >
<intent-filter>
<action android:name="android.net.wifi.STATE_CHANGE" />
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
</intent-filter>
</receiver>

This will trigger the InternetConnectionReceiver class whenever these intents are detected.

Now for your broadcast receiver.

public class InternetConnectionReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// Make sure it's an event we're listening for ...
if (!intent.getAction().equals(WifiManager.NETWORK_STATE_CHANGED_ACTION) &&
!intent.getAction().equals(ConnectivityManager.CONNECTIVITY_ACTION) &&
!intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED))
{
return;
}

ConnectivityManager cm = ((ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE));

if (cm == null) {
return;
}

// Now to check if we're actually connected
if (cm.getActiveNetworkInfo() != null && cm.getActiveNetworkInfo().isConnected()) {
// Start the service to do our thing
Intent pushIntent = new Intent(context, ContentCheckService.class);
context.startService(pushIntent);

Toast.makeText(context, "Start service", Toast.LENGTH_SHORT).show();
}
}
}

In this case it starts a service called ContentCheckService. You can write whatever code you want in there, but I've added an example of a Toast notification so you can easily check if your broadcast receiver is working properly.

Keep the onReceive() call short! According to the BroadcastReiver docs, you only get 10 seconds before it's killed. This is because it runs on the main thread. Palm it off to a service thread as soon as possible!

More on writing services here.

z0MAy
Now that's what I call service!

Sources

Varnish: Clearing your ESI cache

After setting up Varnish ESI caching, we can clear the cache when required. The example below written in Python shows you how to purge the ESI fragments.

In the VCL file, you'll have to add this somewhere at the top of the file. I put it under the backend declarations.

# Allow PURGE requests from the following web servers
acl purge_acl {
"yourhost.com.au";
"anotherserver.com.au";
}

And under "sub vcl_recv", add:

# Allow PURGE requests from our web servers
if (req.request == "PURGE") {
if (!client.ip ~ purge_acl) {
error 405 "Not allowed";
}

return(lookup);
}

Lastly, add in:

sub vcl_hit {
# Clear the cache if a PURGE has been requested
if (req.request == "PURGE") {
set obj.ttl = 0s;
error 200 "Purged.";
}
}

Now you can send PURGE requests to Varnish. When varnish detects a purge command, it'll clear the ESI cache for the given fragment.

For the following code, you'll have to send the full URL to the site's URL. It can definitely be improved, but this should be enough for you to get started.

from urlparse import urlparse
from httplib import HTTPConnection

def esi_purge_url(url):
"""
Clears the request for the given URL.
Uses a HTTP PURGE to perform this action.
The URL is run through urlparse and must point to the
varnish instance, not the varnishadm

@param url: Complete with http://domain.com/absolute/path/
"""
url = urlparse(url)
connection = HTTPConnection(url.hostname, url.port or 80)

path_bits = []
path_bits.append(url.path or '/')

if url.query:
path_bits.append('?')
path_bits.append(url.query)

connection.request('PURGE', ''.join(path_bits), '', {'Host': url.hostname})

response = connection.getresponse()

if response.status != 200:
print 'Purge failed with status: %s' % response.status

return response

Varnish also allows some sort of regex/wildcard purging, but I haven't implemented this yet. If you need mass purging, this post point you in the right direction.

Varnish: Setting up Varnish as your ESI cache (with a Django backend example)

Varnish is a load balancer, an extra layer of server software which sits in place of your web server (Apache, nginx, etc) listening to port 80 (or whatever you configure) and waits for HTTP requests. It then forwards the request to the server which isn't overloaded.

After the response is returned by your web server, Varnish will detect any ESI fragments which need replacing and cache the fragment. Varnish will not request the fragment from the server again until the content expires.

The data fragments are returned from Varnish's memory cache, which is pretty damn quick. This saves your server a whole heap of computational and database load.

This setup has an additional benefit of making it easy to clear the cache of a specific ESI fragment.

Setting up Varnish

You configure Varnish by editing the file at /etc/varnish/default.vcl. The VCL script file tells it how to behave when certain information comes through.

Since Varnish is running on port 80 as your primary point of contact, you'll have to tell it:

  • where your web servers are
  • clear any varying HTTP headers and cookie information when a URL contains "esi" (so caching works for every request)
  • check response for a custom header called "X-ESI-max-age" or "esi-enabled" and enable ESI parsing
  • "X-ESI-max-age" is removed before the request is returned to the user
# Server/port where your HTTP server runs
backend default {
.host = "127.0.0.1";
.port = "82";
}

sub vcl_recv {
# Assuming that all your cacheable fragments/views contain the pattern /esi/ in the URL.
# You can add other patterns too, but for the sake of this tutorial we'll just use this.
if (req.url ~ "/esi/") {
# Remove variances so it caches for every request
unset req.http.Vary;
unset req.http.cookie;
}
}

sub vcl_fetch {
if (beresp.http.Pragma ~ "nocache") {
return(pass);
}

# This tells Varnish that we want to "varnish-cache" this page
# Check for our custom header
if (beresp.http.X-ESI-max-age == "1") {
# Remove custom header
unset beresp.http.X-ESI-max-age;
unset beresp.http.set-cookie;
esi;
}

# This tells Varnish that we want ESI processing on this page
if (beresp.http.esi-enabled == "1") {
esi;
}
}

To test Varnish, run it and type "start". If there are any errors, check your indenting or syntax. Now that's Varnish all set up.

Coding!

Now for the fun part, tweaking your webpages! Add in some settings to "settings.py" so it's easier to configure.

VARNISH_USE_ESI = True # Quick kill-switch
VARNISH_SERVER = "localhost" # Your HTTP server
VARNISH_PORT = 80 # The port of your HTTP server

I've added a module called "varnish" to store the following code. This helper function goes into "varnish/utils.py"

from django.utils.cache import patch_cache_control

def esi(response, use_esi = False, max_age = 0):
"""
This is a helper function which sets the HTTP response headers
required to enable ESI caching and allow for configurable cache lifetimes.
"""
# Use ESI so template fragments are parsed
if use_esi:
response['esi-enabled'] = "1"

# Specify cache time on the ESI views
if max_age > 0:
response['X-IDG-ESI-max-age'] = "1"
patch_cache_control(response, max_age = max_age)

return response

As you can see, it conditionally sets the custom headers which the VCL script is expecting.

A Python decorator simply wraps around a function and can modify the input/output of the given function. The following code is for a decorator varnish() in "varnish/decorators.py", which basically makes it easier to use esi().

def varnish(use_esi = False, max_age = 0):
"""
This decorator calls the esi() function to modify
the response headers from a view.
"""
def wrap(func):
def wrapper(*args, **kwargs):
response = func(*args, **kwargs)
esi(response, use_esi = use_esi, max_age = max_age)
return response

return wrapper

return wrap

In order for Varnish to know that we want a cacheable code fragment view, we need to add into the HTML:

<esi:include src="/path/to/your/esi/cached/view/" />

It's important that we have "/esi/" in the URL pattern as we've configured that pattern in the VCL script. Varnish will attempt to fill it in automatically from cache, or fetch the include URL from your server if necessary.

The following code is for a Django template tag in "varnish/templatetags/varnish.py" which I use to quickly write "esi:include" tags when ESI is enabled, or output the fragment content directly into the template when ESI is disabled.

You can find ContextNode here.

from django import template
from django.conf import settings
from django.template import TemplateSyntaxError, resolve_variable
from django.core import urlresolvers

from twig.utils import ContextNode

register = template.Library()

@register.tag
def esi_cache(parser, tokens):
"""
Usage: Output a HTML fragment, either cache request
to Varnish ESI or full HTML output.
{% url esi-view-name object.id as esi_url %}
{% esi_cache esi_url %}
"""
bits = tokens.split_contents()

if len(bits) != 2:
raise TemplateSyntaxError('%s expects 2 arguments' % bits[0])

def wrap(context):
url = resolve_variable(bits[1], context)

if settings.VARNISH_USE_ESI:
esi_url = "http://%s:%s%s" % (settings.VARNISH_SERVER, settings.VARNISH_PORT, url)
return '<esi:include src="%s"/>' % esi_url

else:
# If we're not using ESI, we can just plug in the view output directly
esi_url = url

# Otherwise call the view and return the data
# @see http://djangosnippets.org/snippets/1568/
resolver = urlresolvers.RegexURLResolver(r'^/', settings.ROOT_URLCONF)
view, args, kwargs = resolver.resolve(esi_url)

if callable(view):
return view(context['request'], *args, **kwargs).content
else:
# This gives a nicer error email in case it ever happens
raise TemplateSyntaxError('Error retrieving "%s"' % esi_url)

return ContextNode(wrap)

Putting it all together

For example you have a URL pattern named "esi-product-summary" in your urls.py file.

urlpatterns = patterns('',
url(r'^product/esi/summary/(?P<id>\d+)/$', 'products.views.esi_summary', name = 'esi-product-summary'),
)

To use ESI fragments in the template:

<div class="reviews">
{% for product in products %}
{% url esi-product-summary product.id as esi_url %}
{% esi_cache esi_url %}
{% endfor %}
</div>

The built-in tag {% url %} generates the URL and stores it into a variable called "esi_url".

If VARNISH_USE_ESI is enabled, then {% esi_cache %} outputs the <esi:include src="/product/esi/summary/123"/> element into the skeleton template.

This skeleton template is then returned to Varnish, which detects the missing fragments and fills them in (if it's not already cached) by making extra HTTP requests to the server for each fragment.

When all the fragments are collected, all the parts are put together and returned to the user. Sounds lengthy but it all happens very quickly, especially when it's already cached.

Controlling cache expiry

So how long does it take to expire? You can configure that in your view. Just add a simple decorator and it'll "just work".

# This tells Varnish to cache it for 1800 seconds (30mins)
from varnish.decorators import varnish

@varnish(max_age = 1800) def esi_summary(request, id):
# The following code has no impact on the ESI stuff.
c = {
'product': Product.objects.get(id = product_id),
}
return render(request, 'products/esi/summary.html', c)

This code is a bit more verbose than it needs to be, but that's mainly due to the extra option USE_ESI.

Now your site is less likely to crumble when a thundering herd of traffic comes your way!

0uNuQ
Prepare yourselves, reddit/digg/slashdot is only a link away!

Enabling ESI on ALL views

If you're using an ESI fragment on a base template, then it may be in convenient for you to enable ESI site-wide. You can either do this through the VCL config file or using Django middleware.

Here's the middleware if you need it:

class VarnishEnableSitewideEsi():
"""
This enables ESI across ALL views on the site which are text/html.
"""
def process_response(self, request, response):
mimetype = response['Content-Type']
mimetype = mimetype.split(';')[0]

if mimetype == "text/html":
return esi(response, use_esi = True)

return response

Sources

Android: Google Maps v2 - Prevent map from resetting upon rotation

An annoying quirk with the Google Map fragment is when you rotate the activity, it'll reload and reset the map.

To prevent that, make sure you set retain instance to True.

protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

setContentView(R.layout.activity_map);

int status = GooglePlayServicesUtil.isGooglePlayServicesAvailable(getBaseContext());

if (status != ConnectionResult.SUCCESS) {
int requestCode = 10;
Dialog dialog = GooglePlayServicesUtil.getErrorDialog(status, this, requestCode);
dialog.show();
return;
}

SupportMapFragment supportMapFragment = (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map);

// Check if first run
if (savedInstanceState == null) {
// Prevent map from resetting when screen rotated
supportMapFragment.setRetainInstance(true);
}
}

And that should be the end of that!

BYrlt
Then suddenly, CAT'S HEAD out of nowhere!

Android: GoogleMaps v2 - Set location/position of map or animate/slide to a location

Much like integrating the GoogleMaps v2 library into your Android app, programmatically moving the camera around isn't as straight forward either.

The first thing you'll need is a target location. The second is a CameraPosition.

LatLng target = new LatLng(lat, lng);
CameraPosition cameraPosition = new CameraPosition.Builder();
.zoom(15)
.target(target)
.build();

This will set up a camera with zoom level 15 (mostly street level) and centered around the given target location.

At this point you can also set up the camera rotation and tilt as well with bearing() and tilt(). Try values of 90 and 30 respectively to see what they do.

Now to get a handle to your map.

SupportMapFragment supportMapFragment = (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map);
googleMap = supportMapFragment.getMap();

Finally, you can position the GoogleMap instance.

googleMap.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition));

That'll animate/slide the camera to the given position. If you want to move it without animation, then use moveCamera() instead.

ios6
Time to get busy mapping!

Sources

XBMC: How to group movies together using movie sets

Sometimes you get a movie series that aren't named in order (such as X1, X2, X3) but instead have all these whacky titles such as The Fast and the Furious, 2 Fast 2 Furious, Fast Five, Furious 6, etc.

To sort the movies in order, we'll need to cast a little NFO magic.

Beside each movie file, create an NFO file with the same name.

For example, the files:

2 Fast 2 Furious.avi
2 Fast 2 Furious.nfo
Fast Five.avi
Fast Five.nfo
Furious 6.avi
Furious 6.nfo
The Fast and the Furious.avi
The Fast and the Furious.nfo

As you can see, the second movie is first and the first movie is last.

In each NFO file, paste in:

<movie>
<title>The Fast and the Furious</title>
<sorttitle>Fast and Furious 1</sorttitle>
<set>Fast and Furious</set>
</movie>
  • The "title" is the name used to display the movie.
  • The "sorttitle" is used for sorting of movies.
  • Lastly, the "set" is the group which the movie belongs to. If you wanted to group all the Lord of the Ring movies then that's what you'll use as the set name.

If you need to specify the URL to scrape, then just add it at the end.

<movie> <title>2 Fast 2 Furious</title>
<sorttitle>Fast and Furious 2</sorttitle>
<set>Fast and Furious</set>
</movie>
http://www.themoviedb.org/movie/584-2-fast-2-furious

If the movies are already in your library, remove them by simply right clicking and selecting remove.

To bring them back into the list, go to Movies > Update to refresh your library. The movies should now appear in their new order.

image 
Any other questions? Refer them to Mona Diesel.

Sources

Android: Prevent AlertDialog from closing when positive button is pressed

This is a tad tricky as it involves code to be called at specific times. To the user there isn't any difference, but the convenience of a good user experience is often overlooked.

private void dialogDeviceName() {
// Generate the dialog content
final LinearLayout layout = new LinearLayout(this);
LayoutInflater.from(this).inflate(R.layout.dialog_device_name, layout);

// Prefill the dialog content
EditText txt = (EditText) layout.findViewById(R.id.txtDeviceName);
txt.setText(G.SETTINGS.deviceName);

// Create the dialog (without showing)
final AlertDialog d = new AlertDialog.Builder(this)
// Shows the button, but assigns no event handler. This still closes by default
.setPositiveButton("Save", null)
.setNegativeButton("Cancel", null)
// Your other options here ...
.setView(layout)
.create();

// This is handy for re-adjusting the size of the dialog when a keyboard is shown
d.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);

// MUST call show before using AlertDialog.getButton(), otherwise you'll get null returned
d.show();

// Override the button's on-click so it doesn't close by default
d.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
EditText txt = (EditText) layout.findViewById(R.id.txtDeviceName);
String newName = txt.getEditableText().toString().trim();

// This prevents the dialog from closing
if (newName.length() == 0) {
showToast("Please enter in a name for your broadcast.");
return;
}

// On success, the dialog must be closed manually
((TextView) findViewById(R.id.txtDeviceName)).setText(newName);
G.SETTINGS.deviceName = newName;
d.dismiss();
}
});
}

Now there are other solutions which only work on API level 8+, but this will compile and work on API level 3+ fine.

ylsb9gm
Stop diadogs from getting away, dead in their tracks (PS. It's sleeping)

Now the order for these key points are:

  • Create the dialog and specify that you want a positive button: setPositiveButton("Save", null)
  • Show the dialog so the buttons are created
  • Get the button and override it's onClick() handler with your own
  • Remember to dismiss the dialog on success

One of the tricky parts is AlertDialog.Builder.show() must be called prior to calling AlertDialog.getButton(). From the docs:

Returns: The button from the dialog, or null if a button does not exist.

Doesn't say anything about showing the dialog first. I guess it's just one of those undocumented quirks.

The rest of the sample code is specific to my Air Waves app, but you can read the code comments if there is any confusion.

Sources

Android: Enable noise cancellation in microphone audio recording

I'll have to admit early on, thinking in terms of audio signals and frequencies has never been a strong point for me so that would definitely attribute to the confusion.

Although Android docs are fairly verbose, once in a while you'll venture into a documentation jargon minefield.

Below is sample code which I got working with:

  • Access to the microphone stream
  • Make use of the device's noise cancellation capabilities
  • Ensure that data stream is read and stored in 16bit

This makes it possible to record speech audio clearly, with minimal background noise.

public static int RECORDER_SAMPLERATE = 8000;
public static int RECORDER_CHANNELS = AudioFormat.CHANNEL_CONFIGURATION_MONO;
public static int RECORDER_AUDIO_ENCODING = AudioFormat.ENCODING_PCM_16BIT;

private void record() {
AudioRecord audioRecorder;
int bufferSizeInBytes, bufferSizeInShorts;
int shortsRead;
short audioBuffer[];

try {
// Get the minimum buffer size required for the successful creation of an AudioRecord object.
bufferSizeInBytes = AudioRecord.getMinBufferSize(
RECORDER_SAMPLERATE,
RECORDER_CHANNELS,
RECORDER_AUDIO_ENCODING
);

bufferSizeInShorts = (bufferSizeInBytes /2);

// Initialize Audio Recorder.
audioRecorder = new AudioRecord(
MediaRecorder.AudioSource.VOICE_RECOGNITION,
RECORDER_SAMPLERATE,
RECORDER_CHANNELS,
RECORDER_AUDIO_ENCODING,
bufferSizeInBytes
);

// Start Recording.
audioBuffer = new short[bufferSizeInShorts];
audioRecorder.startRecording();
isRecording = true;

while (isRecording) {
shortsRead = audioRecorder.read(audioBuffer, 0, bufferSizeInShorts);

if (shortsRead == AudioRecord.ERROR_BAD_VALUE || shortsRead == AudioRecord.ERROR_INVALID_OPERATION) {
Log.e("record()", "Error reading from microphone.");
isRecording = false;
break;
}

// Whatever your code needs to do with the audio here...
}
}
finally {
if (audioRecorder != null) {
audioRecorder.stop();
audioRecorder.release();
}
}
}

Access to the microphone stream: Short Byte

I wanted to keep the audio stream low in bitrate, but just a word of warning, you can't trust AudioFormat.ENCODING_PCW_8BIT. If a device doesn't support 8BIT, it'll be unhappy with getMinBufferSize().

From the docs:

AudioFormat.ENCODING_PCM_8BIT: Audio data format: PCM 8 bit per sample. Not guaranteed to be supported by devices.

AudioFormat.ENCODING_PCM_16BIT: Audio data format: PCM 16 bit per sample. Guaranteed to be supported by devices.

The other thing you have to calculate is the minimum buffer size. Tricky thing is the returned value is in bytes (8bit), not shorts (16bit). This is important as our audio comes in 16bit quality.

To confirm, we can check by looking at the size of the Byte and Short data types:

Byte.SIZE can only store 8 bits
Short.SIZE can store 16 bits

So to calculate the buffer size in shorts; 16bit / 8bit = 2

Which is how this magic line of code came to be.

bufferSizeInShorts = (bufferSizeInBytes /2);

Bonus tip: Handy snippets to convert between byte[] to short[] data types.

To convert short[] to byte[]:

byte[] byteBuffer = new byte[shortsRead *2];

// Copy a smaller version of the buffer
short x[] = new short[shortsRead];

for (int i = 0; i < shortsRead; i++) {
x[i] = audioBuffer[i];
}

ByteBuffer.wrap(byteBuffer).order(ByteOrder.BIG_ENDIAN).asShortBuffer().put(x);

The reason why we need to create a new temporary short[] called "x" is because not all values in audioBuffer will be filled by by AudioRecorder. The unfilled slots will cause exceptions when ByteBuffer tries to convert it.

To convert from byte[] to short[]:

Thankfully this is much shorter.

ByteBuffer.wrap(audioBuffer).order(ByteOrder.BIG_ENDIAN).asShortBuffer().get(shortBuffer);

Ensure that data stream is read and stored in 16bit

Now you might be wondering why I spent so much time writing up about silly bytes and shorts.

Well even though you're streaming audio data at 16bit, you can still access it 8bit at a time by fetching it out as bytes rather than shorts.

Now this bit is vital. In tandem with noise cancellation, we want to also improve the perceived audio quality for the user. By using of only 8bits of the 16bit audio data we are damaging the audio source, causing the sound to become fuzzy and unclear.

Be sure to use the right AudioRecord.read() function:

Now remember what we learned about bytes and shirts from the last section?

http://25.media.tumblr.com/tumblr_mc2cqvb0va1rg1ucro1_400.png
You need short shorts!

Given that audioBuffer is a short[], this will ensure that you'll call the right read() function.

shortsRead = audioRecorder.read(audioBuffer, 0, bufferSizeInShorts);

Make use of the device's noise cancellation capabilities: Audio source

Looking briefly at the AudioRecord setup:

audioRecorder = new AudioRecord(MediaRecorder.AudioSource.VOICE_RECOGNITION, ...)

You might be wondering why I didn't just use MediaRecorder.AudioSource.MIC rather than the oddly named MediaRecorder.AudioSource.VOICE_RECOGNITION.

Well at first I did use MIC, until I found out the hard way that MIC will record EVERYTHING. Soft taps on the table or keyboard, rustling of a plastic bag, someone coughing in the background, etc.

XLyBx
You'll hear everything. Every. Single. Thing.

For my app in particular I needed to record speech WITHOUT all the distracting background fuzz. Using VOICE_RECOGNITION will pass the audio data into the phone's noise cancellation filters before handing it over to you.

So unless you're comfortable with implementing your own noise cancelling algorithm, it's much easier to just let the phone's built-in hardware handle it.

Another way to reduce noise is by using the AudioEffect NoiseSuppressor class available with Jelly Bean (Android 4.1 API level 16). I haven't looked into this very much as I still need to support Android 2.3.

The last method of noise cancellation is to use AudioManager.setParameters() which is available in API level 5+.

am = (AudioManager) activity.getSystemService(Context.AUDIO_SERVICE);
am.setParameters("noise_suppression=auto");

I'm uncertain about effective this is as there's little to no feedback on it's success status or whether manufacturers have even implemented it at all.

I have little confidence by looking at how little details there is on the AudioManager.setParameters() documentation. Even the source code for setParameters() doesn't say anything useful. If you were keen on using it, use it in conjunction with the methods above.

ZgNvg
I trust setParams() as much
as I trust this pole in a storm.

After the explanation, I believe the rest of the sample code is fairly straight forward. The method simply loops until the audio stream fails to read, much like reading from a socket or file. Lastly, remember to clean up after yourself by closing the stream and everything should be happy.

As always, feel free to donate (link below) if you found this information helpful!

Sources

If you feel like checking out the various sources of information I looked at to learn all that, feel free to check out the links below.

Vim: Quickly navigate between long wrapped lines

Moving around long lines in Vim with the arrow keys has always driven me nuts. Sometimes it's just a big chunk of text (think legalese) and I just need to get to a column in the middle of the text and it takes forever to get there.

1j7hjB5
It's unorthodox, but it'll get you there.

To quickly navigate through long lines in normal/command/viewing mode, try pressing "gk" and "gj" to go up/down a visual soft-line and ignore the line wrapping.

If that's the sort of behaviour you're after, you can map it to a key of your choice by editing ~/.vimrc by adding these lines:

" Allow arrow keys to navigate soft lines
map <Up> gk
map <Down> gj

Of course you can substitute <Up> and <Down> with any other key you feel like.

Sources

Android: Wireless ADB debugging with WiFi ADB

Since I couldn't get my Xiaomi Mi2S to connect to the ADB debugger on Windows 7 x64, I had to look at alternative ways to debug my apps.

Luckily, it looks like there's quite a few options. I went with WiFi ADB by ttxapps as it looked well polished and allowed me to disable the screen timeout when debugging.

Worth a shot if you can't get ADB working or forgot your USB cable!

XBMC: Fix for 100% CPU usage when idle in menus

This is something that's had me baffled for some time. It just didn't make sense that CPU usage was barely 9% during video playback, but when it's paused or a menu is shown the damn thing would cook the computer!

It isn't very noticeable when switching between episodes/movies/shows but if I fall asleep before a movie finishes, I'd definitely be woken up by the busy CPU fan when my HTPC tries to stop itself from roasting in the TV unit.

Eqmql
HTPC, meet heat and XBMC.

A little research quickly shows that XBMC still carries a lot of legacy code from it's XBOX days, and redrawing the interface at every given chance is just one of those things.

This means XBMC is written with "game loop" logic in mind, so they had to add some options in order to optimise the redrawing process.

Thankfully there is a fairly easy way to fix this (possible since Eden or Frodo I beleive).

  • Make sure XBMC is NOT running.
  • Open up Windows Explorer and go to "%APPDATA%\XBMC\userdata\" (or ~/.xbmc/userdata/ on Linux)
  • If "advancedsettings.xml" doesn't already exist, create a file called and open it with Notepad.
  • Under the <gui> tag, copy in <algorithmdirtyregions> and <nofliptimeout> as shown below.
<advancedsettings>
<gui>
<algorithmdirtyregions>1</algorithmdirtyregions>
<nofliptimeout>0</nofliptimeout>
</gui>
</advancedsettings>
  • Save and exit.

I've tried the default "1000" value for <nofliptimeout> as shown in the XBMC docs but it didn't seem to have any effect. Only when I set it to "0" would it reduce CPU usage.

And of course, your mileage may vary depending on your graphics card and/or drivers installed. For my Intel E6700 (Core 2 Duo) and AMD A8-5600k it worked like a charm.

Sources

Firefox: Fix Firefox 20's stupid "show all downloads" dropdown

Dear Mozilla,
Thanks for taking the initiative to try something new.
image image
Regarding your new behaviour for the downloads button
I appreciate that you guys are thinking outside the square, but please consider eating your own dog food before sending it out to the public.
But it's really annoying when I have to go through a few extra clicks to do things that used to be straight forward.
And a big thank you for making an option so I can toggle the old one back.
Yours sincerely, Twig.
72jrv8J
Much like this guy overtaking, it works.
But at the same time it shouldn't be this hard.
So, how did I do it? It's actually not that hard!
  • Go to a new tab and type in "about:config".
  • Paste in "browser.download.useToolkitUI"(as of Firefox 26 this no longer works)
  • Toggle the boolean value to "true"
  • Click on the "Download" button and it should work immediately!
image

Firefox 26

This release broke support for the old download manager. As a result, you now have to download an addon in order to revive this feature.

Sources

Building a custom HTPC with mini-ITX form factor for the living-room

People are always hawking on about how the PC is dead, but I've always found that having a specially built PC in the living room dedicated to media playback is much easier to manage.

You get so much more control compared to purchasing a pre-built media player (such as Boxee, WD TV Live, Apple TV, Android media unit, etc). You can actually upgrade software and make changes without hacking it, rather than hoping it already has options/compatibility you want and need.

Majority of it's life will be spent sitting pretty in the TV unit running XBMC as a media centre front-end. It's quite flexible, but a pain in the rear end to set up.

But of course, with every completed DIY project, you get this huge sense of accomplishment that money just can't buy.

And with every PC build, your personal preference will dictate which parts are selected. For me, I'd like my build to be:

  • quiet
  • low power consumption
  • a very small case (preferably slick and clean looking)
  • flawless 1080p playback (without the need for over-clocking)
  • cheap - under $500AUD (at time of build, early 2013)

Problems getting stock and mistakes

With all the possible hardware combinations available to you, being fussy with parts can actually bite you in the bum. What could possibly go wrong you ask? Well ...

My initial choice of cooler was bad as it wouldn't have fit, and the wait for it was even worse. I was after the Scythe Big Shuriken 2 Rev.B Low Profile because everyone recommended this thing, but the sole supplier for Australia just couldn't get any supply! Order placed in January, cancelled in April.

Just to get an idea of how small my case is, here it is next to a an average-sized CPU cooler.

IMG_5843

InWin BQ656S vs CoolerMaster Vortex Plus

Make sure stock can be purchased in your country before committing to it! Buying parts from around the world will mean more shipping costs, which adds greatly to your total cost.

Initially I decided on the LianLi PC Q12 (silver) but that wasn't available ANYWHERE in Australia, not even by order.

So my next choice was the InWin BQ656S, which was conveniently out of stock everywhere so I had to wait until Christmas was over and suppliers could order one in.

Pro Tip: When possible, don't leave your purchases too close to holiday season. Most computer stores close for public holidays and suppliers take long breaks over the Christmas break until the new year resumes. Even then, it'll still take about 2 weeks for stock to start flowing again.

I also forgot to check if the memory was low profile. The red memory sticks (G.Skill Ripjaws-X) I bought were not low profile so it wouldn't have fit with a bigger cooler.

The parts chosen, and why

The photos from this tutorial were taken over the span of 3 months as I waited for parts to arrive. I'll try to clarify each section as much as possible, but hopefully it isn't too confusing.

CPU AMD A8-5600K 3.6GHz Quad-Core APU $111.03
CPU cooler Xigmatek PRAETON LD963 (delivered) $27.37
Motherboard ASRock FM2A75M-ITX $98.38
Memory G.Skill Ares Series 8GB (2x4GB) DDR3-1866 $75.79
Storage Intel 520 Series Cherryville 120GB 2.5" SSD $124.97
Case Inwin BQ656S Mini ITX with 120W PSU $44.75
 

Total in $AUD

$482.29

Optional

IMG_5756
Case didn't arrive until a month all these parts were purchased.

Now I'll do a brief breakdown on why I chose these parts. This is usually part of tutorials which experienced builders tend to skip, but I think it would be beneficial to explain a few decisions.

CPU

AMD A8 because it's the best bang for buck processor, low power consumption and also the awesome graphics AMD built into it.

An AMD A4-5300 would suffice for 1080p video playback and sip only 65W TDP (maximum power draw) compared to it's 100W TDP brethren (A6/A8/A10).

During 1080p playback and idle however, both the A4 and A8 use about 24W~30W on the same motherboard.

I opted for the A8 since I intend on running a few Steam games (mainly Castle Crashers) on the HTPC. If you go with the A4, it's much easier to find a 65W TDP low profile aftermarket cooler.

CPU Cooler

As my parts arrived in batches, I just couldn't wait to build my computer. Like a kid who couldn't wait to unwrap his presents on Christmas Day, I quickly built this HTPC using the stock cooler and planned on reassembling it when the Xigmatek cooler arrived.

After waiting for a few months, I found the Xigmatek PRAETON LD963 and cancelled my order for the Scythe Shuriken cooler. It was the ONLY low profile cooler that:

  • would fit in the case
  • provide enough cooling for 100W processors
  • and had a PWM fan, allowing the motherboard to adjust fan speed/noise (22dBA MAX)
  • and orange looks pretty good against black :)

Due to the open-mesh case design, there is NO sound insulation provided by my case. With the stock cooler, this is very annoying as the fan was rather noisy during quiet movie scenes.

Motherboard

In terms of bang for buck, this board already delivers on benchmarks so I didn't quite need the stuff in the 85X board.

What was most important to me was HDMI output, SATA3 (6Gb/s) for SSD, front USB3 ports and gigabit Ethernet.

WiFi isn't a deal breaker as I've already got Cat5E running around the walls of the house, but 7.1CH HD audio was nice a bonus.

There's a risk though. Some people have been reported this motherboard catching on fire! This was a bit of an uncertain purchase for me, but it's worked out well over the past 5 months.

Memory

"G.Skill Ares Series 8GB (2x4GB) DDR3-1866" to make use of dual channel speeds. 8GB is more than enough for basic media playback, and sparing some for gaming if needed. The motherboard can only access it at 1866mhz without over-clocking anyway.

The ARES ram also is short/low profile, so it'll help accommodate for any low profile coolers you're putting in. The red Ripjaws-X pictured in the photos aren't low profile, so when the Ares memory finally arrived I threw the Ripjaws into another computer.

Storage

When Sandforce released the current generation of SSD controllers, there was a flaw in the firmware which caused catastrophic drive failures.

Most of the SSD manufacturers rushed it out anyway, selling products with broken firmware. Many angry customers had their drives returned.

Intel took a different approach. They took the time and money to work with Sandforce, developing their firmware in order to iron out bugs and get it working, properly.

For this, I chose to pay extra to buy some peace of mind. It's been a few months now and the drive runs pretty sweet. Definitely can't complain about the 15s cold boot on Windows 7.

Most of the stuff will be streamed from another computer anyway. For those who want to stash stuff on the HTPC, get an external. It's easier for everything, especially when you have front-panel USB ports.

Case

Now this is very much up to personal taste, and to be honest there aren't many mini-ITX cases to choose from in Australia.

I admit, this InWin case looks like crap on the official site but when it arrived, there was no regret. Overall, it was surprisingly well built and looks quite nice in person.

The fact it came with a power supply made the choice easier. I didn't have to bother looking into silent Pico PSUs and that kept the price down.

2013-01-26 13.28.13
Comparing it to my sofa cushion, just to give you an idea how small it is.

Throwing it all together

Alright, let's get busy! Time to crack open our canvas and do some prep work.

Note: Before touching any of these electronic components, remember to ground yourself to remove any excess static.

IMG_5827 IMG_5828

If needed, gently move some aside cables to make room for the motherboard.

Notice the big hole on the side in the photo above? That's where you should pop the IO shield as you can't physically do this once the motherboard is in.

This is sometimes referred to as the "back-plate", but these days it also means something else on the underside of the motherboard.

IMG_5830 

Aftermarket CPU cooler (with custom back-plate)

If you're using the stock cooler (or a simple cooler which doesn't require a custom back-plate) then skip this part as you can install the cooler much later in the build process.

------------------------------

Some after-market coolers may require their own bracket, meaning you have to remove the one that came with the motherboard and use the one provided with the cooler.

IMG_20130601_141002

Looking at the photo above, on the right is the original "clipped" state. Get a pen or something pointy to push the pin from the bottom. After pushing, it should look like the left pin so you can pull the clip off.

For the ASRock FM2A75M-ITX, it required access to the back of the motherboard. Instructions will vary depending on the clips used by the manufacturer.

IMG_20130601_140917

This is what it should look like after you've removed both clips. Now it's time to install the custom back-plate.

Some coolers require you to have access to both sides of the motherboard in order latch it on. If that's the case, you're going to have to lock in the CPU first. Jump to the CPU section ahead before continuing.

Aftermarket coolers usually come with some thermal paste. Put on a small amount (usually the size of a rice grain) in the middle of the CPU and then press the cooler onto the CPU evenly. Lock it onto the motherboard when it's in place.

Sorry for the lack of photos when it comes to installing coolers, but you really do need two hands to do this.

Oh and don't forget to connect the CPU fan to the motherboard!

Note: The rest of the guide was written with the assumption that a stock cooler was installed, so you won't see it in the photos.

------------------------------

Motherboard

The InWin case already has non-screw "standoffs" for the motherboard, so it didn't need any standoff screws.

IMG_5829

Look at how small it looks in my hand!

Time to slot in the motherboard. Use the screws provided and lock it in place. Make sure it's not going to move because everything else will sit onto it.

Memory

Take a close look at the photo. There is a groove in the memory slot that's located about 2/3 of the way. It should match the orientation of the memory.

Place the memory onto the slot evenly and push one end in first, then the other. There should be a click on each end as the latch locks automatically.

IMG_5839
If it doesn't match, you're doing it wrong!

CPU

Raise the lever to unlock the CPU socket.

IMG_5841 IMG_5842

Taking a look at the AMD APU chip, you'll notice a little arrow in the bottom right corner. Match that corner with the arrow in the CPU socket (bottom left).

Making sure it's in the right orientation, place the CPU in and push the lever down. It'll move a little but that's fine. The CPU shouldn't move once the lever is in place.

Note: If you're installing an aftermarket cooler, a reminder to go back up a few pages!

Stock CPU cooler

For AMD sockets, place the cooler onto the chip and run the metal clamp through the middle of the cooler. Press firmly to lock it into place and close the latch.

For Intel sockets, place the cooler onto the chip and push each corner into the board until it clicks. Sometimes it's a bit tricky to hear it click. I hate those clips. Rotate the head of each corner to lock it into place.

Oh and don't forget to connect the CPU fan to the motherboard!

Storage

Almost there now! Find the drive bay and lock the SSD into place. Some drive bays require screws, other use clips depending on your case.

IMG_5845
Mine just happen to be on the underside of the HTPC

Connect the power and SATA cables to the motherboard, preferably into SATA0. Just make sure you follow the "L" shape of the SATA lead and connector.

IMG_5844  IMG_5846
Spotted some Foxconn parts :)

Back and front panel connections

Now to plug things in! It tends to be stuff like USB ports, power/reset switch, HDD activity & power lights for the case.

For this step, it'll depend on the case you've chosen and the motherboard. I'm afraid there's not much I can do for you apart from telling to you read the manual and find the bits that match.

IMG_5831 IMG_5832
Pro tip: There's a little triangle in the plug
which helps you figure out which way to put it in.

Next step is gonna suck... Remember those fiddly cables that you gently moved aside? It's time to plug them in, and I'd hate to tell you but these are the worst.

I spend more time trying to get these plugged in properly than compared to the rest of the build!

IMG_5836

IMG_5833 IMG_5834

Take your time with these. Try to spot the triangle/arrow as it indicates a positive lead. Get them right now as it'll be a hassle to fix it up later.

It's good to have a torch handy as your hand is covering the motherboard, making it hard to see inscriptions.

On the bright side, now the hardest part is done.

nuR4V
Onwards to victory!

Wrapping up

Make sure there are no loose screws in the case. Someone I know had their newly-built gaming rig short-circuited by a rogue screw.

Here are some photos of the somewhat schizophrenic build. First up is the temporary build while I waited for the right cooler/memory to arrive.

IMG_5847 IMG_5848
IMG_5849 IMG_20130601_133610

Given the small space to work with, I managed to tuck some of the wires along the side of the case to keep cables neat. Unfortunately, the stock cooler didn't fit and left a huge gap with the cover.

For the HTCP's final boss form, here are pictures of the completed build with the right cooler and memory. No more gaps!

The new CPU cooler is MUCH quieter than the stock one supplied by AMD. Sweet orange colour too, couldn't be happier!

IMG_20130601_150443 IMG_20130601_150506

Time to close up the case as you prepare for your PC's maiden voyage. Time to test the power and reset buttons.

IMG_5850And IT'S ALIVE!!!

Quickly check to see if your LED connectors; power should always be on and disk activity flashing only when it's doing something.

Now you can start setting it up with an operating system, drivers and all of that magical software stuff. Once you're done, you'll have to find a nice place in the TV unit and wire it up properly.

Goals and results

Well, how did this build fare against the initial requirements?

  • Quiet?

Prior to getting into Windows, it's quite loud... But after it gets into Windows? Oh hell yes, can't hear it!

I had to use SpeedFan to automatically manage the fan speed.

  • Low power consumption?

You bet. During idle and video playback, the whole unit only sips on 24W of power!

  • A very small case (preferably slick and clean looking)

This one comes down to personal preference. It isn't my primary choice of case, but it ticks all the boxes such as USB3 and looks "alright". Mini-ITX is damn small though!

  • Flawless 1080p playback (without the need for over-clocking)

Easily. AMD did well with graphics in their APUs.

  • Cheap - under $500AUD (at time of build, early 2013)

Well, yes. The core system was $483 all up, but that's not including the optional parts. It'd be just shy of $500 with the keyboard and definitely over budget with the external hard-drive.

All up I think this was a pretty solid build. XBMC with it's pretty interface and a Wii remote definitely has the wife approval factor down packed.

I highly recommend AMD APUs for these smaller builds, mainly because they're more than enough for the task at hand and cheap!

 
Copyright © Twig's Tech Tips
Theme by BloggerThemes & TopWPThemes Sponsored by iBlogtoBlog