Showing posts with label google. Show all posts
Showing posts with label google. Show all posts

19 July 2020

Photo backup and sync using Google Cloud Bucket Storage

This morning I spotted a tweet by Greg Wilson whom I don't know and have never met. However it appears he's some kind of director at Google Cloud, so that might explain why I'm following him and why he tweeted this:

https://twitter.com/gregsramblings/status/1284743960955510787?s=20

Archiving my newly organized 238k+ photo library (2.2TB) to Google Cloud Storage with: gsutil -m rsync -r -d . gs://{mybucketname} I'm using crazy-cheap 'archive' storage class in single regionStorage price: $0.0012/GB/Month (!) @gcpcloud

This really interested me as I've been trying to upload photos to an AWS (Amazon Web Services) bucket recently but it's been a tedious process.

I am aware that various cloud services like dropbox and pcloud exist, but I want the following:

  1. Encryption
  2. Ease of use
  3. Cheap
I really only want this as a backup in case of fire or failure of one of my crummy old USB HDDs fails. I genuinely don't understand why so many of these services make such a bad job of encryption or offer it as some kind of bizarre add on. Come on people this is 2020, it's not hard and it shouldn't be expensive.

So I realised with some basic AWS skills I can spin up a bucket running glacier for pennies a month. This worked really really well, but the upload tools are slow and not terribly reliable for bulk uploads. Which I guess is understandable because the whole point of AWS is to build your own right?

Back to my original point, I stumbled across this tweet and thought hmm, that sounds nice and easy. Let's give it a go on a dull lockdown Sunday.

The following I followed from here: https://cloud.google.com/storage/docs/quickstart-gsutil

1. Install Python 3.8
Did someone say snakes on a plane?
sudo add-apt-repository ppa:deadsnakes/ppa
sudo apt-get update
sudo apt-get install python3.8

2. Download & Extract Google Cloud SDK
https://cloud.google.com/sdk/docs

3. Create a bucket
Then I created a new Google Cloud Project on their cloud console and a new Cloud Storage Bucket. This was actually really easy and the bucket creation process easily walked me through the access policies and encryption setup. Although to be fair the AWS process is pretty good now as well. Don't forget to select your preferred storage type but bare in mind different types have different latency, pricing and minimum storage commitments.
https://cloud.google.com/storage/pricing#operations-pricing

4. Initialise the sdk
Run gcloud init this will initialise your sdk and link it to your google account. You can then of course select the project you're working on.

5. Copy a file
That's pretty much it, from there you can copy individual files using gloud cp or as Greg's tweet suggest you can use the sync option for bulk upload.

This is such a simple process I was really impressed.

One thing it's important to note is that Google Storage buckets may have a minimum storage time period:
https://cloud.google.com/storage/pricing#archival-pricing


25 October 2016

Google Pixel Review


Everyone and their dog seems to be putting out Google Pixel reviews so I thought I'd chip in with my thoughts and findings.

First thing I discovered is you need a nano sim. Grr, hadn't thought about that. Thankfully BT were amazing and I had one less than 24 hours later.

The phone is smaller than I expected, much smaller. I hate big phones and that's part of the reason why I've stuck with my Nexus 4 for four years. I was genuinely worried about it, but within a few hours I had gotten used to the Pixel's larger length and I have no regrets. It's incredibly small in depth and with is small too, so the larger height is well compensated for.

The phone itself is beautiful, the curves are nice and it feels very comfortable. The metal finish is a joy to feel and the screen blends well into the case. The back glass surrounding the camera and fingerprint reader is odd, I don't quite get it. It spoils the design a little and I don't know why it's not all metal, but it's not of any real consequence.

The screen is stunning, absolutely incredible, again I'm comparing most of this to my Nexus 4 so it is worlds apart. The colours and display are crystal clear and I sometimes find myself just staring at it.

The speed is lightening fast and it seems to cope with whatever I can throw at it with ease. I haven't really pushed it yet but it is so responsive and quick I can't see it struggling. The battery is good, compared to my four year old phone it lasts infinitely longer, but it's no more or less than I'd expect from a modern phone. It doesn't last weeks but it'll get me through a couple of days.

The USB C is a cool feature, but sadly it's not new nor unique, it works and it charges fast, actually it charges really really fast. Plus you're less likely to destroy your phone by ramming the charging cable in upside down. The fingerprint reader isn't new either, but frankly that's rocked my world! I love it.

Now the OS is a difficult one, it's fine and I have no complaints. However ...I am an Android developer and have used phones by every manufacturer you care to name and every Android OS extensively. I think we're well beyond the point where an OS update makes any real difference. In terms of speed and battery use, they've pretty much done all they can. What we see now is minor updates and UI tweaks like the settings changes. Not since Material design has anything really made much difference to the user. I'm not unimpressed, it just hasn't changed my interactions with the phone much at all.

There are a few things with confuse me and they are largely the things they've "borrowed" from Apple:

  • The round icons, not sure it makes a lot of difference, but what do we developers do? Can we release with round and square icons? If we switch to square, what happens to the old OSs that aren't prepared for round icons?
  • Quick Tap or whatever you call it where you can long press on a launcher icon? That's just a blatant rip off, and it adds nothing to the user experience. Bah!


Lastly is the Google Assistant, this is impressive! Its learnt my voice and ignores my girlfriend's, which I love! It understands easily what I'm asking it and responds quickly and generally with a surprising insight. That said....I'm still not going to talk to it!

So there are my highlights, in short it's a fantastically well put together phone and I'm really enjoying it. Go get one.

12 May 2015

Android Auto First Play

Sadly I'm not lucky enough to have an Android Auto headset in my car, nor will my current car support one. However, I am desperately keen to have Android Auto in my car, to me it make so much sense as most proprietary systems really are awful. So in lieu of an actual system to play with, I thought I’d give Android Auto app creation a go, and see how it worked.

First of all read the dev guide:

There are currently limitations, meaning only Audio and Messaging apps are available, so I thought I’d have a crack at creating an Audio app. I’m really not looking at a shiny well designed app here, I just want to get a proof of concept type app out the door.

  1. Setup
    1. Create a new project selecting Android 5.0 (Api 21) or newer as the target
    2. Import support library (22.1.1 or better) in gradle
compile 'com.android.support:appcompat-v7:22.1.1'
    1. Open SDK manager and install “Android Auto API Simulators” from the Extras branch

  1. Update Android Project to use Auto
We need to tell Android Studio we’re creating an Auto project, so create an xml folder in the res directory and add a file named
automotive_app_desc.xml
With the following contents

<automotiveApp>
    <uses name="media" />
</automotiveApp>

Now tell the manifest where to find this file by adding inside the application tag:

<meta-data android:name="com.google.android.gms.car.application" android:resource="@xml/automotive_app_desc"/>

You can also give yourself an icon for your app

<meta-data android:name="com.google.android.gms.car.notification.SmallIcon" android:resource="@mipmap/ic_launcher" />

  1. Install the simulator
This is explained here:
You basically need to use adb to install an app which is supplied in the auto simulator downloaded in step 2. You can find the apk here:
<sdk>/extras/google/simulators/media-browser-simulator.apk
This isn’t what I expected at all. I was expecting a virtual device, but instead you get a simulator that runs on your actual phone or device and simulates the two types of android auto app. It’s a bit odd, but I guess it works.
If you’re setup you should find an app on your phone named “Media Sim”, run this and you should see the Google Play App running and working fine.

Code!

OK Now we’re ready to write some code. Don’t forget, I’m just creating a proof of concept Audio app here. So instead of streaming music I’ve copied an mp3 to res/raw and I’m going to try and play this file.

Create a service in the Manifest:

<service android:name=".MusicService" android:exported="true">
    <intent-filter>
        <action android:name="android.media.browse.MediaBrowserService"/>
    </intent-filter>
</service>

Create a class in your package and make it extend MediaBrowserService. This will mean you’ve got to implement the method onLoadChildren() and onGetRoot(). Now as you will see if you walk through the Google example this is how we create a tree structure of bands, albums and songs. Meaning you can traverse your music library. I was simply looking for the quickest route through all this to display one file, so I’ve created an array list of one mediaItem which is loaded with my mp3 and returned.
If you’re struggling to figure what to do here I advise to download the Google sample:

I’ve also created a MediaSessionCallback class which extends MediaSession.Callback. As you can see by the implemented methods, this is just a callback class for the play, pause, skip etc buttons. My version is pretty quick and dirty. Google provides a standard button interface for audio apps and in order to interface with these buttons we’re going to use the MediaSession callback.

Here’s the manifest:


<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.wunelli.android.autotest" >

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >

        <meta-data android:name="com.google.android.gms.car.application"
                   android:resource="@xml/automotive_app_desc"/>

        <meta-data android:name="com.google.android.gms.car.notification.SmallIcon"
                   android:resource="@mipmap/ic_launcher" />

        <activity
            android:name=".ActivityMain"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <service android:name=".MusicService" android:exported="true">
            <intent-filter>
                <action android:name="android.media.browse.MediaBrowserService"/>
            </intent-filter>
        </service>
    </application>
</manifest>



Here’s the code for the service:

package com.wunelli.android.autotest;

import android.media.MediaMetadata;
import android.media.MediaPlayer;
import android.media.browse.MediaBrowser;
import android.media.session.MediaSession;
import android.os.Bundle;
import android.service.media.MediaBrowserService;
import android.util.Log;

import java.util.ArrayList;
import java.util.List;

public class MusicService extends MediaBrowserService{

    private MediaSession mSession;
    MediaPlayer mPlayer;

    private static final String TAG = MusicService.class.getSimpleName();
    public static final String CUSTOM_METADATA_TRACK_SOURCE = "__SOURCE__";

    @Override
    public void onCreate() {
        super.onCreate();
        Log.d(TAG, "onCreate");

        initMedia();

        // Start a new MediaSession
        mSession = new MediaSession(this, "MusicService");
        setSessionToken(mSession.getSessionToken());
        mSession.setCallback(new MediaSessionCallback());
        mSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
    }

    @Override
    public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) {
        Log.d(TAG, "OnGetRoot: clientPackageName=" + clientPackageName + "; clientUid=" + clientUid + " ; rootHints=" + rootHints);

        return new BrowserRoot("__ROOT__", null);
    }

    private void initMedia(){
        mPlayer = MediaPlayer.create(this, R.raw.roboto);
    }

    @Override
    public void onLoadChildren(String parentId, Result<List<MediaBrowser.MediaItem>> result) {

        List<MediaBrowser.MediaItem> mediaItems = new ArrayList<>();

        MediaMetadata item = new MediaMetadata.Builder()
                .putString(MediaMetadata.METADATA_KEY_MEDIA_ID, "1")
                .putString(CUSTOM_METADATA_TRACK_SOURCE, "roboto.mp3")
                .putString(MediaMetadata.METADATA_KEY_ALBUM, "Kilroy Was Here")
                .putString(MediaMetadata.METADATA_KEY_ARTIST, "Styx")
                .putLong(MediaMetadata.METADATA_KEY_DURATION, 330000)
                .putString(MediaMetadata.METADATA_KEY_GENRE, "rock")
                .putString(MediaMetadata.METADATA_KEY_ALBUM_ART_URI, "album_art.jpg")
                .putString(MediaMetadata.METADATA_KEY_TITLE, "Roboto")
                .putLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER, 1)
                .putLong(MediaMetadata.METADATA_KEY_NUM_TRACKS, 1)
                .build();
        String musicId = item.getString(MediaMetadata.METADATA_KEY_MEDIA_ID);

        String hierarchyAwareMediaID = "rock|" + musicId;
        MediaMetadata trackCopy = new MediaMetadata.Builder(item)
                .putString(MediaMetadata.METADATA_KEY_MEDIA_ID, hierarchyAwareMediaID)
                .build();
        MediaBrowser.MediaItem bItem = new MediaBrowser.MediaItem(trackCopy.getDescription(), MediaBrowser.MediaItem.FLAG_PLAYABLE);
        mediaItems.add(bItem);

        result.sendResult(mediaItems);
    }

    private final class MediaSessionCallback extends MediaSession.Callback {
        @Override
        public void onPlay() {
            Log.d(TAG, "play");
            mPlayer.start();
        }

        @Override
        public void onSkipToQueueItem(long queueId) {
            Log.d(TAG, "OnSkipToQueueItem:" + queueId);
        }

        @Override
        public void onSeekTo(long position) {
            Log.d(TAG, "onSeekTo:" + position);
        }

        @Override
        public void onPlayFromMediaId(String mediaId, Bundle extras) {
            Log.d(TAG, "playFromMediaId mediaId:" + mediaId + "  extras=" + extras);
            mPlayer.start();
        }

        @Override
        public void onPause() {
            Log.d(TAG, "pause.");
            mPlayer.start();
        }

        @Override
        public void onStop() {
            Log.d(TAG, "stop.");
            mPlayer.reset();
            initMedia();
        }

        @Override
        public void onSkipToNext() {
            Log.d(TAG, "skipToNext");
        }

        @Override
        public void onSkipToPrevious() {
            Log.d(TAG, "skipToPrevious");
        }

        @Override
        public void onCustomAction(String action, Bundle extras) {
            Log.i(TAG, "Unsupported action: " + action);
        }

        @Override
        public void onPlayFromSearch(String query, Bundle extras) {
            Log.d(TAG, "playFromSearch  query=" + query);
        }
    }
}















20 November 2014

Lollipop with Nexus 7 (2012)

Just a quick one to anyone who owns the 2012 version of the Nexus 7. DON'T upgrade to Lollipop.
I got the Over the Air (OTA) update last week and put it off for a few days as I don't like to rush. Eventually I got fed up with the reminders and went ahead.

Well what a disaster, its made my Nexus 7 almost unusable. The Keyboard doesn't open for sometimes up to a minute, very few of the apps run at all and its generally slow and un-responsive. 

This is a crying shame as I think Lollipop is a great release, I like the direction Android has taken. Its just a real shame Google didn't test this before pushing it out to everyone.

07 July 2014

Android Wearables First Go

I thought I’d try the new Android wear SDK and see if I could do anything useful with it. It took a while and a few head scratching moments but I got there in the end. What I wanted to do was send a message from the Android wear watch to my Android phone. Seems like such an easy ask!


First up you need Android Studio. I hope they make it possible with Eclipse / ADT, but I couldn't make it happen and quickly gave up! You also need to be super up to date with your SDK, as of today my versions:


  • Android SDK Tools 23.0.2
  • Android 4.4W (API 20)
  • Android 4.4.2 (API 19)
  • Google Play Services revision 18 (5.0)


Once all that is ready and working without error you need a to create a new project and follow the steps as per the Android developer page: http://developer.android.com/training/wearables/apps/creating.html
Basically create an app for mobile as per usual and a partner application for wear. You also need to setup an emulator or use a real watch. I can’t afford a real wear watch so I'm on the emulator. Follow the steps to connect your phone via usb cable and the emulator, bit fiddly but works eventually. The basic idea here is to use Google Play Services to transfer messages between the watch and the phone with the Message API. I believe this is new which is why it is so important to ensure everything is up to date.


Now the code, first the mobile side of things.


This is the gradle dependancies. My min sdk is 9 and my target sdk is 20.
dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    wearApp project(':wear')
    compile 'com.android.support:appcompat-v7:19.+'
    compile 'com.google.android.gms:play-services-wearable:+'
}


Now we’re not making a fancy front end here as this is just really a proof of concept. Here’s the MainActivity.java. As you should be able to see this is a fairly simple listener.


public class MainActivity extends Activity implements GoogleApiClient.ConnectionCallbacks,
    GoogleApiClient.OnConnectionFailedListener,
    MessageApi.MessageListener{


Now some member variables:


    GoogleApiClient mGoogleApiClient;
    public static final String START_ACTIVITY_PATH = "/start/MainActivity";


Here is the on create:


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

         // Create a GoogleApiClient instance
        mGoogleApiClient = new GoogleApiClient.Builder(this)
                .addApi(Wearable.API)
                .addConnectionCallbacks(this)
                .addOnConnectionFailedListener(this)
                .build();

         mGoogleApiClient.connect();
}


What we’re doing here is initialising the Google API Client with the Wearable.API. These are the methods overwritten from the implements section.


    @Override
    public void onConnected(Bundle bundle) {
        Log.i("mobile", "Connected");
        //We are connected, we can add our listener to this Activity.
        Wearable.MessageApi.addListener(mGoogleApiClient, this);
    }

     @Override
    public void onConnectionSuspended(int i) {
        Log.i("mobile", "Connection Suspended");
    }

     @Override
    public void onConnectionFailed(ConnectionResult connectionResult) {
        Log.i("mobile", "Connection Failed");
    }

     @Override
    public void onMessageReceived(MessageEvent messageEvent) {
        Log.i("mobile", "msg recieved and understood");

         if (messageEvent.getPath().equals(START_ACTIVITY_PATH)) {
            Log.i("mobile", "******WHOOOO*******");
            //Send a message to a Handler for UI Update.
            myHandler.sendEmptyMessage(DO_UPDATE_TEXT);
        }
    }


That’s the mobile side of things more or less done :) Again, nothing fancy, just proof of concept.


Now the wearable part of the project. Here we’re going to send the message, but again we have to connect to Google API Client.


I’m just going to post the whole file here as it’s probably easier. I’ll skip the layout, as its just a button.


  1. First (in onCreate) we define and connect to our mGoogleApiClient.
  2. OnConnected we start of the getConnectedNodes Async Task. This needs to run seperate from the UI and basically grabs all connected nodes. In our case there is only one, but you should really check here and maybe flash up a dialog or something if there are more or less than one clients connected.


  1. Once that’s done we send a message as such sendMsg(results.get(0));. This sends the node ID we got from the Wearable API and calls the sendMsg function


  1. In SendMsg we call Wearable.MessageApi.sendMessage. As expected this sends our message. Right now the message is meaningless, but you could easily modify this example to send a real message and have the listener display it.


That’s it. Hope it helps, here is the wear project code:



package com.example.com.wearable;

 import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.wearable.view.WatchViewStub;
import android.util.Log;
import android.view.View;
import android.widget.TextView;

 import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.wearable.MessageApi;
import com.google.android.gms.wearable.Node;
import com.google.android.gms.wearable.NodeApi;
import com.google.android.gms.wearable.Wearable;

 import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;

 public class WearActivity extends Activity implements GoogleApiClient.ConnectionCallbacks,
        GoogleApiClient.OnConnectionFailedListener {

     private TextView mTextView;
    GoogleApiClient mGoogleApiClient;
    public static final String START_ACTIVITY_PATH = "/start/MainActivity";

     @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_wear);

         // Create a GoogleApiClient instance
        mGoogleApiClient = new GoogleApiClient.Builder(this)
                .addApi(Wearable.API)
                .addConnectionCallbacks(this)
                .addOnConnectionFailedListener(this)
                .build();

         mGoogleApiClient.connect();

         final WatchViewStub stub = (WatchViewStub) findViewById(R.id.watch_view_stub);
        stub.setOnLayoutInflatedListener(new WatchViewStub.OnLayoutInflatedListener() {
            @Override
            public void onLayoutInflated(WatchViewStub stub) {
                mTextView = (TextView) stub.findViewById(R.id.text);

                 findViewById(R.id.activity_wear_send_msg).setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        GetConnectedNodes task = new GetConnectedNodes();
                        task.execute(new String[]{"com"});
                    }
                });

             }
        });
    }

     private Collection<String> getNodes() {
        HashSet<String> results = new HashSet<String>();
        NodeApi.GetConnectedNodesResult nodes = Wearable.NodeApi.getConnectedNodes(mGoogleApiClient).await();
        for (Node node : nodes.getNodes()) {
            results.add(node.getId());
            Log.i("wear", node.getId());
        }
        return results;
    }

     @Override
    public void onConnected(Bundle bundle) {
        Log.i("wear", "Connection success");
        GetConnectedNodes task = new GetConnectedNodes();
        task.execute(new String[] { "" });
    }

     @Override
    public void onConnectionSuspended(int i) {
        Log.i("wear", "Connection suspended");
    }

     @Override
    public void onConnectionFailed(ConnectionResult connectionResult) {
        Log.i("wear", "Connection failed");
    }

     private void sendMsg(String node){
        String msg = "All your base";

         MessageApi.SendMessageResult result = Wearable.MessageApi.sendMessage(mGoogleApiClient, node, START_ACTIVITY_PATH, msg.getBytes()).await();
        if (!result.getStatus().isSuccess()) {
            Log.e("wear", "ERROR: failed to send Message: " + result.getStatus());
        }else{
            Log.i("wear", "Message sent: " + result.getStatus());
        }

     }

     private class GetConnectedNodes extends AsyncTask<String, Void, Void> {
        protected Void doInBackground(String... params) {
            ArrayList<String> results = new ArrayList<String>();
            NodeApi.GetConnectedNodesResult nodes = Wearable.NodeApi.getConnectedNodes(mGoogleApiClient).await();

             Log.i("wear", "Hello from GetConnectedNodes");
            Log.i("wear", "node count:" + String.valueOf(nodes.getNodes().size()));

             for (Node node : nodes.getNodes()) {
                results.add(node.getId());
                Log.i("wear", node.getId());
            }

             if(results.size() > 0){
                sendMsg(results.get(0));
            }
            return null;
        }
    }
}

And here's the gradle:

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.google.android.support:wearable:+'
    compile 'com.google.android.gms:play-services:+'
}




03 July 2014

24 Hours with Google Glass

So I've been lucky enough to get my hands on a pair of Google Glass and took them for a spin. Opinion is largely split as to their worth. They've certainly caused some excitement and a lot of chatter around the office.


Recently news has reached us that they've been banned at UK Cinemas, not a huge story but for some reason the media has made a big fuss about it. Why on earth anyone would want to record a whole film using a shaky crummy camera on your head I don't know. Does anyone download poor quality in-cinema recorded films these days?


The first thing everyone asks is about the privacy, are you filming me? toilets etc. I think this is just a fear thing, it's not that people record video where they shouldn't, it is just that they could. I remember the same discussion about camera phones, the amount of people I see texting while in the washroom is barely noticed any more....although it is gross. It’s slightly odd to me that people worry about this but they don’t worry about CCTV.


The second thing, (and I think the most important thing) that strikes most people is what do they do. OK so you put them on and take a photo or two or maybe play with the fantastic star chart app. After that it's a case of....ok now what? I think the big takeaway here is that Glass is re-active. It's great for notifications, texts, emails even phone calls work really well. Glass is happy to read them out to you and you can even reply via voice which works brilliantly. As a thing to "play" with, Glass is not impressive.


Most apps work well with voice, Glass does a good job of presenting the options available to you although it does need to improve. Google Play Music was notably poor here. You can't start nor stop music with voice, instead you have to tap the side. Any application that forces me to tap the side of the glasses loses the point. Why have a hands free wearable I can talk to, if I can only talk to it half of the time! I can tap my phone for that. It's in development though!


Next thing is in the car, obviously I tried this as a passenger :) First up the navigation app, this is very impressive. Turning itself off until a maneuver comes up then piping up with clear instructions and audio. Second is notifications with hands free, as I mentioned before, this is really great hands free and does mostly work well. However it is incredibly distracting. Even though you're still looking in the direction of the car in front, you're not focusing and so I would strongly suggest not using it whilst driving.


This leads nicely into my conclusion, what are they for? The hard cold fact is they are big and look weird. So you're not going to use them out with friends. I was very self conscious in public so avoided wearing them out and about. So if you don't use them when driving, don't use them in public and don't use them when with friends when would you use them? Which begs the question where are Google taking them? Are they hoping we'll all just suck it up and start looking a bit nerdy? Or is it just one big experiment?


So to conclude they are neat, they mostly work well and seem like a great step toward augmented reality. However they *are not* augmented reality, a few killer apps would work great for this but I can’t see that happening. I think the privacy critics will hush eventually. I really don't think much will happen until they make them smaller and more discrete, but maybe this is just Google's aim to nudge the rest of the world toward better lenses and smaller technology. For now I think Google will push wearables like watches more than Glass, watches don’t have cameras! I suspect Glass will become just another experiment or niche product. Maybe the technology for augmented reality just isn't there yet, but good try Google.

25 June 2014

Android and Google wishlist


Tomorrow is Google IO. The highlight of the year for all things Google, basically Christmas for an Android developer! There are millions of tech blogs out there speculating as to what we should expect. Sadly I'm in not at all affiliated with Google, or its employees, so I'm in no position to offer any insight what-so-ever!

I can however outline a few things off my Google / Android wishlist. This is very much a work in progress and I would love some comments and feedback on your thoughts / wants!

1) Android App Build Speed
This is a biggie for developers, I admit, I have no idea as to its feasibility. Building and deploying an app from Eclipse onto a device / Virtual Device takes forever. Especially annoying if you're working on the UI and need to tweak and deploy small regular updates. I wish it was super fast!

2) Google Now on Lock Screen
OK I haven't *really* thought this one through, as there are obvious security restrictions. However when I'm driving, I want to be able to say "OK Google...." Some things would be great when I'm in the car. For example, "OK Google, call Arthur Dent...." Very useful if I have bluetooth. "OK Google, play Guns N Roses". Again possibly brilliantly useful.

3) Android TV
Chromecast rocks, it really is brilliant, what happened to that idea of getting it built into TVs? That would save me a HDPI port and that annoying usb cable.

4) Upload my movies to Google Movies
I know this will never happen, but how bout letting me upload my existing dvd movies or ultraviolet movie collection to Google Movies so I can play them on my chromecast?

5) Google Photowall
Random one, my Mum doesn't shut up about photos on the TV. She will literally die happy if she can show her bffs her boring (trust me) holiday snaps on the TV. Preferablly controlled by her tablet. I did try a photowall product from Google but it was vaporware and a bit rubbish.

6) Shadows in Android
My boss is an iPhone user <sigh>. He doesn't shut up about Shadows! Sure we can add a hack with degrading lines but they don't look or feel as good or as natural shadows.

7) Java 1.7 for Android
Would be nice

8) Chromecast Que
I want to be able to que music. Through all my friends. Much like the Nexus Q. I want party mode, so all my friends can contribute to a shared music que from their unique music que.

9) Development on a Chromebook
I want a chromebook, I like the concept. However I can't justify a laptop that I can't develop on!

10) Bring back Ned Stark.

That's all I've got for now. Would love to hear your ideas....

--
I've updated this a bit as watching Google I/O. Seems like they got a few of my ideas sorted before I could articulate them ;)

25 January 2014

Google Cloud Messaging with Android and ColdFusion backend


Google Cloud Messaging (GCM) is an awesome little service. It enabled "push" messages to be sent from Google to a specific Android phone and picked up by your application. The scope for these messages is massive but the intent is for the content to be fairly small. A great example (and a great name) is a tickle. This is very small message intended to instruct the application to go and perform an action.

For example, say your app needs data to be kept in sync with your server. This could be done by polling, but polling is resource intensive and sucks up precious bandwidth. A far better idea would be for your server to send out a message whenever the necessary data is updated and tell your app to come and re-fetch the data when possible. The message sent to your app is called a tickle!

I've created a simple tutorial to briefly describe GCM and show an example. My example does have a lot of moving parts to it but hopefully is simple enough to get the basic idea. For my backend server I'm using a CFML server which keeps track of any device registering for messages and allows me to send a message out too that device.

The sample app uses three components:

  1. GCM Connection Server - A Google Cloud project to send the message from you to the device.
  2. Application Server - Your own personal server that tracks device ids and issues messages. This can be cfml, php aspx, whatever, but it needs the capacity to store data, preferably in a db.
  3. Your Android app - The app you write and distribute as necessary.

Step 1 - The GCM Server

OK First we need to setup our Google Cloud Messaging service. To do this we log onto Google's cloud messaging panel and configure a new project. This project will now sit and wait for instructions and when told will relay messages from our server to our elected android device.

To set this up, follow the instructions here: http://developer.android.com/google/gcm/gs.html

Step 2 - The Application Server

As mentioned for this I'm using CFML here with a mysql db. I've not spent much time on this so it doesn't look pretty but I just wanted to illustrate the point. First the db, I've used one simple table:

CREATE TABLE gcmtest{
 intID INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
 strName varchar(100) not null,
 registrationid varchar(200) not null
}

That's it for db work! We have an ID column, a human readable name and a registration id which will contain the device id which is registered with Google.

Now the application server needs two files. One to respond to incoming registration requests and one to send messages, first the registration responder. Now a word of warning, this isn't production worthy, it's just a test bed. You should sanitise and protect your data much more thoroughly. As you can see it takes two values from the URL and inserts them into the db.

receive.cfm:
<cfif structKeyExists(url, "registrationid") AND structKeyExists(url, "name")>

    <cfquery name="qInsertReg" datasource="local">
        insert into gcmtest 
        (
            registrationid,
            strname
        )
        values
        (
            <cfqueryparam cfsqltype="CF_SQL_VARCHAR"    value="#url.registrationid#" />,
            <cfqueryparam cfsqltype="CF_SQL_VARCHAR"    value="#url.name#" />
        )
    </cfquery>

    <cfoutput>Done.</cfoutput>

</cfif>

Next you need the page which will send out messages, send.cfm:


<cfif structKeyExists(url, "regid")>

    <cfif structKeyExists(url, "strmsg")>
        <cfset strmymsg = url.strmsg />
    <cfelse>
        <cfset strmymsg = "all your base are belong to me" />
    </cfif>

    <cfset stFields = { "registration_ids": [ "#url.regid#" ], "data": {msg: "#strmymsg#"} }>
    
    <cfhttp url="https://android.googleapis.com/gcm/send" method="post" result="httpResp" timeout="60">
        <cfhttpparam type="header" name="Content-Type" value="application/json" />
        <cfhttpparam type="header" name="Authorization" value="key=***yourkeyfromgoogle***" />
        <cfhttpparam type="body" value="#serializeJSON(stFields)#">
    </cfhttp>
    
        
    <cfif httpResp.status_code eq 200>
        <span style="color:green;font-weight:bold;">Message Sent</span><br />
    </cfif>
</cfif>


<cfquery name="qGet" datasource="local">
    select * from gcmtest
</cfquery>

<form action="send.cfm" method="GET">
    <strong>Select Recipient:</strong>
    <br />
    
    <cfoutput query="qGet">
        <input type="radio" name="regid" value="#registrationid#">#strname#<br>
    </cfoutput>
    <br />
    
    <strong>Message:</strong><br />
    <input name="strmsg" type="text" /><br /><br />
    <input type="submit" value="submit" name="submit">
</form>

As you can see, we get everything from the gcmtest table and output it with radio buttons and a message box. When you hit send it self posts and the cfhttp takes over. For the cfhttp we use a json struct containing the registration id and the message. This we pass to the url https://android.googleapis.com/gcm/send obviously not forgetting to pass along our authorization key we created in step one.

That's it for the server side!

Step 3 - Android In Action

Now we need to have our Android app a) register with and b) receive messages from CGM. Now there is a lot of code here, so I'll post the project on gitHub but I'm hoping to cover the basics. There are obviously loads of things you can do with this message but for now I'm just going to use the basic Google demo method of posting a notification. To do this we register a WakefulBroadcastReceiver which will keep the device alive in case it hears a broadcast message. Then it will fire an intentService.

a) Manifest.xml
You need these permissions:

    <!-- GCM connects to Google Services. -->
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.GET_ACCOUNTS" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

    <permission android:name="com.google.android.gcm.demo.app.permission.C2D_MESSAGE" android:protectionLevel="signature" />
    <uses-permission android:name="com.google.android.gcm.demo.app.permission.C2D_MESSAGE" />
    <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />

You also need to register the services:


<receiver
      android:name=".GcmBroadcastReceiver"
      android:permission="com.google.android.c2dm.permission.SEND" >
          <intent-filter>
               <!-- Receives the actual messages. -->
               <action android:name="com.google.android.c2dm.intent.RECEIVE" />
               <category android:name="com.google.android.gcm.demo.app" />
          </intent-filter>
</receiver>
<service android:name=".GcmIntentService" />

Then what we're going to do in ActivityMain is check for the existence of a stored preference for the users name. If found we'll start a Fragment to welcome the user, if not we'll ask the user for their name.


FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
String strName = getNameFromPrefs();

if(strName.length() > 0){
 Bundle bundle = new Bundle();
 bundle.putString(FragmentWelcome.TAG_NAME, strName);
 FragmentWelcome frag = new FragmentWelcome();
 frag.setArguments(bundle);
 ft.replace(R.id.activity_default_fragment_container, frag, FragmentWelcome.class.getSimpleName());
}else{
 ft.replace(R.id.activity_default_fragment_container, new FragmentName(), FragmentName.class.getSimpleName());
}

ft.commit();

Now FragmentName is really simple and I shall spare you the details. It is a simple layout with an input text box. It checks for internet and validates the input and if so it calls a listener which returns to the main activity. The Activity saves the user's name in the savedPreferences and opens FragmentWelcome.

FragmentWelcome covers the following steps:
  • Get user's name via intent
  • verify GooglePlayServices
  • Gets your GCM Sender ID (created in step 1). Store this in strings.xml or somewhere sensible.
  • Using your GCM Sender ID it posts to Google Play Services and gets your device id.
        private void registerInBackground() {
            new AsyncTask<String, Void, String>() {
                @Override
                protected String doInBackground(String... params) {
                    String msg = "";
                    String name = "";
                    try {
                        if (gcm == null) {
                            gcm = GoogleCloudMessaging.getInstance(context);
                        }
                        regid = gcm.register(SENDER_ID);
    

  • Gets the URL of the receieve.cfm file we created in step 2 (again store it in strings or prefs) and appends the user's name and the device id to the url.
  • Call an AsyncTask to open DefaultHttpClient and hit the url we have now created.
        private class UploadRegistrationID extends AsyncTask<String, Void, String> {
            @Override
            protected String doInBackground(String... urls) {
                String response = "";
                for (String url : urls) {
                    DefaultHttpClient client = new DefaultHttpClient();
                    HttpGet httpGet = new HttpGet(url);
                    try {
                        HttpResponse execute = client.execute(httpGet);
                        InputStream content = execute.getEntity().getContent();
        
                        BufferedReader buffer = new BufferedReader(new InputStreamReader(content));
                        String s = "ServerResponse:";
                        while ((s = buffer.readLine()) != null) {
                            response += s;
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
                return response;
            }
    

That's it. Once you've run your Android app and entered your name you should be able to go to your send.cfm file in a web browser and send a message through to your device. The message will then pop up in your notification window!

Here's the Android project on github:
https://github.com/jamessolo12/gcmPushDemo

Magic ;)






24 July 2013

Google Play Game Services


I've previously created my own custom built high scores add on for my Android app, frankly it was pretty rubbish. It was local only, not very pretty and had no public competition element. There are a few third party apps but they cost money. That is until at Google IO Google announced Google Play Game Services. I thought I'd take a look and I was incredibly impressed.

http://developer.android.com/google/play-services/games.html

Game Services allows you to instantly integrate a leaderboard into your app. It does this using Google + which means your users can share their scores with their circles, specific friends or of course with the public. I love this because just thinking about creating a user id for every user and a server side backend to deal with all that makes me shudder. Plus you can add in achievements which is pretty cool too.

So if you want to get started adding play services I very much recommend downloading Google's sample app "Type-a-Number":

https://developers.google.com/games/services/android/quickstart

Then run through the steps to get the API set-up on Google's developer console and of course provides a sample app and code to study. I really recommend you study this and get a good grasp on how it all works. I'm not going to run through all the code here, as Google have already done that. The same applies to the API, you do need to do a bit of setup on Google's developer console. Just make sure you copy the IDs of your game service and your leaderboard into your android project. Preferably in the same way Google suggest with an ids.xml file.

You're going to need to set-up eclipse and your project with Google Play Services Library which I already had for Google Maps anyway. The sample app uses the Base Game Utils library, which isn't totally necessary, but it does make things a little simpler. Again, all steps are covered in the quickstart.

The first thing is to get the Google Plus button working. This is a simple case of adding in the com.google.android.gms.common.SignInButton button to your layout file.
Then add a listener:

//Listener for Sign in
findViewById(R.id.sign_in_button).setOnClickListener(new View.OnClickListener() {
    public void onClick(View v) {
        beginUserInitiatedSignIn();
    }
});

Now beginUserInitiatedSignIn() is a function in Base Game Utils which deals with the Google + magic. It does have a callback function though which you can utilise, here's mine:

@Override
public void onSignInSucceeded() {
    Toast.makeText(this, "Signed in", Toast.LENGTH_SHORT).show();
    
    mShowSignIn = false;
    updateUi();

}

UpdateUi() handles hiding the sign in button and showing the sign out button.

Last but not least we need to push our scores.

getGamesClient().submitScore(getString(R.string.leaderboard_highscores),intScore);

It's spectacularly easy to do.

There are however a few things I've learnt:
  1. Be very wary of your certificate. You'll need to make sure the SHA1 hash you use is the same you export your Android project as the one you enter on the game services API.
  2. When you come to release this, if you've been using your debug certificate you'll need to delete your game services on the API and start it all over again. I've tried many times to tweak the SHA1 in an active game service but it always breaks. So best to start fresh with a production ready certificate and use it for the API as well.
  3. The IDs you get from play services must be copied exactly to your ids.xml file and your manifest must have a line like this:
    <meta-data android:name="com.google.android.gms.games.APP_ID" android:value="@string/app_id" />

  4. If you want to delete your scores or change the IDs you use for the game services or leaderboard you need to goto:
    Settings -> Google -> Google + -> Apps With Google + Sign In -> xx_AppName_xx -> Disconnect App
    You'll get the option here to delete your scores. This can be a bit temperamental, so change IDs only if you're really stuck.

One last thing worth mentioning, and its addressed to Google, whom I'm sure study my blog every day! When one looks at the achievements board, you can't expand the achievement to get the full description of what is required. On a phone, this means you'll never know what's required.

Thanks and good luck.

20 March 2013

Hamlet's Monkey - Part 3

I've previously blogged about my Hamlet's Monkey project.

Part 1 where I introduced the concept and did it in CFML:
http://webdeveloperpadawan.blogspot.co.uk/2012/10/hamlets-monkey-code-for-fun.html

Part 2 Where I ported the project into a java class with some JSON file read and writing:
http://webdeveloperpadawan.blogspot.co.uk/2013/03/hamlets-monkey-part-2.html

OK So now this whole project is moving toward where I was really excited to take it. GOOGLE! hahah I want to put this project on Google App Engine (GAE) so it uses cloud computing. That way a GAE cloud instance is like an actual monkey, tapping away at the keyboard and trying to re-write Hamlet! Awesome! This straight away introduces two potential problems:
  1. I've got to turn the project into a java servlet
  2. Tracking progress - In part 2 we added file storage, this won't work in the cloud, so we'll need to find a better method.


OK So converting the main crux of the method to a servlet isn't that complicated. The first thing you need to do is install the GAE plugin for eclipse. https://developers.google.com/appengine/docs/java/tools/eclipse Then we'll convert the project we made in Part 2 to a servlet, the public class needs to extend HTTPServlet:
public class Shakespearemonkey extends HttpServlet {

and the main method becomes doGet, which is what is called when the servlet responds to a http get request:
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException {


    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException {
        int            intCount                 = 0;
        String        strSubString            = "";
        String        strShakespeare            = Shakespeare.replaceAll("[^a-zA-Z]", ""); //Shakespeare without spaces etc
        String        strMonkeyString            = "";
        String        strBestSoFar            = "";
        response.setContentType("text/html");
        
        while (intCount < intNumLoops) {
            intCount++;
            
            //generate the random guess, one keystroke at a time
            strMonkeyString    = strMonkeyString + Character.toString(generateRandomLetter());
            
            strSubString    =    strShakespeare.substring(0,strMonkeyString.length());
            
            //check if we've guessed correctly so far
            if(strMonkeyString.equalsIgnoreCase(strSubString)){
                //Is this our best guess so far
                if(strSubString.length() > strBestSoFar.length()){
                    strBestSoFar    =    strSubString;
                }
            }else{
                //incorrect guess, start again
                strMonkeyString    = "";
            }
        }
        
        trackProgress(intCount,strBestSoFar);
        
        //purely for output, re-read the latest and update user on progress
        try {
            response.getWriter().println("Good Morning, I am your monkey! I will be trying to guess the string: " + strShakespeare + "<br />");
            MonkeyResults monkeyresults    = readResultsFromFile();
            response.getWriter().println("My best guess so far is: ");
            response.getWriter().println((String) monkeyresults.getBestGuess());
            response.getWriter().println("<br />I have made ");
            response.getWriter().println((int) monkeyresults.getKeyStrokes());
            response.getWriter().println("keystrokes");
        } catch (IOException e) {
            e.printStackTrace();
        }
        
    }


So what I've done is created an object for storing all our progress data. Possibly not necessary / overkill but its OO and it feels good. It's just two getters and setters, so I won't bore you with the code. What is different in the trackProgress function though is I've replaced the JSON code with this:
        MonkeyResults        monkeyResults            = readResultsFromFile();
        
        if(monkeyResults.getKeyStrokes() != 0 && monkeyResults.getBestGuess() != ""){
            strBestGuessFromFile    = (String) monkeyResults.getBestGuess();
            intKeyStrokesFromFile    = (int) monkeyResults.getKeyStrokes();
        }


So I'm going to use the GAE datastore to keep track of our progress. The first step is writing to the datastore:

    static Key            theResultsKey    = KeyFactory.createKey("Results","tblMonkeyResults");
    DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
    

    
    private void writeResultsToFile(int intKeyStrokes, String strBestGuess){
        //I write the number of keystrokes and the best guess so far to the datastore
        Entity objDaoOut = new Entity("Results", "tblMonkeyResults");
        
        objDaoOut.setProperty("intKeyStrokes",intKeyStrokes);
        objDaoOut.setProperty("strBestGuess", strBestGuess);

        datastore.put(objDaoOut);
    }
The next of course is the new read function, which returns an implementation of our local object:
    private MonkeyResults readResultsFromFile() {
        //I read results from the datastore and return an instance of class MonkeyResults
        MonkeyResults monkeyResults    = new MonkeyResults();
        long intStrokes    = 0;
        String strGuess    = "";
        
        try{
            Entity objDaoIn = datastore.get(theResultsKey);
        
            intStrokes    = (long) objDaoIn.getProperty("intKeyStrokes");
            strGuess    = (String) objDaoIn.getProperty("strBestGuess");
        }catch(EntityNotFoundException e){
            //e.printStackTrace();
        }finally{
            monkeyResults.setKeyStrokes((int)intStrokes);
            monkeyResults.setBestGuess(strGuess);
        }
        
        return monkeyResults;
    }
That's basically it. We have converted our java function to a Java Servlet and modified the file read and write to use the Google Datastore. Simples.

Once you're done, you should be able to test it locally, if it works you right click on the project and goto Google -> Deploy to App Engine. Hey presto google uploads it all for you and you should have a successfully running monkey!

Here's my monkey: http://shakespearmonkey.appspot.com/ http://pastebin.com/1etNP1ge

24 September 2009

Open Blue Draggon and Google App Engine

Hey Guys,

Anyone following cloud computing and coldFusion should absolutely check out some amazing work by a friend of mine Paul Kukiel:

http://blog.kukiel.net/2009/09/coldfusion-on-google-app-engine-with.html

He's built on some great work Google / Blue Dragon have been doing with porting a basic cloud computing style java cf instance (open Blue Dragon) onto Google app engine. It looks like it's work in progress but it's super exciting to see.

You can even try it yourself, go on it's easier than you think!