com.javadog.cgeowear.MobileService.java Source code

Java tutorial

Introduction

Here is the source code for com.javadog.cgeowear.MobileService.java

Source

/*
   Copyright 2014 Cullin Moran
   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.javadog.cgeowear;

import android.app.Notification;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.location.Location;
import android.os.Bundle;
import android.os.Handler;
import android.os.Handler.Callback;
import android.os.IBinder;
import android.os.Message;
import android.preference.PreferenceManager;
import android.support.v4.app.NotificationCompat;
import android.support.v4.content.LocalBroadcastManager;
import android.util.Log;
import android.widget.Toast;

import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.location.LocationServices;
import com.google.android.gms.wearable.Node;
import com.google.android.gms.wearable.NodeApi;
import com.google.android.gms.wearable.Wearable;
import com.javadog.LocationUtils.LocationUtils;
import com.javadog.WearMessageDataset.MessageDataset;

import java.net.ConnectException;
import java.util.HashSet;
import java.util.NoSuchElementException;

public class MobileService extends Service
        implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener {
    public static final String DEBUG_TAG = "com.javadog.cgeowear";

    private static final String INTENT_INIT = "cgeo.geocaching.wear.NAVIGATE_TO";
    private static final String INTENT_STOP = "com.javadog.cgeowear.STOP_APP";

    private static final String MESSAGE_NAVIGATING_NOW = "Navigating on Android Wear";
    private static final String MESSAGE_NO_WEAR_DEVICE = "No Android Wear device paired!";
    private static final String MESSAGE_ERROR_COMMUNICATING = "Error communicating with Wear device. Contact support.";

    /**
     * Constants from c:geo's Android Wear API.
     */
    private static final String PREFIX = "cgeo.geocaching.intent.extra.";
    private static final String EXTRA_CACHE_NAME = PREFIX + "name";
    private static final String EXTRA_GEOCODE = PREFIX + "geocode";
    private static final String EXTRA_LATITUDE = PREFIX + "latitude";
    private static final String EXTRA_LONGITUDE = PREFIX + "longitude";

    private GoogleApiClient apiClient;
    private WearInterface wearInterface;
    private LocationUtils locationUtils;

    private String cacheName;
    private String geocode;
    private boolean useWatchCompass;
    private Location geocacheLocation;

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if (intent != null) {
            final String action = intent.getAction();
            if (INTENT_INIT.equals(action)) {
                cacheName = intent.getStringExtra(EXTRA_CACHE_NAME);
                geocode = intent.getStringExtra(EXTRA_GEOCODE);

                final double latitude = intent.getDoubleExtra(EXTRA_LATITUDE, 0d);
                final double longitude = intent.getDoubleExtra(EXTRA_LONGITUDE, 0d);

                geocacheLocation = new Location("c:geo");
                geocacheLocation.setLatitude(latitude);
                geocacheLocation.setLongitude(longitude);

                SharedPreferences userPrefs = PreferenceManager
                        .getDefaultSharedPreferences(getApplicationContext());
                useWatchCompass = userPrefs.getBoolean(MessageDataset.KEY_WATCH_COMPASS, true);
                setupLocationUtils(geocacheLocation);

                connectGoogleApiClient();
            }
        }
        return START_STICKY;
    }

    private void setupLocationUtils(Location geocacheLocation) {
        if (locationUtils != null) {
            locationUtils.stopListeningForUpdates();
        }

        locationUtils = new LocationUtils(geocacheLocation,

                new LocationUtils.OnDistanceUpdateListener() {
                    @Override
                    public void onDistanceUpdate(float newDistance) {
                        if (wearInterface != null) {
                            wearInterface.sendDistanceUpdate(newDistance);
                        }
                    }
                },

                // Don't set up listener for direction updates here if user wants to use watch compass;
                // that will be calculated watch-side.
                (useWatchCompass ? null : new LocationUtils.OnDirectionUpdateListener() {
                    @Override
                    public void onDirectionUpdate(float newDirection) {
                        wearInterface.sendDirectionUpdate(newDirection);
                    }
                }),

                // Only need this when using watch compass mode
                (useWatchCompass ? new LocationUtils.OnLocationUpdateListener() {
                    @Override
                    public void onLocationUpdate(Location newLocation) {
                        wearInterface.sendLocationUpdate(newLocation);
                    }
                } : null));
    }

    private void connectGoogleApiClient() {
        if (apiClient != null) {
            apiClient.unregisterConnectionCallbacks(this);
            apiClient.unregisterConnectionFailedListener(this);
            apiClient.disconnect();
        }
        apiClient = new GoogleApiClient.Builder(this, this, this).addApi(Wearable.API).addApi(LocationServices.API)
                .build();
        apiClient.connect();
    }

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

        //Register listener for INTENT_STOP events (from persistent notification)
        IntentFilter filter = new IntentFilter(INTENT_STOP);
        registerReceiver(intentReceiver, filter);

        //Register listener for when watch app closes
        IntentFilter localFilter = new IntentFilter(ListenerService.PATH_KILL_APP);
        LocalBroadcastManager.getInstance(this).registerReceiver(localReceiver, localFilter);

        //Show a persistent notification
        Intent stopServiceIntent = new Intent(INTENT_STOP);
        PendingIntent nIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, stopServiceIntent, 0);
        Notification notification = new NotificationCompat.Builder(getApplicationContext()).setOngoing(true)
                .setContentIntent(nIntent).setSmallIcon(R.drawable.ic_launcher)
                .setContentTitle(getText(R.string.app_name)).setContentText(getText(R.string.notification_text))
                .build();

        //Start service in foreground
        startForeground(R.string.app_name, notification);
    }

    /**
     * Handles INTENT_STOP event.
     */
    BroadcastReceiver intentReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (INTENT_STOP.equals(action)) {
                Log.d(DEBUG_TAG, "Received 'stop' intent; stopping phone service.");
                stopSelf();
            }
        }
    };

    /**
     * Handles broadcast originated by watch app closing; stops this service.
     */
    BroadcastReceiver localReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (ListenerService.PATH_KILL_APP.equals(action)) {
                Log.d(DEBUG_TAG, "Watch app closed; stopping phone service.");
                stopSelf();
            }
        }
    };

    @Override
    public void onDestroy() {
        stopWearApp();
        stopForeground(true);
        apiClient.disconnect();

        //Stop listeners
        unregisterReceiver(intentReceiver);
        LocalBroadcastManager.getInstance(this).unregisterReceiver(localReceiver);
        locationUtils.stopListeningForUpdates();

        super.onDestroy();
    }

    /**
     * Stops the Android Wear counterpart to this app.
     */
    private void stopWearApp() {
        if (wearInterface != null) {
            wearInterface.sendKillRequest();
        }
    }

    final Handler initThreadHandler = new Handler(new Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            //Display any String received as a Toast; quit if there was a fatal error
            String message = (String) msg.obj;
            Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show();

            if (message.equals(MESSAGE_NO_WEAR_DEVICE) || message.equals(MESSAGE_ERROR_COMMUNICATING)) {
                stopSelf();
            }
            return true;
        }
    });

    @Override
    public void onConnected(Bundle bundle) {
        Log.d(DEBUG_TAG, "Connected to Play Services");

        // Start tracking all requested info on the phone side
        locationUtils.startLocationTracking(apiClient);
        if (!useWatchCompass) {
            locationUtils.startDirectionalTracking(getApplicationContext());
        }

        //Get ID of connected Wear device and send it the initial cache info (in another thread)
        new Thread(new Runnable() {
            @Override
            public void run() {
                HashSet<String> connectedWearDevices = new HashSet<>();
                NodeApi.GetConnectedNodesResult nodes = Wearable.NodeApi.getConnectedNodes(apiClient).await();
                for (Node node : nodes.getNodes()) {
                    connectedWearDevices.add(node.getId());
                }

                Message m = new Message();
                try {
                    wearInterface = new WearInterface(apiClient, connectedWearDevices.iterator().next());
                    wearInterface.initTracking(cacheName, geocode, 0f, 0f, useWatchCompass, geocacheLocation);
                    m.obj = MESSAGE_NAVIGATING_NOW;

                } catch (ConnectException e) {
                    Log.e(DEBUG_TAG, "Couldn't send initial tracking data.");
                    m.obj = MESSAGE_ERROR_COMMUNICATING;

                } catch (NoSuchElementException e) {
                    Log.e(DEBUG_TAG, "No Wear devices connected. Killing service...");
                    m.obj = MESSAGE_NO_WEAR_DEVICE;

                } finally {
                    initThreadHandler.sendMessage(m);
                }
            }
        }).start();
    }

    @Override
    public void onConnectionSuspended(int i) {
        Log.d(DEBUG_TAG, "Play Services connection suspended.");
    }

    @Override
    public void onConnectionFailed(ConnectionResult connectionResult) {
        Log.e(DEBUG_TAG, "Failed to connect to Google Play Services.");
    }

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