Showing posts with label Android Studio. Show all posts
Showing posts with label Android Studio. Show all posts

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);
        }
    }
}















19 February 2015

Android Studio Build Types

Android Studio, somewhat predictably, allows two deployment modes debug and release. Configuring this in gradle allows you to configure certain options like if we should use proguard or not and what signing config to use. However this can be taken further to allow customization of certain java files based on release or debug build.


I’m not talking here about flavours, which is something slightly different. What I want to do is use one java class for debug and a different one for a release build. This allows me to suppress some debugging functions on a release build.

1. Create a Simple Project

First I create a new project and quickly setup a simple MainActivity with a button to launch a SecondActivity. I’m going to keep these activities very simple just to prove the concept. Don’t create the SecondActivity yet, we’ll do it in the next step.

2. Create Build Types

First update your app/build.gradle file to reflect the following, note the buildTypes:


apply plugin: 'com.android.application'

android {
    compileSdkVersion 21
    buildToolsVersion "21.1.2"

    defaultConfig {
        applicationId "com.example.android.myapplication"
        minSdkVersion 10
        targetSdkVersion 21
        versionCode 1
        versionName "1.0"
    }

    signingConfigs {
        debug {
            storeFile file("--path-to-debug-keystore--")
        }
        release {
            storeFile file("--path-to-release-keystore--")
            storePassword "--password--"
            keyAlias "--alias--"
            keyPassword "--password--"
        }
    }

    buildTypes {
        release {
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
            signingConfig signingConfigs.release
        }
        debug {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
            signingConfig signingConfigs.debug
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:21.0.3'
}

Note I’ve included a release keystore here with password and alias password. This is obviously not recommended for a real app. You would ideally release using the “Generate Signed APK…” However I’ve done it in this example as it allows me to quickly release using the build button.

3. Create folder structure

Create release and debug folders inside src. Inside both create a java folder, one (normally debug) will go blue and allow you to create a package. Create a new package matching your default package. In the other (normally release) you’ll have to just create folders instead of a package. Now create a SecondActivity.java in both. As the image below shows, you'll never get both release and debug to be correctly marked as a package. However when you switch using the build variants tab, you'll see the currently selected one change.



4. Test

Now you’re setup to configure your second activity as you wish, in my example I had each update a textview to say debug or release. Open the Build Variants tab in Android Studio and switch between build variants. This allows you to toggle the modes and release as such.

22 January 2015

Google App Engine and Android playing nice.

I'm a big fan of cloud services, especially when they give you a free or basic quota such as Google App Engine which you can use for development or testing. I’ve done quite a bit of work with Google App Engine before but not in a while. It became a real heavy beast involving importing add ons to Eclipse and configuring a multitude of environment settings.

However recently I've moved to Android Studio and apparently Google Cloud support is built in. Learning this I was then inspired by a recent post on the Android Developers Blog:
http://android-developers.blogspot.co.uk/2014/12/build-mobile-app-services-with-google.html

I felt this wasn't a great tutorial, there were large sections left out and it basically didn't work nearly as easily as I’d hoped it would. Sorry Android Developers, but not your best work. Still the steps to get started were simple enough:
  1. Create a super simple Android app. Basic Hello world stuff.
  2. Give your app Internet permissions
  3. Create a new Google Cloud Module
    • File -> New Module
    • Click “Google Cloud Module”
    • Select App Engine Java Endpoints Module
Android studio will now create a GAE backend module for you and automatically tie it into your Android app. Now you need a bean and an endpoint.

MyBean.java
package com.example.myapplication.backend;

public class MyBean {

    private String myData;

    public String getData() {
        return myData;
    }

    public void setData(String data) {
        myData = data;
    }
}


MyEndpoint.java
package com.example.myapplication.backend;

import com.google.api.server.spi.config.Api;
import com.google.api.server.spi.config.ApiMethod;
import com.google.api.server.spi.config.ApiNamespace;

import javax.inject.Named;

/**
 * An endpoint class we are exposing
 */
@Api(name = "myApi", version = "v1", namespace = @ApiNamespace(ownerDomain = "backend.myapplication.example.com", ownerName = "backend.myapplication.example.com", packagePath = ""))
public class MyEndpoint {

    /**
     * A simple endpoint method that takes a name and says Hi back
     */
    @ApiMethod(name = "sayHi")
    public MyBean sayHi(@Named("name") String name) {
        MyBean response = new MyBean();
        response.setData("Hi, " + name);

        return response;
    }

}
and here’s the Gradle file:

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'com.google.appengine:gradle-appengine-plugin:1.9.14'
    }
}

repositories {
    mavenCentral();
}

apply plugin: 'java'
apply plugin: 'war'
apply plugin: 'appengine'

sourceCompatibility = JavaVersion.VERSION_1_7
targetCompatibility = JavaVersion.VERSION_1_7

dependencies {
    appengineSdk 'com.google.appengine:appengine-java-sdk:1.9.14'
    compile 'com.google.appengine:appengine-endpoints:1.9.14'
    compile 'com.google.appengine:appengine-endpoints-deps:1.9.14'
    compile 'javax.servlet:servlet-api:2.5'
}

appengine {
    downloadSdk = true
    appcfg {
        oauth2 = true
    }
    endpoints {
        getClientLibsOnBuild = true
        getDiscoveryDocsOnBuild = true
    }
}


Frustratingly you’ll get red errors all over the place. Cannot resolve symbol api. This really winds me up and I’ve not yet figured out how to fix it. However it does build and run with these problems, so not really an error!

If you change the run drop down to “backend” and hit run hopefully this will all compile and you’ll get an Android Studio message with a localhost url. This is Android Studio setting up a local version of Google App Engine and using Jetty to host it. You should see a url output which you can copy to your browser, something like:
http://localhost:8080/

Hitting this url you should get an index file saying Hello Endpoints or something with a nice pretty bootstrap wrapper. You can enter something into the text field and your GAE application will say Hi to you.

Now we need to plug this into our Android app. First we need a new AsyncTask

package tester.example.com.myapplication;

import android.content.Context;
import android.os.AsyncTask;
import android.support.v4.util.Pair;
import android.widget.Toast;

import com.example.myapplication.backend.myApi.MyApi;

import com.google.api.client.extensions.android.http.AndroidHttp;
import com.google.api.client.extensions.android.json.AndroidJsonFactory;
import com.google.api.client.googleapis.services.AbstractGoogleClientRequest;
import com.google.api.client.googleapis.services.GoogleClientRequestInitializer;


import java.io.IOException;

class EndpointsAsyncTask extends AsyncTask<Pair<Context, String>, Void, String> {
    private static MyApi myApiService = null;
    private Context context;

    @Override
    protected String doInBackground(Pair<Context, String>... params) {
        if(myApiService == null) {  // Only do this once
            MyApi.Builder builder = new MyApi.Builder(AndroidHttp.newCompatibleTransport(),
                    new AndroidJsonFactory(), null)
                    // options for running against local devappserver
                    // - 10.0.2.2 is localhost's IP address in Android emulator
                    // - turn off compression when running against local devappserver
                    .setRootUrl("http://10.0.2.2:8080/_ah/api/")
                    .setGoogleClientRequestInitializer(new GoogleClientRequestInitializer() {
                        @Override
                        public void initialize(AbstractGoogleClientRequest<?> abstractGoogleClientRequest) throws IOException {
                            abstractGoogleClientRequest.setDisableGZipContent(true);
                        }
                    });
            // end options for devappserver

            myApiService = builder.build();
        }

        context = params[0].first;
        String name = params[0].second;

        try {
            return myApiService.sayHi(name).execute().getData();
        } catch (IOException e) {
            return e.getMessage();
        }
    }

    @Override
    protected void onPostExecute(String result) {
        Toast.makeText(context, result, Toast.LENGTH_LONG).show();
    }
}
Now in your app activityMain or somewhere, fire a call to the AsyncTask:

new EndpointsAsyncTask().execute(new Pair(this, "Manfred"));

The dependencies in your Android app should look like this:

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:21.0.3'
    compile 'com.google.android.gms:play-services:6.1.71'
    compile project(path: ':backend', configuration: 'android-endpoints')
}

Now if you run your Android app in a Android Virtual Device, you should see your local server respond with a Hi Message.

This is a pretty basic example, but you get the idea and its a great first step on the path toward Android and Google Cloud playing well together.

20 November 2014

Android Studio Keyboard shortcuts


I've previously blogged about my frustrations moving from Eclipse to Android Studio. Largely this extends from a re-work of the keyboard mappings. I've used Eclipse for many years and have become very reliant on my keyboard short-cuts to speed up my work.

Recently I discovered the ability to make Android Studio run using Eclipse shortcuts. This literally changed my whole working day and has made me so happy!

Simply click File -> Settings then either scroll down to keymap or search for keymap.

In the dropdown just change the keyboard shortcuts to Eclipse:


Brilliant :D