com.hmsoft.weargoproremote.services.WearMessageHandlerService.java Source code

Java tutorial

Introduction

Here is the source code for com.hmsoft.weargoproremote.services.WearMessageHandlerService.java

Source

/*
 * Copyright (C) 2014 Mauricio Rodriguez (ranametal@users.sf.net)
 *
 * 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.hmsoft.weargoproremote.services;

import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.preference.PreferenceManager;
import android.support.v4.app.NotificationCompat.Builder;
import android.text.TextUtils;

import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.wearable.Node;
import com.google.android.gms.wearable.NodeApi;
import com.google.android.gms.wearable.Wearable;
import com.hmsoft.libcommon.constants.WearMessages;
import com.hmsoft.libcommon.general.Logger;
import com.hmsoft.libcommon.general.Timer;
import com.hmsoft.libcommon.general.Utils;
import com.hmsoft.libcommon.gopro.GoProController;
import com.hmsoft.libcommon.gopro.GoProStatus;
import com.hmsoft.weargoproremote.BuildConfig;
import com.hmsoft.weargoproremote.R;
import com.hmsoft.weargoproremote.helpers.WifiHelper;
import com.hmsoft.weargoproremote.ui.MobileMainActivity;

import java.io.File;

public class WearMessageHandlerService extends Service
        implements SharedPreferences.OnSharedPreferenceChangeListener, Handler.Callback {

    private static final String TAG = "WearMessageHandlerService";
    private static final int MESSAGE_INTENT = 1;

    public static final String ACTION_STOP = BuildConfig.APPLICATION_ID + ".ACTION_STOP";
    public static final String ACTION_HANDLE_MESSAGE_FROM_WEAR = BuildConfig.APPLICATION_ID
            + ".HANDLE_MESSAGE_FROM_WEAR";
    public static final String ACTION_SEND_MESSAGE_TO_WEAR = BuildConfig.APPLICATION_ID + ".SEND_MESSAGE_TO_WEAR";
    //public static final String ACTION_LONG_TASK = BuildConfig.APPLICATION_ID + ".LONG_TASK";
    //public static final String EXTRA_TASK = BuildConfig.APPLICATION_ID + ".EXTRA_TASK";
    public static final String EXTRA_MESSAGE = BuildConfig.APPLICATION_ID + ".EXTRA_MESSAGE";
    public static final String EXTRA_DATA = BuildConfig.APPLICATION_ID + ".EXTRA_DATA";
    public static final String EXTRA_DONT_SEND_STOP_TO_WEAR = BuildConfig.APPLICATION_ID
            + ".DONT_SEND_STOP_TO_WEAR";

    private static volatile Integer sCurrentWiFiNetworkId = null;

    private Builder mNotificationBuilder;
    private volatile boolean mPreviewEnabled;
    private volatile GoProController mGoProController;
    private volatile GoogleApiClient mGoogleApiClient;
    private volatile Node mWearableNode = null;
    private volatile String mPass;
    private volatile SharedPreferences mPrefs;

    private volatile Looper mExecutorLooper;
    private volatile Handler mWorkHandler;
    //private volatile Looper mLongTaskLooper;
    //private volatile Handler mLongTaskHandler;

    private Timer mSendStatusTimer;
    private String mCameraName;
    volatile boolean mStopped;
    private File mThumbsCacheDirectoryFile;

    @Override
    public void onCreate() {
        super.onCreate();
        if (Logger.DEBUG)
            Logger.debug(TAG, "Service created");

        HandlerThread executorThread = new HandlerThread("WearMessageHandlerService");
        executorThread.start();
        mExecutorLooper = executorThread.getLooper();
        mWorkHandler = new Handler(mExecutorLooper, this);

        mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
        mPass = mPrefs.getString(getString(R.string.preference_wifi_password_key), "");
        mPreviewEnabled = mPrefs.getBoolean(getString(R.string.preference_watch_preview_enabled_key), true);
        mGoProController = GoProController.getDefaultInstance(mPass);
        mPrefs.registerOnSharedPreferenceChangeListener(this);

        mSendStatusTimer = new Timer(60000, mWorkHandler, new Timer.TimerTask() {
            @Override
            public void onTick(int ticks) {
                sendCameraStatus();
                if (BuildConfig.DEBUG)
                    Logger.debug(TAG, "Status sent...");
            }
        });

        updateNotification(getString(R.string.status_connection_starting));
    }

    @Override
    public void onDestroy() {
        mSendStatusTimer.stop();
        mPrefs.unregisterOnSharedPreferenceChangeListener(this);
        mExecutorLooper.quit();
        disconnectGoogleApiClient();
        stopForeground(true);
        GoProController.clear();
        if (Logger.DEBUG)
            Logger.debug(TAG, "Service destroyed");
        super.onDestroy();
    }

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

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if (intent != null) {
            final String action = intent.getAction();

            if (ACTION_STOP.equals(action)) {
                mStopped = true;
                mWorkHandler.removeCallbacksAndMessages(null);
                mSendStatusTimer.stop();
                updateNotification(getString(R.string.status_connection_disconnecting));
            }

            Message msg = mWorkHandler.obtainMessage(MESSAGE_INTENT, intent);
            mWorkHandler.sendMessage(msg);
        }
        return START_STICKY;
    }

    @Override
    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
        if (getString(R.string.preference_wifi_password_key).equals(key)) {
            mPass = sharedPreferences.getString(key, "");
            mGoProController = GoProController.getDefaultInstance(mPass);
        } else {
            mPreviewEnabled = mPrefs.getBoolean(getString(R.string.preference_watch_preview_enabled_key), true);
        }
    }

    /*private void longTaskMessage(int task) {
    if(mLongTaskLooper == null) {
        HandlerThread longTaskThread = new HandlerThread("longTaskThread");
        longTaskThread.start();
        mLongTaskLooper = longTaskThread.getLooper();
        mLongTaskHandler = new Handler(mLongTaskLooper, this);
    }
    Intent i = new Intent(ACTION_LONG_TASK);
    i.putExtra(EXTRA_TASK, task);
        
    Message msg = mLongTaskHandler.obtainMessage(MESSAGE_INTENT, i);
    mLongTaskHandler.sendMessage(msg);
    }*/

    private File getThumbsCacheDirectoryFile() {
        if (mThumbsCacheDirectoryFile == null) {
            mThumbsCacheDirectoryFile = new File(getExternalCacheDir(), "thumbs");
        }
        return mThumbsCacheDirectoryFile;
    }

    void updateNotification(String contentText) {
        if (mNotificationBuilder == null) {
            Context context = getApplicationContext();
            Intent activityIntent = new Intent(context, MobileMainActivity.class);
            activityIntent.setAction(Intent.ACTION_MAIN);
            activityIntent.addCategory(Intent.CATEGORY_LAUNCHER);
            activityIntent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION | Intent.FLAG_ACTIVITY_NEW_TASK);

            Intent stopIntent = new Intent(context, WearMessageHandlerService.class);
            stopIntent.setAction(ACTION_STOP);

            mNotificationBuilder = (new Builder(this)).setSmallIcon(R.drawable.icon_notification)
                    .setContentTitle(getString(R.string.notification_tittle))
                    .setContentIntent(PendingIntent.getActivity(context, 0, activityIntent, 0)).setLocalOnly(true)
                    .addAction(R.drawable.icon_power, getString(R.string.action_stop),
                            PendingIntent.getService(context, 0, stopIntent, 0));
        }

        mNotificationBuilder.setContentText(contentText);
        startForeground(1, mNotificationBuilder.build());
    }

    private boolean connectGoogleApiClient() {
        if (mGoogleApiClient == null) {
            mGoogleApiClient = new GoogleApiClient.Builder(this).addApi(Wearable.API).build();
        }

        if (!mGoogleApiClient.isConnected() || !mGoogleApiClient.isConnecting()) {
            mGoogleApiClient.blockingConnect();
        }

        return mGoogleApiClient.isConnected();
    }

    private void disconnectGoogleApiClient() {
        if (mGoogleApiClient != null && mGoogleApiClient.isConnected()) {
            mGoogleApiClient.disconnect();
            mGoogleApiClient = null;
            if (Logger.DEBUG)
                Logger.debug(TAG, "GAPI client disconnected.");
        }
    }

    private boolean findWearableNode() {
        if (mGoogleApiClient != null && mWearableNode == null) {
            NodeApi.GetConnectedNodesResult result = Wearable.NodeApi.getConnectedNodes(mGoogleApiClient).await();
            if (result.getNodes().size() > 0) {
                mWearableNode = result.getNodes().get(0);
                if (BuildConfig.DEBUG) {
                    Logger.debug(TAG, "Found wearable: name=" + mWearableNode.getDisplayName() + ", id="
                            + mWearableNode.getId());
                }
            } else {
                mWearableNode = null;
            }
        }
        return mWearableNode != null;
    }

    private void sendToWearable(String path, String param, byte[] data) {
        sendToWearable(WearMessages.addParamToMessage(path, param), data);
    }

    private void sendToWearable(String path, byte[] data) {

        if (!connectGoogleApiClient()) {
            updateNotification(getString(R.string.status_googleapi_error));
            Logger.error(TAG, "Failed to connect GoogleApiClient");
            return;
        }

        if (!findWearableNode()) {
            updateNotification(getString(R.string.status_no_wearable_node));
            Logger.error(TAG, "Failed to find wearable node");
            return;
        }

        if (Logger.DEBUG)
            Logger.debug(TAG, "Send to wearable:" + path);

        Wearable.MessageApi.sendMessage(mGoogleApiClient, mWearableNode.getId(), path, data).await();
    }

    void sendCameraStatus() {
        sendCameraStatus(0);
    }

    private void sendCameraStatus(long time2Wait) {
        if (time2Wait > 0)
            Utils.sleep(time2Wait);

        byte[] status = mGoProController.getRawStatus();
        sendToWearable(WearMessages.MESSAGE_CAMERA_STATUS, status);
        byte mode = status != null && status.length > GoProStatus.Fields.CAMERA_MODE_FIELD
                ? status[GoProStatus.Fields.CAMERA_MODE_FIELD]
                : GoProStatus.UNKNOW;

        String text;
        if (mode == GoProStatus.UNKNOW) {
            text = getString(R.string.status_connection_failed);
        } else if (mode == GoProStatus.CAMERA_MODE_OFF_WIFION) {
            text = getString(R.string.status_connection_camera_off);
        } else {
            if (TextUtils.isEmpty(mCameraName)) {
                text = getString(R.string.status_connection_connected1);
            } else {
                text = getString(R.string.status_connection_connected, mCameraName);
            }
        }
        updateNotification(text);
    }

    private byte setCameraMode(byte newMode) {
        byte cameraMode = GoProStatus.UNKNOW;
        byte[] status = mGoProController.getLastStatus();
        if (status != null && status.length > GoProStatus.Fields.CAMERA_MODE_FIELD) {
            cameraMode = status[GoProStatus.Fields.CAMERA_MODE_FIELD];
        }
        if (cameraMode <= GoProStatus.UNKNOW) {
            return GoProStatus.UNKNOW;
        }
        if (newMode < 0 || cameraMode == newMode) {
            return cameraMode;
        }
        if (mGoProController.setCameraMode(newMode)) {
            Utils.sleep(750);
            return newMode;
        }
        return GoProStatus.UNKNOW;
    }

    private boolean sendThumbsToWatch(int start, int end, boolean forceSync) {
        if (forceSync) {
            mGoProController.refreshMediaCachedList();
        }

        File cacheDir = getThumbsCacheDirectoryFile();
        mGoProController.setThumbnailCacheDirectory(cacheDir);
        boolean send = false;
        for (int c = start; c <= end; c++) {
            String fileName = mGoProController.getMediaFileNameAtReverseIndex(c);
            if (!TextUtils.isEmpty(fileName) && (forceSync || !(new File(cacheDir, fileName)).exists())) {
                byte[] thumb = mGoProController.getImgThumbnail(fileName);
                if (thumb != null) {
                    String name = GoProController.Thumbnail.getWearName(fileName);
                    sendToWearable(WearMessages.MESSAGE_SYNC_THUMB, name, thumb);
                    send = true;
                    if (Logger.DEBUG)
                        Logger.debug(TAG, "Synced thumb: %s", fileName);
                }
            }
        }
        return send;
    }

    @Override
    public boolean handleMessage(Message msg) {
        Intent intent = (Intent) msg.obj;

        final String action = intent.getAction();
        final String message = intent.getStringExtra(EXTRA_MESSAGE);
        final byte[] data = intent.getByteArrayExtra(EXTRA_DATA);

        long startTime = System.currentTimeMillis();

        switch (action) {
        case ACTION_HANDLE_MESSAGE_FROM_WEAR:
            if (!mStopped)
                handleMessage(message, data);
            break;
        case ACTION_SEND_MESSAGE_TO_WEAR:
            sendToWearable(message, data);
            break;
        case ACTION_STOP:
            boolean sendStopMsg = !intent.getBooleanExtra(EXTRA_DONT_SEND_STOP_TO_WEAR, false);
            if (sendStopMsg)
                sendToWearable(WearMessages.MESSAGE_STOP, null);
            handleMessage(WearMessages.MESSAGE_DISCONNECT, null);
            break;
        }

        if (BuildConfig.DEBUG) {
            long time = System.currentTimeMillis() - startTime;
            Logger.debug(TAG, "Message %s handled in %ds (%d)", message, time / 1000, time);
        }

        return true;
    }

    /*private void handleLongTask(int task) {
    }*/

    private void handleMessage(String message, byte[] data) {

        long waitTime = 0;
        byte param = WearMessages.PARAM_UNKNOW;
        if (data != null && data.length > 0) {
            param = data[0];
        }

        switch (message) {
        case WearMessages.MESSAGE_CONNECT:
            updateNotification(getString(R.string.status_connection_connecting));

            String ssid = mPrefs.getString(getString(R.string.preference_wifi_name_key), "");
            if (TextUtils.isEmpty(ssid) || TextUtils.isEmpty(mPass)) {
                byte[] status = new byte[GoProStatus.RAW_STATUS_LEN];
                status[GoProStatus.Fields.CAMERA_MODE_FIELD] = GoProStatus.CAMERA_MODE_NO_CONFIG;
                sendToWearable(WearMessages.MESSAGE_CAMERA_STATUS, status);
                updateNotification(getString(R.string.status_connection_noconfig));
                break;
            }

            if (sCurrentWiFiNetworkId == null) {
                sCurrentWiFiNetworkId = WifiHelper.getCurrentWiFiNetworkId(getApplicationContext());
            }
            WifiHelper.connectToWifi(getApplicationContext(), ssid, mPass);

            if (GoProStatus.LastCameraStatus != null
                    && GoProStatus.LastCameraStatus.length == GoProStatus.RAW_STATUS_LEN
                    && GoProStatus.LastCameraStatus[GoProStatus.Fields.CAMERA_MODE_FIELD] == GoProStatus.CAMERA_MODE_OFF_WIFION) {
                if (mGoProController.turnOn()) {
                    Utils.sleep(5000);
                }
            }
            byte[] name = mGoProController.getRawCameraName();
            if (name != null) {
                mCameraName = GoProStatus.getCameraName(new String(name));
                sendToWearable(WearMessages.MESSAGE_CAMERA_NAME, name);
            }

            sendCameraStatus();

            if (name != null) {
                sendThumbsToWatch(1, 50, false);
            }
            mSendStatusTimer.start();
            break;

        case WearMessages.MESSAGE_DISCONNECT:
            updateNotification(getString(R.string.status_connection_disconnecting));
            if (sCurrentWiFiNetworkId != null) {
                boolean restoreNet = mPrefs.getBoolean(getString(R.string.preference_wifi_restore_network_key),
                        true);
                if (restoreNet) {
                    WifiHelper.connectToWifi(getApplicationContext(), sCurrentWiFiNetworkId);
                }
                sCurrentWiFiNetworkId = null;
            }
            stopForeground(true);
            stopSelf();
            break;

        case WearMessages.MESSAGE_SHUTTER:
            byte mode = setCameraMode(param);
            if (mode != GoProStatus.UNKNOW) {
                boolean isRecording = false;
                byte[] status = mGoProController.getLastStatus();
                if (status != null && status.length > GoProStatus.Fields.IS_RECORDING_FIELD) {
                    isRecording = status[GoProStatus.Fields.IS_RECORDING_FIELD] != 0;
                }
                if (mGoProController.shutter(!isRecording)) {
                    waitTime = isRecording ? 1550 : 1250;
                    if (mPreviewEnabled && (mode == GoProStatus.CAMERA_MODE_PHOTO
                            || mode == GoProStatus.CAMERA_MODE_BURST || isRecording)) {
                        waitTime = 1000;
                        if (mode == GoProStatus.CAMERA_MODE_BURST) {
                            waitTime = 1350;
                        }
                        Utils.sleep(waitTime);
                        mGoProController.setThumbnailCacheDirectory(getThumbsCacheDirectoryFile());
                        GoProController.Thumbnail thumb = mGoProController.getLastImgThumbnail();
                        if (thumb != null) {
                            String thumbName = thumb.getWearName();
                            sendToWearable(WearMessages.MESSAGE_LAST_IMG_THUMB, thumbName, thumb.BitmapData);
                        }
                        waitTime = 250;
                        sendThumbsToWatch(1, 10, false);
                    }
                }
            }
            sendCameraStatus(waitTime);
            break;

        case WearMessages.MESSAGE_SET_CAMERA_MODE:
            if (setCameraMode(param) != GoProStatus.UNKNOW) {
                waitTime = 200;
            }
            sendCameraStatus(waitTime);
            break;

        case WearMessages.MESSAGE_SET_VIDEO_MODE:
            if (mGoProController.setVideoMode(param)) {
                waitTime = 1100;
            }
            sendCameraStatus(waitTime);
            break;

        case WearMessages.MESSAGE_SET_TIMELAPSE_INTERVAL:
            if (mGoProController.setTimelapseInterval(param)) {
                waitTime = 1100;
            }
            sendCameraStatus(waitTime);
            break;

        case WearMessages.MESSAGE_CAMERA_STATUS:
            sendCameraStatus();
            break;

        case WearMessages.MESSAGE_SET_SPOTMETTER:
            if (mGoProController.setSpotMeter(param == 1)) {
                waitTime = 100;
            }
            sendCameraStatus(waitTime);
            break;

        case WearMessages.MESSAGE_SET_BEEP_VOLUME:
            if (mGoProController.setVolume(param)) {
                waitTime = 100;
            }
            sendCameraStatus(waitTime);
            break;

        case WearMessages.MESSAGE_SET_UPSIDEDOWN:
            if (mGoProController.setOrientationUpDown(param == 1)) {
                waitTime = 100;
            }
            sendCameraStatus(waitTime);
            break;

        case WearMessages.MESSAGE_SET_LEDS:
            if (mGoProController.setLeds(param)) {
                waitTime = 100;
            }
            sendCameraStatus(waitTime);
            break;

        case WearMessages.MESSAGE_SET_OSD:
            if (mGoProController.setOnScreenDisplay(param == 1)) {
                waitTime = 100;
            }
            sendCameraStatus(waitTime);
            break;

        case WearMessages.MESSAGE_SET_BEEPING:
            if (mGoProController.locate(param == 1)) {
                waitTime = 500;
            }
            sendCameraStatus(waitTime);
            break;

        case WearMessages.MESSAGE_POWEROFF:
            if (mGoProController.turnOff()) {
                waitTime = 500;
            }
            sendCameraStatus(waitTime);
            break;

        case WearMessages.MESSAGE_SYNC_THUMB:
            int start = 0;
            int end = 0;
            boolean forceSync = false;

            if (data != null) {
                start = data[0];
                end = data[1];
                forceSync = data[2] == 1;
            }

            boolean send = sendThumbsToWatch(start, end, forceSync);
            if (send) {
                sendToWearable(WearMessages.MESSAGE_SYNC_THUMB_FINISHED, null);
            }
            break;
        }
    }
}