by.zatta.pilight.connection.ConnectionService.java Source code

Java tutorial

Introduction

Here is the source code for by.zatta.pilight.connection.ConnectionService.java

Source

/******************************************************************************************
 * 
 * Copyright (C) 2013 Zatta
 * 
 * This file is part of pilight for android.
 * 
 * pilight for android is free software: you can redistribute it and/or modify 
 * it under the terms of the GNU General Public License as published by the 
 * Free Software Foundation, either version 3 of the License, or (at your option)
 * any later version.
 * 
 * pilight for android is distributed in the hope that it will be useful, but 
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 
 * for more details.
 * 
 * You should have received a copy of the GNU General Public License along 
 * with pilightfor android.
 * If not, see <http://www.gnu.org/licenses/>
 * 
 * Copyright (c) 2013 pilight project
 ********************************************************************************************/

package by.zatta.pilight.connection;

import android.annotation.SuppressLint;
import android.app.Notification;
import android.app.NotificationManager;
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.content.SharedPreferences.Editor;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.Parcelable;
import android.os.RemoteException;
import android.preference.PreferenceManager;
import android.util.Log;

import org.json.JSONException;
import org.json.JSONObject;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;

import by.zatta.pilight.MainActivity;
import by.zatta.pilight.R;
import by.zatta.pilight.model.Config;
import by.zatta.pilight.model.DeviceEntry;
import by.zatta.pilight.model.Origin;
import by.zatta.pilight.model.OriginEntry;

public class ConnectionService extends Service {

    public static NotificationType mCurrentNotif = NotificationType.DESTROYED;

    public static final int MSG_REGISTER_CLIENT = 1912376432;
    public static final int MSG_SET_BUNDLE = 1927364723;
    public static final int MSG_SET_STATUS = 1098378957;
    public static final int MSG_SWITCH_DEVICE = 1755547587;
    public static final int MSG_UNREGISTER_CLIENT = 1873487903;
    private static Context aCtx;
    private static Context ctx;

    public static List<Messenger> mClients = new ArrayList<Messenger>(); // Keeps track of all current registered clients (activities,
    // widget:))
    private static List<DeviceEntry> mDevices = new ArrayList<DeviceEntry>();
    private static NotificationManager mNotMan;
    private static Notification.Builder builder;
    private static HeartBeat mHB = null;
    private static long timeBeat = new Date().getTime();
    private static WriteMonitor mWM = null;
    private static final String TAG = "Zatta::ConnectionService";
    private static boolean isConnectionUp = false;
    private static boolean isDestroying;

    public enum NotificationType {
        CONNECTED, CONNECTING, DESTROYED, FAILED, LOST_CONNECTION, UPDATE,
    }

    private BroadcastReceiver mMessageReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            // Extract data included in the Intent
            String action = intent.getAction();
            // Log.v("receiver", "Got inent action: " + action);

            if (action.equals("pilight-kill-service")) {
                // Log.v(TAG, "kill recieved");
                stopSelf();
            } else if (action.equals("pilight-reconnect")) {
                // Log.v(TAG, "kill recieved");
                stopForeground(false);
                makeNotification(NotificationType.CONNECTING, aCtx.getString(R.string.noti_reconnect));
                if (isConnectionUp)
                    dropConnection();
                else
                    isConnectionUp = makeConnection();
            } else if (action.equals("pilight-switch-device")) {
                if (isConnectionUp)
                    Log.v(TAG, "broadcastReceiver: " + intent.getStringExtra("command"));
                Server.CONNECTION
                        .sentCommand("{\"message\":\"send\",\"code\":{" + intent.getStringExtra("command") + "}}");
            }
        }
    };
    private final Messenger mMessenger = new Messenger(new IncomingMessageHandler()); // Target we publish for clients to send messages to
    // IncomingHandler.

    /**
     * When binding to the service, we return an interface to our messenger for sending messages to the service.
     */
    @Override
    public IBinder onBind(Intent intent) {
        Log.v(TAG, "onBind");
        return mMessenger.getBinder();
    }

    @Override
    public void onRebind(Intent intent) {
        // Log.v(TAG, "onRebind");
        super.onRebind(intent);
    }

    @Override
    public boolean onUnbind(Intent intent) {
        // Log.v(TAG, "onUnbind");
        super.onUnbind(intent);
        return true;
    }

    @Override
    public void onCreate() {
        Log.v(TAG, "onCreate");
        super.onCreate();
        ctx = this;
        aCtx = getApplicationContext();

        SharedPreferences getPrefs = PreferenceManager.getDefaultSharedPreferences(getBaseContext());
        String language = getPrefs.getString("languagePref", "unknown");
        if (!language.equals("unknown"))
            makeLocale(language);

        IntentFilter filter = new IntentFilter();
        filter.addAction("pilight-reconnect");
        filter.addAction("pilight-kill-service");
        filter.addAction("pilight-switch-device");
        this.registerReceiver(mMessageReceiver, filter);

        mNotMan = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        isConnectionUp = makeConnection();
        isDestroying = false;
        Log.v(TAG, "onCreate done");

    }

    @Override
    public void onDestroy() {
        Log.v(TAG, "onDestroy");
        this.unregisterReceiver(mMessageReceiver);
        sendMessageToUI(MSG_SET_STATUS, NotificationType.DESTROYED.name());
        isDestroying = true;
        dropConnection();
        mNotMan.cancel(35);
        super.onDestroy();

    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        //??

        sendMessageToUI(MSG_SET_STATUS, mCurrentNotif.name());
        if (intent.hasExtra("command") && isConnectionUp) {
            Server.CONNECTION
                    .sentCommand("{\"message\":\"send\",\"code\":{" + intent.getStringExtra("command") + "}}");
            SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(aCtx);
            boolean useService = prefs.getBoolean("useService", true);
            if (!useService) {
                if (mWM == null || !mWM.isAlive()) {
                    mWM = new WriteMonitor();
                    mWM.setName("WriteMonitor");
                    mWM.start();
                }
            }
        }
        return START_STICKY;
    }

    public void makeLocale(String language) {
        Log.w(TAG, language + " makeLocale");
        Locale locale = new Locale(language);
        Locale.setDefault(locale);
        Configuration config = new Configuration();
        config.locale = locale;
        getBaseContext().getResources().updateConfiguration(config,
                getBaseContext().getResources().getDisplayMetrics());
    }

    private static boolean dropConnection() {
        Log.v(TAG, "dropConnection called");
        try {
            mHB.interrupt();
        } catch (Throwable e) {
            Log.w(TAG, "couldnt interrupt the heart-beat");
        }
        try {
            mWM.interrupt();
        } catch (Throwable e) {
            Log.w(TAG, "couldnt interrupt the WriteMonitor");
        }
        mHB = null;
        mWM = null;
        Server.CONNECTION.disconnect();
        isConnectionUp = false;
        return false;
    }

    private boolean makeConnection() {
        if (mCurrentNotif == NotificationType.DESTROYED)
            makeNotification(NotificationType.CONNECTING, aCtx.getString(R.string.noti_connecting));
        String serverString = Server.CONNECTION.setup();
        if (serverString.contains("{\"config\":")) {
            try {
                JSONObject json = new JSONObject(serverString);
                mDevices = Config.getDevices(json.getJSONObject("config"));
                Collections.sort(mDevices);
                addedToPreferences();
                makeNotification(NotificationType.CONNECTED, aCtx.getString(R.string.noti_connected));
                sendMessageToUI(MSG_SET_BUNDLE, null);
                startForeground(35, builder.build());
                if (mHB == null || !mHB.isAlive()) {
                    mHB = new HeartBeat();
                    mHB.setName("Heart-Beat");
                    mHB.start();
                }
                return true;

            } catch (JSONException e) {
                Log.w(TAG, "problems in JSONning");
                return false;
            }
        } else {
            makeNotification(NotificationType.FAILED, null);
            return false;
        }
    }

    private boolean addedToPreferences() {
        String currentNetwork = null;
        ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo info = cm.getActiveNetworkInfo();
        if (!(info == null)) {
            // Log.d(TAG, "networkInfo: " + info.getExtraInfo());
            currentNetwork = info.getExtraInfo();
        }
        WifiManager wifiManager = (WifiManager) ctx.getSystemService(Context.WIFI_SERVICE);
        WifiInfo wifiInfo = wifiManager.getConnectionInfo();
        if (!(wifiInfo == null)) {
            // Log.d(TAG, "wifiInfo:" + wifiInfo.getSSID());
            currentNetwork = wifiInfo.getSSID();
        }
        if (currentNetwork == null)
            return false;

        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(aCtx);
        String previous = prefs.getString("networks_known", "");
        // Log.d(TAG, previous);
        currentNetwork = currentNetwork.replace("\"", "");
        if (previous.contains(currentNetwork)) {
            // Log.d(TAG, previous + " did contain " + currentNetwork);
            return false;
        } else {
            previous = previous + "|&|" + currentNetwork;
            // Log.d(TAG, previous);
            Editor edit = prefs.edit();
            edit.putString("networks_known", previous);
            edit.commit();
            return true;
        }
    }

    public static void postUpdate(String update) {
        timeBeat = new Date().getTime();
        //Log.w(TAG, update);
        if (update.contains("origin\":\"config") && !(update.contains("cpu"))) {
            try {
                OriginEntry originEntry = Origin.getOriginEntry(new JSONObject(update));
                mDevices = Config.update(originEntry);
                update = Config.getLastUpdateString();
                Log.i(TAG, update.replace("\n", " "));
            } catch (JSONException e) {
                Log.w(TAG, "iets mis met het jsonObject?");
            }
            makeNotification(NotificationType.UPDATE, update);
            sendMessageToUI(MSG_SET_BUNDLE,
                    java.text.DateFormat.getTimeInstance().format(Calendar.getInstance().getTime()) + update);
        } else if (update.contains("LOST_CONNECTION") && !isDestroying) {
            makeNotification(NotificationType.LOST_CONNECTION, aCtx.getString(R.string.noti_lost));
            dropConnection();
            ctx.sendBroadcast(new Intent("pilight-reconnect"));
        }
    }

    private static Bitmap bigPic(int png) {
        Bitmap mBitmap = BitmapFactory.decodeResource(aCtx.getResources(), png);
        int height = (int) aCtx.getResources().getDimension(android.R.dimen.notification_large_icon_height);
        int width = (int) aCtx.getResources().getDimension(android.R.dimen.notification_large_icon_width);
        mBitmap = Bitmap.createScaledBitmap(mBitmap, width / 3, height / 3, false);
        return mBitmap;
    }

    private static void makeNotification(NotificationType type, String message) {
        Intent main;
        Intent kill;
        PendingIntent sentBroadcast;
        PendingIntent startMainActivity;
        PendingIntent killService;

        String myDate = java.text.DateFormat.getTimeInstance().format(Calendar.getInstance().getTime());

        //Log.v(TAG, "setting notification: " + type.name() + " while current = " + mCurrentNotif.name());
        if (type != mCurrentNotif) {
            //Log.v(TAG, "setting NEW notification: " + type.name());
            sendMessageToUI(MSG_SET_STATUS, type.name());
            switch (type) {
            case DESTROYED:
                builder = new Notification.Builder(ctx);
                builder.setSmallIcon(R.drawable.eye_black).setLargeIcon(bigPic(R.drawable.eye_black))
                        .setContentTitle(aCtx.getString(R.string.app_name)).setContentText(message);
                mCurrentNotif = NotificationType.DESTROYED;
                break;
            case CONNECTING:
                kill = new Intent("pilight-kill-service");
                killService = PendingIntent.getBroadcast(ctx, 0, kill, PendingIntent.FLAG_UPDATE_CURRENT);

                builder = new Notification.Builder(ctx).setContentTitle(aCtx.getString(R.string.app_name))
                        .setContentText(message).setDeleteIntent(killService).setSmallIcon(R.drawable.eye_trans)
                        .setLargeIcon(bigPic(R.drawable.eye_trans));
                mCurrentNotif = NotificationType.CONNECTING;
                break;
            case CONNECTED:
                main = new Intent(ctx, MainActivity.class);
                main.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
                startMainActivity = PendingIntent.getActivity(ctx, 0, main, PendingIntent.FLAG_UPDATE_CURRENT);

                builder = new Notification.Builder(ctx).setContentTitle(aCtx.getString(R.string.app_name))
                        .setContentText(message + "\n" + myDate).setContentIntent(startMainActivity)
                        .setSmallIcon(R.drawable.eye_white).setLargeIcon(bigPic(R.drawable.eye_white));
                mCurrentNotif = NotificationType.CONNECTED;
                break;
            case FAILED:
                main = new Intent("pilight-reconnect");
                sentBroadcast = PendingIntent.getBroadcast(ctx, 0, main, PendingIntent.FLAG_UPDATE_CURRENT);
                kill = new Intent("pilight-kill-service");
                killService = PendingIntent.getBroadcast(ctx, 0, kill, PendingIntent.FLAG_UPDATE_CURRENT);

                builder = new Notification.Builder(ctx).setContentTitle("pilight")
                        .setContentText(aCtx.getString(R.string.noti_failed)).setDeleteIntent(killService)
                        .addAction(R.drawable.action_refresh, aCtx.getString(R.string.noti_retry), sentBroadcast)
                        .setSmallIcon(R.drawable.eye_trans).setLargeIcon(bigPic(R.drawable.eye_trans));
                mCurrentNotif = NotificationType.FAILED;
                break;
            case LOST_CONNECTION:
                main = new Intent("pilight-reconnect");
                sentBroadcast = PendingIntent.getBroadcast(ctx, 0, main, PendingIntent.FLAG_UPDATE_CURRENT);
                kill = new Intent("pilight-kill-service");
                killService = PendingIntent.getBroadcast(ctx, 0, kill, PendingIntent.FLAG_UPDATE_CURRENT);

                builder = new Notification.Builder(ctx).setContentTitle(aCtx.getString(R.string.app_name))
                        .setContentText(message + "\n" + myDate).setDeleteIntent(killService)
                        .addAction(R.drawable.action_refresh, aCtx.getString(R.string.noti_retry), sentBroadcast)
                        .setSmallIcon(R.drawable.eye_trans).setLargeIcon(bigPic(R.drawable.eye_trans));
                mCurrentNotif = NotificationType.LOST_CONNECTION;
                break;
            case UPDATE:
                main = new Intent(ctx, MainActivity.class);
                main.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
                startMainActivity = PendingIntent.getActivity(ctx, 0, main, PendingIntent.FLAG_UPDATE_CURRENT);
                String[] title = message.split("\n");
                message = message.replace(title[0] + "\n", "");
                builder = new Notification.Builder(ctx).setContentTitle(title[0]).setContentText(message)
                        .setStyle(new Notification.BigTextStyle().bigText(message))
                        .setContentIntent(startMainActivity).setSmallIcon(R.drawable.eye_white)
                        .setLargeIcon(bigPic(R.drawable.eye_white));
                mCurrentNotif = NotificationType.UPDATE;
                break;
            default:
                break;
            }
        } else {
            if (message != null) {
                if (message.contains("Stamp")) {
                    String[] title = message.split("\n");
                    message = message.replace(title[0] + "\n", "");
                    builder.setContentTitle(title[0]).setStyle(new Notification.BigTextStyle().bigText(message));
                    builder.setContentText(message);
                } else {
                    builder.setContentTitle(myDate).setStyle(new Notification.BigTextStyle().bigText(message));
                    builder.setContentText(message);
                }
            }
        }
        mNotMan.notify(35, builder.build());
    }

    /**
     * Send the data to all clients.
     * 
     * @param message
     *            The message to send.
     */
    private static void sendMessageToUI(int what, String message) {
        // Log.v(TAG, "sent message called, clients attached: " + Integer.toString(mClients.size()));
        Iterator<Messenger> messengerIterator = mClients.iterator();
        while (messengerIterator.hasNext()) {
            Messenger messenger = messengerIterator.next();
            try {
                Bundle bundle = new Bundle();
                bundle.setClassLoader(aCtx.getClassLoader());
                switch (what) {
                case MSG_SET_STATUS:
                    // Log.v(TAG, "setting status: " + message);
                    bundle.putString("status", message);
                    Message msg_string = Message.obtain(null, MSG_SET_STATUS);
                    msg_string.setData(bundle);
                    messenger.send(msg_string);
                    break;
                case MSG_SET_BUNDLE:
                    if (!mDevices.isEmpty()) {
                        // Log.v(TAG, "putting mDevices");
                        bundle.putParcelableArrayList("config", (ArrayList<? extends Parcelable>) mDevices);
                        Message msg = Message.obtain(null, MSG_SET_BUNDLE);
                        msg.setData(bundle);
                        messenger.send(msg);
                    }
                    break;
                }
            } catch (RemoteException e) {
                Log.w(TAG, "mClients.remove called");
                mClients.remove(messenger);
            }
        }
    }

    public class WriteMonitor extends Thread {
        @Override
        public void run() {
            Log.v(TAG, "WriteMonitor started");
            try {
                while (!Thread.interrupted() && Server.CONNECTION.isWriting()) {
                    Thread.sleep(3000);
                }
                Log.v(TAG, "WriteMonitor finished, stopping");
                if (mClients.isEmpty())
                    stopSelf();
            } catch (InterruptedException e) {
            }
        }
    }

    /**
     * Handle incoming messages from MainActivity
     */
    @SuppressLint("HandlerLeak")
    private class IncomingMessageHandler extends Handler { // Handler of incoming messages from clients.
        @Override
        public void handleMessage(Message msg) {
            // Log.v(TAG, "handleMessage: " + msg.what);
            switch (msg.what) {
            case MSG_REGISTER_CLIENT:
                mClients.clear(); // Bit hackery, I admit
                mClients.add(msg.replyTo);
                sendMessageToUI(MSG_SET_STATUS, mCurrentNotif.name());
                if (!mDevices.isEmpty())
                    sendMessageToUI(MSG_SET_BUNDLE, null);
                break;
            case MSG_UNREGISTER_CLIENT:
                mClients.remove(msg.replyTo);
                break;
            case MSG_SWITCH_DEVICE:
                Server.CONNECTION.sentCommand(
                        "{\"message\":\"send\",\"code\":{" + msg.getData().getString("command") + "}}");
                break;
            default:
                super.handleMessage(msg);
            }
        }
    }

    public class HeartBeat extends Thread {
        long timeHeart;
        long runs = 0;

        @Override
        public void interrupt() {
            // Log.v(TAG, "interrupted HEARTBEAT");
            super.interrupt();
        }

        @Override
        public void run() {
            try {
                while (isConnectionUp) {
                    timeHeart = new Date().getTime();
                    if (timeHeart - timeBeat > 5000) {
                        Server.CONNECTION.sentCommand("HEART");
                        Thread.sleep(1500);
                    }
                    if (timeHeart - timeBeat > 7000) {
                        postUpdate("LOST_CONNECTION");
                        isConnectionUp = false;
                    }
                    Thread.sleep(30000);
                }
                Log.v(TAG, "heart-beat thread comes to an end");
            } catch (Exception e) {
                Log.w(TAG, "something wrong in the heart-beat");
            }
        }
    }
}