Friday, December 14, 2012

How to Build Live Wallpaper with Canvas on Android

This will be a very basic canvas live wallpaper with android. Just to get people started.
Just read the comments on the code to understand how everything works.

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="org.altlimit.samplelivewallpaper"
          android:versionCode="1"
          android:versionName="1.0">
    <uses-sdk android:minSdkVersion="15"/>
    <application android:label="@string/app_name" android:icon="@drawable/ic_launcher">
        <activity android:name="MyActivity"
                  android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
        <!--The service that will be drawing your wallpaper-->
        <service android:name=".SampleService"
                 android:label="@string/app_name"
                 android:permission="android.permission.BIND_WALLPAPER"
                 android:icon="@drawable/ic_launcher">

            <intent-filter>
                <action android:name="android.service.wallpaper.WallpaperService" />
            </intent-filter>
            <!--To tell your wallpaper which settings activity it will launch-->
            <meta-data android:name="android.service.wallpaper"
                       android:resource="@xml/meta" />

        </service>
        <!--The activity declaration settings for your wallpaper-->
        <activity android:label="Settings"
                  android:name=".SampleSettings"
                  android:exported="true"
                  android:icon="@drawable/ic_launcher">
        </activity>

    </application>

    <uses-feature
            android:name="android.software.live_wallpaper"
            android:required="true" >
    </uses-feature>

</manifest>

res/xml/settings.xml - this is a normal preference activity
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
                  android:title="Sample Live Wallpaper Settings"
                  android:key="samplelive_wallpaper_settings">

    <PreferenceCategory
            android:title="Settings">

        <CheckBoxPreference
                android:key="pref_sample_checkbox"
                android:defaultValue="false"
                android:title="Sample Checkbox"
                />
        <EditTextPreference
                android:key="pref_count"
                android:defaultValue="10"
                android:numeric="integer"
                android:title="Write Me This amount"
                />
        <EditTextPreference
                android:key="pref_text"
                android:defaultValue="Hello World"
                android:title="What to write"
                />
    </PreferenceCategory>

</PreferenceScreen>


SampleSettings.java - normal preference activity
package org.altlimit.samplelivewallpaper;

import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceActivity;

public class SampleSettings extends PreferenceActivity implements SharedPreferences.OnSharedPreferenceChangeListener {

    @Override
    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) {

    }

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

        addPreferencesFromResource(R.xml.settings);
    }
}


MyActivity.java
package org.altlimit.samplelivewallpaper;

import android.app.Activity;
import android.app.WallpaperManager;
import android.content.Intent;
import android.os.Bundle;

public class MyActivity extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        // Call the live wallpaper picker when started your app
        Intent intent = new Intent(WallpaperManager.ACTION_LIVE_WALLPAPER_CHOOSER);
        startActivity(intent);

    }

    @Override
    protected void onResume() {
        super.onResume();    //To change body of overridden methods use File | Settings | File Templates.
        finish();
    }
}


SampleService.java
package org.altlimit.samplelivewallpaper;

import android.content.SharedPreferences;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.service.wallpaper.WallpaperService;
import android.text.Layout;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.util.Log;
import android.view.SurfaceHolder;

public class SampleService extends WallpaperService {


    /**
     * A wallpaper service is actually just another android service.
     * You just need to implement the onCreateEngine to get started and return your engine
     */
    @Override
    public Engine onCreateEngine() {
        return new SampleEngine();
    }

    class SampleEngine extends Engine {
        /**
         * Create variables for your settings
         */
        private boolean checkedSettings = false;
        private String textToWrite = null;
        private int timesToWrite = 0;
        private boolean isVisible = false;

        private Paint paint;
        private StaticLayout staticLayout;
        private Handler redrawHandler = new Handler();
        private Runnable redrawRunnable = new Runnable() {
            @Override
            public void run() {
                draw();
            }
        };

        // Here is where all the drawing stuff happens
        private void draw() {
            SurfaceHolder holder = getSurfaceHolder();
            Canvas c = null;
            try {
                c = holder.lockCanvas();
                if (c != null) {
                    // Resets your canvas to black surface
                    c.drawColor(Color.WHITE);
                    int cW = c.getWidth();
                    int cH = c.getHeight();

                    if (textToWrite != null && timesToWrite > 0) {
                        paint = new Paint();

                        String multipleText = "";
                        for (int i = 0; i < timesToWrite; i ++) {
                            multipleText = multipleText.concat(" ").concat(textToWrite);
                        }

                        paint.setColor(Color.BLACK);
                        staticLayout = new StaticLayout(multipleText, new TextPaint(paint), cW - 10, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
                        // Move top left where to write the text
                        c.translate(10, cH / 3);
                        staticLayout.draw(c);

                        // if you want to write more stuff to canvas
                        c.restore();
                    }

                }
            } finally {
                if (c != null)
                    holder.unlockCanvasAndPost(c);
            }
            // If you have changing data then here is how you redraw
            if (isVisible) {
                // if you have a longer delay you probably want to clear other in queue to avoid multiple draws
                redrawHandler.removeCallbacks(redrawRunnable);
                redrawHandler.postDelayed(redrawRunnable, 500); // you add delay if you dont change oftent
            }
        }

        @Override
        public void onVisibilityChanged(boolean visible) {
            super.onVisibilityChanged(visible);
            isVisible = visible;
            // Called when your wallpaper is viewed or not so load settings if it is to show changes instantly
            if (isVisible) {
                final SharedPreferences preference = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
                checkedSettings = preference.getBoolean("pref_sample_checkbox", false);
                textToWrite = preference.getString("pref_text", null);
                timesToWrite = Integer.valueOf(preference.getString("pref_count", "0"));
                // Now we update the canvas
                draw();
            }
        }
    }
}


That's all of the basic foundation of creating live wallpaper with canvas. You can implement more method for the engine and the service to have more control. But this will basically give you all the initial starting point. Download the full project here
Friday, December 7, 2012

Join Query on Google App Engine Datastore

App Engine Datastore is a no sql database. That means you cannot  do standard sql queries. But they do have the basic queries using GQL. It is a no SQL database which is very reliable, does not slow down even with terabytes of data, and has a nice indexing mechanism to fetch data.

You can use Google Cloud SQL if you need a relational database, it's a manage mySQL database by google. By the time I'm writing this, they now support up to 100GB of mySQL database, but still limited to the limitations of a standard mySQL and it's coolness.

So if you really can't avoid a de-normalize table like you want to show the name of a user in a list, I'll show you samples. This will all be using python 2.7 and ndb.


from google.appengine.ext import ndb
# just a sample model for how to efficiently join them
class User(ndb.Model):
    name = ndb.StringProperty()
    photo = ndb.BlobKeyProperty()
    """
    always create a text version of your blobkey and store
    the result of images.get_serving_url(blob) to it one time if
    your app allows it
    """    
    photo_path = ndb.TextProperty()

class Page(ndb.Model):
    data = ndb.TextProperty()

class Comment(ndb.Model):
    user = ndb.KeyProperty()
    page = ndb.KeyProperty()
    created = ndb.DateTimeProperty(auto_now_add=True)
    message = ndb.TextProperty()

    @classmethod
    @ndb.tasklet
    def get_comment_async(comment):
        result = comment.to_dict()
        user = yield comment.user.get_async()
        result['user'] = { 'name' : user.name, 'photo' : user.photo_path }
        raise ndb.Return(result)

# on your handler
class MainHandler(webapp.RequestHandler):
    def get(self):
        page = Page.get_by_id(self.request.get('id'))
        # now we query comments of the page
        comments = Comment.query(Comment.page == page.key)
                          .order(-Comment.created).fetch()
        futures = []
        # another good use of tasklet is to load fields info asynchronously
        for comment in comments:
            futures.append(yield Comment.get_comment_async(comment))
        # ndb will try to batch what it can so it only does few network hops
        ndb.Future.wait_all(futures)
        # pass your results to your view
        view_data['comments'] = [future.get_result() for future in futures]
Thursday, December 6, 2012

Web Scraping with Google App Engine

Here is a quick tutorial on how you can scrape google search results asynchronously with app engine and caching its result in memcache. You should not use this directly because you can get blocked by google, this is just a sample for you on scraping web pages, feeds, xml, etc.

But if you do want to do something like this, I recommend adding delays, and act more like a human on your scrapes. But I believe that is against their TOS.

I added the use of async here for people who don't know how to use them yet so they can learn in the process. The code below is a complete working google search scraper, read the code comments to understand everything.

This is all done with python 2.7 with ndb

app.yaml
application: your-application-id
version: 1
runtime: python27
api_version: 1
threadsafe: true

handlers:
- url: /.*
  script: main.app

libraries:
- name: lxml
  version: latest
main.py
import urllib
from urlparse import urlparse, parse_qs
from google.appengine.ext import webapp, ndb
from lxml import html

# make the function an ndb.tasklet so you don't need to wait for each search
@ndb.tasklet
def search_google_async(keyword):
    """
    ndb has all the async methods of memcache & urlfetch
    and tries to auto batch everything behind the scenes
    """
    ctx = ndb.get_context()
    url = 'http://www.google.com/search?' + urllib.urlencode({ 'q' : keyword })
    """
    if you don't know yield, you should read up on it a bit, google yield and generators with python
    simple explanation: your function will stop here and do all the operations in batches
    then continue on with the next yields
    """
    # check first if you already cached the results
    cache = yield ctx.memcache_get(url)
    if cache:
        """
        tasklets returns by raising an exception so converting a normal function to its async
        counterpart you just add yield before any async calls
        then change return to raise
        """
        # if you did return the cached results
        raise ndb.Return(cache)

    # we use async method of urlfetch from ndb context
    response = yield ctx.urlfetch(url)

    links = []
    if response.status_code == 200:
        raw_html = response.content
        # use the lxml library to convert the string to dom
        dom = html.fromstring(raw_html)
        # use a css selector to get all anchor tags
        anchors = dom.cssselect('a')
        for anchor in anchors:
            # get its href attribute
            link = anchor.get('href')
            """
            since google put all the results like this,
            you can probably do a[href^=/url?q=] on the css selector
            """
            if link.startswith('/url?q='):
                # we get the query string q= you can do this however you want
                # it stores the url of the results
                parsedUrl = urlparse('http://www.google.com' + link)
                queryStr = parse_qs(parsedUrl.query)
                links.append(queryStr['q'])
        
        """
        now we set the results in memcache with url key and value of list of links
        you can remove yield here and batch all of it later since we have
        app = ndb.toplevel(app)
        meaning it will not terminate until all async methods are finished
        """
        yield ctx.memcache_set(url, links)
        
    # we return the links of result
    raise ndb.Return(links)

class MainHandler(webapp.RequestHandler):

    def get(self):
        keywords = [
            'how to make pizza',
            'where can i buy a dog',
            'how big is the grand canyon'
        ]
        """
        an ndb.tasklet return sets of futures
        so we get them all then do everything with as little
        as possible calls, let the ndb stuff handle the batching
        """
        futures = []
        for keyword in keywords:
            futures.append(search_google_async(keyword))
        # so here is where everything waits for the results
        ndb.Future.wait_all(futures)
        # you call .get_result() which is the value you raised/returned in your tasklet
        self.response.out.write([future.get_result() for future in futures])


app = webapp.WSGIApplication([('/', MainHandler)],
                             debug=True)

# to make sure all unhandled async task are finished
app = ndb.toplevel(app)