Cache Grab

I came across this today. Whip down to question 11 where is reads…

I want to use SWAP or COMPCACHE to increase the available RAM on my Android phone. Is that safe ?

The answer there states compcache is safe while swap is considered unsafe. A closer looks tells a slightly different story, however.

Compcache is a system that conserves memory by compressing a portion of it. This portion is readable by the root user at /dev/block/ramzswapN, where N is some number. This means that any application running on a device utilizing compcache could have portions of its address space readable from the above mentioned path.

I have a rooted device running custom firmware with compcache installed. So, I tried reading data from the cache after using some apps that contained sensitive information. As I expected, I found that there were pieces of that information in the cache. As with the case I explored previously, passwords and encryption don’t help here.

So, would I call compcache any safer than swap? Nope.

The Root of It

You’ve heard of Android. But, what do you know about Rooting? What the heck is that you might ask? Custom community developed ROMs are all the craze these days and in order to install them, you’ll need to perform a process called Rooting. This gives you complete, no holds barred access to your device. Sounds great, right? But, consider what would happen if your precious device were to fall into the wrong hands? Here is an example of what could go wrong.

Using the Android SDK, with your device wired in via USB, you can remote shell into it via adb shell. From here, you can poke around and find all sorts of things. Issuing ps within the shell will display output similar to the following:

USER     PID   PPID  VSIZE  RSS     WCHAN    PC         NAME
system    497   122   114564 18008 ffffffff afd0ee3c S com.android.settings
app_16    522   122   117952 13276 ffffffff afd0ee3c S com.google.android.voicesearch
root      539   2     0      0     c006a118 00000000 S tiwlan_wifi_wq
wifi      543   1     3112   752   ffffffff afd0deb4 S /system/bin/wpa_supplicant
app_10    711   122   109676 14248 ffffffff afd0ee3c S com.android.providers.calendar
app_57    764   122   0      0     ffffffff 00000000 Z droid.apps.docs
app_17    816   122   119136 23252 ffffffff afd0ee3c S com.android.vending
dhcp      875   1     936    372   c00d6198 afd0ec0c S /system/bin/dhcpcd
app_14    932   122   105392 12868 ffffffff afd0ee3c S com.android.defcontainer
app_30    962   122   105400 12272 ffffffff afd0ee3c S com.svox.pico
app_43    976   122   135648 22348 ffffffff afd0ee3c S com.google.android.apps.maps
app_42    996   122   105376 12216 ffffffff afd0ee3c S com.android.vending.updater


This is a listing of all running processes on the device. Each process has a unique identifier called a PID. With root access, we can actually dump process memory (live application data) to a file by sending the process the SIGUSR1 kill signal. The generated file is called a heap dump and can be created by issuing kill -10 pid in the shell.

You’ll know it was successful because you’ll see something similar to the below in the log:

I/dalvikvm( 1003): threadid=3: reacting to signal 10
I/dalvikvm( 1003): SIGUSR1 forcing GC and HPROF dump
I/dalvikvm( 1003): hprof: dumping VM heap to "/data/misc/heap-dump-tm1326509736-pid1003.hprof-hptemp".
I/dalvikvm( 1003): hprof: dumping heap strings to "/data/misc/heap-dump-tm1326509736-pid1003.hprof".
I/dalvikvm( 1003): hprof: heap dump completed, temp file removed
D/dalvikvm( 1003): GC_HPROF_DUMP_HEAP freed 1461 objects / 141080 bytes in 5951ms


Now that the process memory has been saved to the file system, it can be pulled off the device and processed by a memory analysis tool such as MAT

I’ve tried this with one of my devices, gathering heap dumps for a few different processes. Not too surprisingly, plenty of data I’d rather not have just anyone taking a peek at was readily available. In some cases, passwords were pointed out quite clearly.

For a legitimate software developer, this functionality can be great for debugging / troubleshooting purposes. However, for the everyday user, this is quite the opposite. A thief could swipe a given device and, if rooted, have access to a lot of live, possibly private, data. Unfortunately, password protecting your device does not prevent this. And, apps that secure data with passwords or encryption may be at risk too since the secured data could already be readable in memory.

Thankfully, Google has closed this vulnerability as of Android 2.3 by disabling heap dumps via the previously mentioned kill signal. This doesn’t mean that devices running this software are impenetrable. But, it does make this type of hack a bit harder.

So, the moral of the story is that a malicious individual will likely find a way to get the information they want, if they’re talented (some may prefer the term evil?) enough. However, if you value your privacy, you should be aware that you may be giving it away by rooting your Android devices.

SyncMyPix 0.15

The good news is, I just pushed a new version of SyncMyPix to the market. And, if you’re turkish or polish, you may notice improved name matching thanks to some work from a new contributor. The bad news is, unless you’re turkish or polish, there’s nothing to get excited about. To be honest, more came out of this release than went in.

Here’s the summary of changes:

  • Minimum supported SDK is level 8 (Froyo)
  • Removed Send Log option

I’m bumping the OS requirement up to workaround an Android SSL bug. It’s quite sad that it has come to this as a solution. But, I just don’t have the energy or motivation to code up a fix that, in my opinion, would be a sloppy workaround for an OS bug. And, I’ve removed the Send Log option because its more than served its purpose.

I know some users might be upset about my so called solution, so I’ve provided an app build on code.google.com that provides legacy support. That is, the legacy build runs on Android 1.5 and up.

I’m quite happy that users have found SyncMyPix so useful. It’s been a lot of fun developing it. Unfortunately, this will probably be the last bit of work I put into it for awhile. However, the source is available on github. And, pull requests are always welcome.

Let’s Bounce, Yo

I was told by the Internet that creating an overshoot / bounce / elastic animation using CABasicAnimation is impossible.

Perhaps my search-foo failed me. Nevertheless, I decided to whip up a quick test to see if the rumour is true. As it turns out, you can get a simple, overshoot effect using the handy animation class. See below for the guts of a UIViewController:

- (void)loadView {

	UIView* mainView = [[UIView alloc] initWithFrame:CGRectMake(0, 20, 320, 460)];
	mainView.backgroundColor = [UIColor whiteColor];
	self.view = mainView;
	[mainView release];

	// A view to play with. In my case, it's a ball
	//
	TestView* testView = [[TestView alloc] initWithFrame:CGRectMake(kInitialX, 20, 48, 48)];
	testView.backgroundColor = [UIColor clearColor];
	[self.view addSubview:testView];
	[testView release];

	// Detect dragging gestures
	//
	UIPanGestureRecognizer* recognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(moveIt:)];
	[testView addGestureRecognizer:recognizer];
	[recognizer release];

}

-(void) moveIt:(UIPanGestureRecognizer*)recognizer {

	UIView *view = recognizer.view;

	// Dragging...
	//
	if (recognizer.state == UIGestureRecognizerStateBegan || recognizer.state == UIGestureRecognizerStateChanged) {
		CGPoint translation = [recognizer translationInView:view.superview];

		// A simple method of simulating the feeling that the view is attached to an elastic
		//
		float const adjustment = translation.x * 0.75;
		NSLog(@"Adjust the view's center by %f", adjustment);

		[view setCenter:CGPointMake(view.center.x + adjustment, view.center.y)];
		[recognizer setTranslation:CGPointZero inView:view.superview];

	// Not dragging...
	//
	} else {
		[self animateBack:view];
	}
}

-(void) animateBack:(UIView*)view {

	CABasicAnimation *theAnimation;

	// Animate the x-coordinate of the view to bounce the view back to its start position
	// as if it's attached to an elastic
	//
	theAnimation=[CABasicAnimation animationWithKeyPath:@"position.x"];

	// Get the x-coordinate of the center of the view at its start position
	//
	const float startX = kInitialX + view.bounds.size.width / 2;

	theAnimation.fromValue=[NSNumber numberWithFloat:view.center.x];
	theAnimation.toValue=[NSNumber numberWithFloat:startX];

	theAnimation.duration=0.7;

	// Overshoot easing function
	//
	theAnimation.timingFunction=[CAMediaTimingFunction functionWithControlPoints:0.25f :0.1f :0.25f :1.5f];
	[[view layer] addAnimation:theAnimation forKey:@"position"];

	// Set end position
	//
	[[view layer] setValue:[NSNumber numberWithFloat:startX] forKeyPath:@"position.x"];
}

Substitute TestView for a view of your choosing. What I was aiming for with this experiment was a dragging behaviour that gives the user the feeling that the view is attached to an elastic band. I think it works pretty well.

Centered on iOS

I’ve been dabbling in Objective-C and the iOS SDK recently. Overall, Apple has done a fantastic job with the development tools.

I thought I’d share a quick tip I’ve picked up while learning to use the Quartz 2D API, part of the Core Graphics framework.

When custom drawing is required for a UI element, the typical approach is to override the drawRect method available to all UIView subclasses. From the SDK documentation:

The default implementation of this method does nothing. Subclasses that use native drawing technologies (such as Core Graphics and UIKit) to draw their view’s content should override this method and implement their drawing code there.

Sometimes when drawing lines, shapes, etc., there’s a need to shrink, grow, and move things around to achieve the desired effect. An easy way do this is by using transformation matrices. However, to make things happen smoothly, the order of operations should be considered carefully.

Let’s consider scaling operations. The Quartz 2D Programming Guide states that scaling “…changes the scale of the coordinate space by the x and y factors you specify, effectively stretching or shrinking coordinates.” Now, since all drawing begins from the origin of the coordinate system (top left corner of the screen on iOS), this means that scaling operations will effect drawing such that it’s as if whatever is being drawing is being resized by dragging the bottom right corner inward or outward.

If that doesn’t make any sense, perhaps a visual would help. Let’s consider a case where the requirement is to draw 50 rectangles of alternating colours and decreasing size.

50 Scaled Black and White Rectangles

As you can see, the rectangles always begin at the origin. If we wanted to center each of these rectangles in the view, we could take one of two approaches:

  1. Scale and translate each rectangle to the center of the view
  2. Translate the origin to the center of the view

As it turns out, the second option is much simpler. The idea is to center the origin in the view, create a rectangle path that is centered on the origin, and scale and draw each rectangle. Since a scale transformation matrix scales the x and y coordinates by the factors specified, after each transformation is applied, the effect is that each rectangle is scaled inwards from each corner towards the center. Thus, by centering the origin first, we save ourselves some work.

Result of translating the origin

And, here’s code for the drawRect method…

-(void) drawRect:(CGRect)rect {

	CGContextRef context = UIGraphicsGetCurrentContext();
	CGContextSaveGState(context);

	// find the center of the view
	const float centerX = self.bounds.size.width / 2;
	const float centerY = self.bounds.size.height / 2;

	// move the origin the the center
	CGContextTranslateCTM(context, centerX, centerY);

	// create base rectangle path
	CGMutablePathRef path = CGPathCreateMutable();
	CGPathAddRect(path, NULL, CGRectMake(-centerX,
										 -centerY,
										 self.bounds.size.width,
										 self.bounds.size.height));

	CGPathCloseSubpath(path);

	// save the current state of the context
	// this is not necessary in our scenario, but it's a good idea to save the state
	// so we can easily pop off all our scale transformations
	CGContextSaveGState(context);

	for (int i = 0; i < 50; i++) {

		if (i % 2 == 0) {
			[[UIColor blackColor] set];
		} else {
			[[UIColor whiteColor] set];
		}

		CGContextScaleCTM(context, .9, .9);
		CGContextAddPath(context, path);
		CGContextDrawPath(context, kCGPathFill);
	}

	// restore to the previous state of the context
	CGContextRestoreGState(context);

	// release memory
	CGPathRelease(path);
}

A Simple, Dynamic Thread Pool

Tonight, I whipped up something useful, so I’ve decided to share. A nice change from other nights when I’m vegging on the couch doing anything but being productive. It’s a thread pool that can start with x number of threads and size itself up to y number of threads if the number of queued tasks grows above a certain threshold. You’ll also notice that once the queue is empty, the number of active threads drops back down to the minimum. The minimum can even be zero. Many platforms provide concurrent utility classes for accomplishing this or similar behaviour. However, if you’re writing code on a platform, such as J2ME, you’ll be out of luck. So, without further ado, here it is:

//
//  DynamicThreadPool.java
//
//  Authors:
// 		Neil Loknath <neil.loknath@gmail.com>
//
//  Copyright 2010 Neil Loknath
//
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//
//  http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
//

package com.nloko.util.concurrent;

import java.util.Queue;

/**
 * A thread pool that automatically adjusts its size depending
 * on specified settings / conditions
 * @author neil
 *
 */
public class DynamicThreadPool {
	private volatile boolean running = true;
	/**
	 * Queued work
	 */
	private final Queue queue;

	/**
	 * Minimum number of threads
	 */
	private final int minThreads;
	/**
	 * Maximum number of threads
	 */
	private final int maxThreads;
	/**
	 * Threshold of queued work to exceed before starting more threads
	 */
	private final int threshold;

	/**
	 * Track threads
	 */
	private final Thread[] threads;

	/**
	 * Number of threads
	 */
	private int threadCount;

	/**
	 * Create thread pool
	 * @param q
	 */
	public DynamicThreadPool(Queue q) {
		this(q, 0, 2);
	}

	/**
	 * Create thread pool with specified min and max threads
	 * @param q
	 * @param min
	 * @param max
	 */
	public DynamicThreadPool(Queue q, int min, int max) {
		this(q, min, max, 50);
	}

	/**
	 * Create thread pool with specified min and max threads
	 * @param q
	 * @param min
	 * @param max
	 * @param threshold - queued work to exceed before starting threads up to max
	 */
	public DynamicThreadPool(Queue q, int min, int max, int threshold) {
		queue = q;
		minThreads = min;
		maxThreads = max;
		this.threshold = threshold;

		threads = new Thread[maxThreads];

		// start initial number of threads
		for (int i = 0; i < minThreads; i++) {
			threads[i] = new Worker(i);
			threads[i].start();

			System.out.println("Initializing thread number " + i);
		}

		threadCount = minThreads;
	}

	/**
	 * Get number of active threads
	 * @return
	 */
	public int activeThreads() {
		int active = 0;

		for (int i = 0; i < threads.length; i++) {
			if (threads[i] != null && threads[i].isAlive()) active++;
		}

		return active;
	}

	/**
	 * Shutdown threads and flag pool as not running
	 */
	public void shutdown() {
		System.out.println("Shutting pool down.");

		running = false;

		// empty queue and notify all waiting threads
		synchronized(queue) {
			queue.clear();
			queue.notifyAll();
		}
	}

	/**
	 * Execute an asynchronous task
	 * @param r
	 */
	public void execute(Runnable r) {
		synchronized(queue) {
			// start a thread if none running or threshold exceeded
			if (threadCount == 0 || queue.size() >= threshold) {
				if (threadCount < maxThreads) {
					threads[threadCount] = new Worker(threadCount);
					threads[threadCount].start();

					System.out.println("Thread needed. Creating number " + threadCount);
					threadCount++;
				}
			}

			queue.add(r);
			queue.notify();
		}
	}

	/**
	 * Inner class as worker thread to run queued tasks
	 * @author neil
	 *
	 */
	private class Worker extends Thread {
		/*
		 * Track position of thread in array
		 */
		private final int index;

		public Worker(int index) {
			this.index = index;
		}

		public void run() {
			while (running) {
				Runnable r = null;

				synchronized(queue) {
					if (!queue.isEmpty()) {
						r = (Runnable) queue.poll();
					}
					// queue is empty, so kill active threads over minimum
					else if (threadCount > minThreads) {
						System.out.println("Too many threads. Killing thread number " + index);

						// eliminate reference to dying thread so it can be GC'd
						threads[index] = threads[threadCount - 1];
						threads[threadCount - 1] = null;
						threadCount--;

						// notify other threads waiting so they can terminate / process work
						queue.notify();

						break;
					}
					// minimum threads active, so just wait for work
					else {
						try {
							System.out.println("Thread number " + index + " waiting for work");
							queue.wait();

							System.out.println("Thread number " + index + " awakened");
						} catch (InterruptedException e) {}
					}
				}

				if (r != null) {
					try {
						// run schedule work
						r.run();
					} catch (Throwable t) {
						// prevent thread leakage
					}
				}
			}

			System.out.println("Thread number " + index + " finished");
		}
	}
}

As you can see, there’s a lot of print statements in there for testing purposes. The testing I’ve done so far is fairly minimal. So, you’ll likely want to play around with this before putting it to work. I’m confident that it’s very close to solid, however. Also, depending on what Java platform you’ll be using this code on, you may need to swap out the dependancy on the Queue interface for something that works for you.

Blackberry: Animated Part 2

In my last post, I wrote about the lack of animation API provided in the Blackberry SDK. Sure, OS 6.0 provides some means for doing animations, and OS 5.0 added an API for screen transition animation. But, that’s not enough for developers targeting devices running earlier OS versions. According to some numbers I dug up, there are still quite a few users running OS 4.6. So, the fact that OS fragmentation exists can’t be ignored. Maybe, at some point in the future when all Blackberry users are running OS 6.0 and up, animated UIs will be a lot easier to create and maintain. But, until then, a lot of the work is left up to the developer.

The simple example of field layout animation I provided previously works great, but it’s missing something. When watching those fields slide into place, I’m reminded of an Atari 2600 video game. The animation is too stiff and robotic. It needs life. It needs the help of easing curves. An ActionScript developer by the name of Robert Penner posted a chapter from a book he’s written that gives an excellent introduction to the concept of easing curves. In addition, he’s published a bunch of easing equations and licensed them under the BSD license. If you have any interest at all in this kind of stuff, I highly recommend taking a look at his work.

With the help of Penner’s work, I’ve added an overshoot interpolator to the original animation example I wrote. This makes the two text fields overshoot their targets and decelerate into position. It makes for a much more lively and fluid visual. In addition to this, I’ve created two other example projects. The first one demonstrates what could be called an elastic or bounce effect. The field being animated, captioned “Boing,” bounces into its target position like it’s attached to a bungee cord. It can be a great effect when used to drop things like status messages, for example, into place for a brief period of time and then pulling them out of view. This example shows how the dynamic positioning of the animated field can have an interesting cascading effect on the fields around it. The last example, however, eliminates that cascading effect and simply overlays the animated field over top the others.

As far as performance is concerned, these examples are probably not going to set any FPS records. They rely on the updateLayout method provided by the framework’s Field class. When called, it triggers the sublayout method of the containing Manager (which is a subclass of Field). A more efficient method may be to bypass the layout and directly paint objects on the screen. Instead of calling updateLayout on each notification from the Animation class, a call to the Manager’s invalidate method could be performed instead. That would, in turn, trigger its contents to be painted. The contents could be fields, shapes, text, bitmaps, or whatever. But, unless your application is really busy, I’m assuming that the updateLayout method will be acceptable. I have yet to actually try these techniques in an application, so I could be wrong.

I’ll leave you with a short screencast of one of the example projects in action. It’s really not a great visual of the animation, as it looks choppier than it actually runs on the simulator. But, perhaps it will be enough to get you excited about adding animations to your applications too!

Hello World from Neil Loknath on Vimeo.

Blackberry: Animated

After using a few Blackberry applications, I noticed that many (if not all) of the them lack anything more than a very basic UI. So, I decided to take a peek and figure out why.

As it turns out, RIM hasn’t provided an API for animations in their Blackberry SDK until now. Now being OS 6.0. Yes, after six OS versions, they’ve finally realized that people enjoy a visually attractive and fun user experience. Better late than never, I guess.

So, what to do if you’re writing apps for devices running anything less than OS 6.0? Googling the keywords “Blackberry animation” brings up very few hits. I did find a great example, however, on stackoverflow.com. I found that one particularly interesting, so I decided to work up a test based on it. It’s very, very basic. But, the Animation class that materialized is something that could be very useful. See below:

/**
 * Copyright 2010 Neil Loknath
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ***/

package com.nloko.animationtest;

import net.rim.device.api.system.Display;

public class Animation {
	public static interface AnimationListener {
		void onNewFrame(int time);
	}

	private static final int DEFAULT_FPS = 40;

	private int _duration;
	private int _time;
	private int _fps = DEFAULT_FPS;
	private Thread _thread;
	private volatile boolean _running;

	private AnimationListener _listener;

	public Animation(int duration) {
		_duration = duration;
	}

	/**
	 * The number of frames in an animation per second
	 * @param fps
	 */
	public void setFPS(int fps) {
		_fps = fps;
	}

	public int getFPS() {
		return _fps;
	}

	/**
	 * The total duration of the animation
	 * @param duration
	 */
	public void setDuration(int duration) {
		_duration = duration;
	}

	public int getDuration() {
		return _duration;
	}

	/**
	 * The time elasped since the animation started
	 * @return
	 */
	public int getTimeElapsed() {
		return _time;
	}

	public void setListener(AnimationListener l) {
		_listener = l;
	}

	/**
	 * Start the animation
	 */
	public synchronized void start() {
		if (_thread == null) {
			_running = true;
			_thread = new Thread(new Runnable() {
				public void run() {

					final int idleTime = 1000 / _fps;
					final int duration = _duration;
					_time = 0;

					try {
						while(_running && _time <= duration) {
							animate(_time);
							_time += idleTime;

							Thread.sleep(idleTime);
						}

						if (_time - idleTime < duration) animate(duration);

					} catch (InterruptedException e) {}

					stop();
				}
			});

			_thread.start();
		}
	}

	/**
	 * Stop the animation
	 */
	public synchronized void stop() {

		_running = false;

		if (_thread != null && _thread.isAlive()) {
			_thread.interrupt();
			_thread = null;
		}
	}

	/**
	 *
	 * @param time
	 */
	private void animate(int time) {
		AnimationListener listener = _listener;
		if (listener != null) {
			listener.onNewFrame(time);
		}

	}
}

Without going into too much detail, what the class does is abstract away all the boring details of managing when and how often to display animated frames. All the user of the class needs to do is tell it the duration of the animation, when to start, and listen for events. To see it in action, here’s a sample project that simply scrolls in the word “Hello” from the left and the word “World” from the top until they both meet in the centre to form the sentence (can you guess?) …”Hello World!”

In the coming weeks, I hope to find the time to add an Interpolator to this class for rendering effects like acceleration, deceleration, and elastic effects ie. overshooting a target and bouncing back. Fun stuff!

SyncMyPix 0.14.4

This post is a little overdue, but better late than never.

Even though I’ve been extremely busy lately, I’ve still managed to churn out some updates to SyncMyPix. I last blogged about version 0.12. A lot has changed since then. Here’s a quick summary of the most notable changes:

  • Improved picture download reliability
  • Improved picture quality
  • Added ‘Cache to SD’ option for up to 5Mb picture caching on SD card
  • Enabled installation on internal or SD memory for Froyo
  • Added ‘Send Log’ option for troubleshooting assistance
  • UI refresh
  • Crop option now  always based off original photos

There are some other bug fixes here and there. For a complete listing, you can view the change log.

UI Refresh

My goal was fresh, bright, and simple. I think I’ve achieved that, but here’s a screenshot so you can see for yourself.

A much welcomed side effect of the changes was an improvement to the overall responsiveness of the UI. This is probably due to the removal of the background image and transparencies. The bar at the bottom provides handy access to common functions. I’ve seen a lot of apps put bars like this at the top of the application. But, to me it seems much more natural at the bottom. I typically hold my phone with one hand and operate it with my thumb. By placing the buttons at the bottom, they are easily accessible during one-handed operation.

Small Screen Support

Some have been asking about this. And, you’ll be happy to know that it’s coming. Actually, it’s already there. It will be enabled in the next release.

Caching

By caching photos to the SD card, you get a speedier experience along with some savings to your precious data usage. If you have caching enabled (it is by default), the original photos will be cached in /Android/data/com.nloko.android.syncmypix/cache. By using this specific directory structure, if you should choose to uninstall SyncMyPix, the cache will be cleared as well (Android supports this on Froyo only). Hopefully, more developers will make use of this, as it is very annoying to have to clean this type of stuff up.

You may notice that these photos will appear in your Gallery app. If you don’t like this, you can add an empty file named “.nomedia” to the previously mentioned directory. Then, the Gallery will ignore the cache.

I hope you’ve been enjoying the updates. Hopefully, I’ll be able to keep them coming!

A new home

…for my blog, that is. I’ve moved to WordPress, and I’m hosting on NearlyFreeSpeech.NET. I love the NFSN pricing model. It’s basically pay-as-you-go for the web hosting world. You only pay for what you use. Costing me pennies so far.

Why did I migrate my blog? You mean aside from the fact that I’m a geek, and I love to tinker with things? Mostly, it’s because I like the idea of having full control over my blog and its hosting.

I went with a “.ca” top-level domain mostly for privacy reasons. .ca domains and registrant information are maintained by CIRA in a national registry. And, registrant information is kept private.

So, those of you who followed my old blog will want to start following this one instead. If you do happen to end up at the old site, however, you’ll notice that I’ve gone through the tedious operation of deleting some old posts and updating meaningful ones with links that will direct you to the appropriate page here.  Since Blogger doesn’t allow a way to implement 301 redirects, it seemed like the best thing to do. The old blog didn’t really have all that many posts anyway.

Due to the import process, some of the old posts may look like they were formatted by a two year old. I’ll likely fix up any of those kinds of issues as I come across them/find the time to do so.

That’s it. That’s all. More to come soon!