com.nbplus.push.PushService.java Source code

Java tutorial

Introduction

Here is the source code for com.nbplus.push.PushService.java

Source

/*
 * Copyright (c) 2015. NB Plus (www.nbplus.co.kr)
 *
 * 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.nbplus.push;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.net.ConnectivityManager;
import android.net.wifi.WifiManager;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.provider.Settings;
import android.support.v4.app.NotificationCompat;
import android.support.v7.app.AlertDialog;
import android.util.Log;

import com.android.volley.DefaultRetryPolicy;
import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.HurlStack;
import com.android.volley.toolbox.RequestFuture;
import com.android.volley.toolbox.Volley;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.annotations.SerializedName;
import com.google.gson.reflect.TypeToken;
import com.nbplus.push.data.PushConstants;
import com.nbplus.push.data.PushInterfaceData;

import org.basdroid.common.DeviceUtils;
import org.basdroid.common.NetworkUtils;
import org.basdroid.common.StringUtils;
import org.basdroid.volley.GsonRequest;

import java.io.IOException;
import java.lang.ref.WeakReference;
import java.net.HttpURLConnection;
import java.net.URL;

public class PushService extends Service {
    private static final String TAG = PushService.class.getName();

    // minutes
    public static final int MILLISECONDS = 1000;
    public static final int mNextRetryPeriodTerm = 5;

    // Wifi lock that we hold when streaming files from the internet, in order to prevent the
    // device from shutting off the Wifi radio
    WifiManager.WifiLock mWifiLock;

    // The ID we use for the notification (the onscreen alert that appears at the notification
    // area at the top of the screen as an icon -- and as text as well if the user expands the
    // notification area).
    final int NOTIFICATION_ID = 1;
    NotificationManager mNotificationManager;
    Notification mNotification = null;
    NotificationCompat.Builder mBuilder;
    boolean mLastConnectionStatus = false;
    SharedPreferences mPrefs;

    Thread mPushThread;
    PushRunnable mPushRunnable;

    String mPushInterfaceServerAddress = null;
    final String PUSH_IF_CONTEXT = "/is/api/appRequest/SessionRequest";

    /**
     * start : push interface variables
     */
    RequestFuture<PushInterfaceData> mGwRequestFuture = null;
    String mRequestBody;
    RequestQueue mRequestQueue;
    GetPushInterfaceTask mIfTask = null;

    class GetPushInterfaceRequestBody {
        @SerializedName("DEVICE_ID")
        public String deviceId;
        @SerializedName("DEVICE_TYPE")
        public String deviceType;
        @SerializedName("VERSION")
        public String pushVersion;
        @SerializedName("MAKER")
        public String vendor;
        @SerializedName("MODEL")
        public String model;
        @SerializedName("OS")
        public String os;
    }
    // end

    Context mContext = null;

    private PushServiceHandler mHandler = new PushServiceHandler(this);

    //  ? 
    private static class PushServiceHandler extends Handler {
        private final WeakReference<PushService> mService;

        public PushServiceHandler(PushService service) {
            mService = new WeakReference<>(service);
        }

        @Override
        public void handleMessage(Message msg) {
            PushService service = mService.get();
            if (service != null) {
                service.handleMessage(msg);
            }
        }
    }

    public void handleMessage(Message msg) {
        if (msg == null) {
            return;
        }
        switch (msg.what) {
        case PushConstants.HANDLER_MESSAGE_RETRY_MESSAGE:
            Log.d(TAG, "HANDLER_MESSAGE_RETRY_MESSAGE received !!!");
            if (mPushRunnable.getState() == PushRunnable.State.Connected) {
                Log.d(TAG, ">> Already reconnected. ignore retry message !!!");
                return;
            }
            mHandler.removeMessages(PushConstants.HANDLER_MESSAGE_RETRY_MESSAGE);
            if (NetworkUtils.isConnected(this)) {
                getPushGatewayInformationFromServer();
            } else {
                Log.d(TAG, "NetworkUtils.isConnected(this) == false.. Retry when re-connected wifi...");
            }
            break;
        case PushConstants.HANDLER_MESSAGE_GET_PUSH_GATEWAY_DATA:
            Log.d(TAG, "HANDLER_MESSAGE_GET_PUSH_GATEWAY_DATA received !!!");
            if (mIfTask != null) {
                mIfTask.cancel(true);
            }
            mIfTask = null;

            PushInterfaceData data = (PushInterfaceData) msg.obj;
            Log.d(TAG, "result = " + ((data != null) ? data.resultCode : null) + ", data = " + data);
            if (data != null && PushConstants.RESULT_OK.equals(data.resultCode)) {
                mPushRunnable.startPushClientSocket(data);
            } else {
                if (mPushRunnable.getState() == PushRunnable.State.Connected) {
                    Log.d(TAG, "Close previous connection !!!. and retry.");
                    mPushRunnable.releasePushClientSocket(NetworkUtils.isConnected(this));
                } else {
                    Log.d(TAG, "retry HANDLER_MESSAGE_GET_PUSH_GATEWAY_DATA!!!. after 1 min..");
                    mHandler.removeMessages(PushConstants.HANDLER_MESSAGE_RETRY_MESSAGE);
                    mHandler.sendEmptyMessageDelayed(PushConstants.HANDLER_MESSAGE_RETRY_MESSAGE,
                            PushService.MILLISECONDS * PushService.mNextRetryPeriodTerm);
                }
            }
            break;
        case PushConstants.HANDLER_MESSAGE_CONNECTIVITY_CHANGED:
            final boolean isConnected = NetworkUtils.isConnected(this);
            Log.d(TAG, "CONNECTIVITY_CHANGED received isConnected = " + isConnected
                    + ", mPushRunnable.getState() = " + mPushRunnable.getState());
            if (mLastConnectionStatus == isConnected) {
                if (isConnected && mPushRunnable.getState() == PushRunnable.State.Stopped
                        && !StringUtils.isEmptyString(mPushInterfaceServerAddress)) {
                    Log.d(TAG,
                            "mLastConnectionStatus == isConnected.. and network is connected. But push service is not connected. re=connect");
                    mHandler.removeMessages(PushConstants.HANDLER_MESSAGE_RETRY_MESSAGE);
                    getPushGatewayInformationFromServer();
                } else {
                    Log.d(TAG, "mLastConnectionStatus == isConnected do not anything.");
                }
                return;
            }

            mLastConnectionStatus = isConnected;
            if (mLastConnectionStatus) {
                Log.d(TAG, "HANDLER_MESSAGE_CONNECTIVITY_CHANGED network is connected !!!");
                if (!StringUtils.isEmptyString(mPushInterfaceServerAddress)) {
                    getPushGatewayInformationFromServer();
                }
            } else {
                Log.d(TAG, "HANDLER_MESSAGE_CONNECTIVITY_CHANGED network is disconnected !!!");
                if (mPushRunnable.getState() != PushRunnable.State.Stopped) {
                    mPushRunnable.releasePushClientSocket(false);
                }
                mHandler.removeMessages(PushConstants.HANDLER_MESSAGE_RETRY_MESSAGE);
            }
            break;
        }
    }

    private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {

        @Override
        public void onReceive(Context context, Intent intent) {
            final String action = intent.getAction();
            if (ConnectivityManager.CONNECTIVITY_ACTION.equals(action)) {
                mHandler.sendEmptyMessage(PushConstants.HANDLER_MESSAGE_CONNECTIVITY_CHANGED);
            }
        }

    };

    /**
     * Called by the system every time a client explicitly starts the service by calling
     * {@link android.content.Context#startService}, providing the arguments it supplied and a
     * unique integer token representing the start request.  Do not call this method directly.
     * <p/>
     * <p>For backwards compatibility, the default implementation calls
     * {@link #onStart} and returns either {@link #START_STICKY}
     * or {@link #START_STICKY_COMPATIBILITY}.
     * <p/>
     * <p>If you need your application to run on platform versions prior to API
     * level 5, you can use the following model to handle the older {@link #onStart}
     * callback in that case.  The <code>handleCommand</code> method is implemented by
     * you as appropriate:
     * <p/>
     * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/ForegroundService.java
     * start_compatibility}
     * <p/>
     * <p class="caution">Note that the system calls this on your
     * service's main thread.  A service's main thread is the same
     * thread where UI operations take place for Activities running in the
     * same process.  You should always avoid stalling the main
     * thread's event loop.  When doing long-running operations,
     * network calls, or heavy disk I/O, you should kick off a new
     * thread, or use {@link android.os.AsyncTask}.</p>
     *
     * @param intent  The Intent supplied to {@link android.content.Context#startService},
     *                as given.  This may be null if the service is being restarted after
     *                its process has gone away, and it had previously returned anything
     *                except {@link #START_STICKY_COMPATIBILITY}.
     * @param flags   Additional data about this start request.  Currently either
     *                0, {@link #START_FLAG_REDELIVERY}, or {@link #START_FLAG_RETRY}.
     * @param startId A unique integer representing this specific request to
     *                start.  Use with {@link #stopSelfResult(int)}.
     * @return The return value indicates what semantics the system should
     * use for the service's current started state.  It may be one of the
     * constants associated with the {@link #START_CONTINUATION_MASK} bits.
     * @see #stopSelfResult(int)
     */
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        String pushInterfaceServerAddress = null;
        String action = (intent != null) ? intent.getAction() : null;
        Log.d(TAG, "onStartCommand in service.. action = " + action);
        if (action == null) {
            action = PushConstants.ACTION_START_SERVICE;
            pushInterfaceServerAddress = mPrefs.getString(PushConstants.PREF_KEY_PUSH_IF_ADDRESS, "");
        }
        if (action.equals(PushConstants.ACTION_START_SERVICE)) {
            if (intent != null && action != null) {
                pushInterfaceServerAddress = intent.getStringExtra(PushConstants.EXTRA_START_SERVICE_IFADDRESS);
            }
            if (!StringUtils.isEmptyString(pushInterfaceServerAddress)) {
                if (mPushInterfaceServerAddress == null
                        || !pushInterfaceServerAddress.equals(mPushInterfaceServerAddress)) {
                    mPushInterfaceServerAddress = pushInterfaceServerAddress;
                    mPrefs.edit().putString(PushConstants.PREF_KEY_PUSH_IF_ADDRESS, mPushInterfaceServerAddress)
                            .apply();

                    if (mPushRunnable.getState() == PushRunnable.State.IfRetrieving) {
                        if (mIfTask != null) {
                            mIfTask.cancel(true);
                        }
                        mIfTask = null;
                    }

                    if (mPushRunnable.getState() == PushRunnable.State.Connected) {
                        mPushRunnable.releasePushClientSocket(false);
                    }
                    Log.d(TAG,
                            "mPushInterfaceServerAddress is (re)setted!!! getPushGatewayInformationFromServer()");
                    getPushGatewayInformationFromServer();
                } else {
                    if (pushInterfaceServerAddress.equals(mPushInterfaceServerAddress)) {
                        Log.d(TAG, ">> Same address... do not anything...");
                        return Service.START_STICKY;
                    }
                }
            } else {
                Log.e(TAG, ">> mPushInterfaceServerAddress is empty... maybe logout???? !!!");
                mPushRunnable.releasePushClientSocket(false);
            }
        } else if (action.equals(PushConstants.ACTION_GET_STATUS)) {
            mPushRunnable.sendSatusChangedBroadcastMessage(PushConstants.PUSH_STATUS_WHAT_NORMAL);
        }

        /**
         * ? ??? .
         */
        return Service.START_STICKY;//super.onStartCommand(intent, flags, startId);
    }

    /**
     * 1  ????... ? .
     * @param url
     */
    private void initPushGatewayTaskSettings(final String url) {
        if (mRequestBody == null) {
            String prefName = mContext.getApplicationContext().getPackageName() + "_preferences";
            SharedPreferences prefs = mContext.getSharedPreferences(prefName, Context.MODE_PRIVATE);

            // load from preferences..
            String deviceId = prefs.getString(PushConstants.KEY_DEVICE_ID, "");
            if (StringUtils.isEmptyString(deviceId)) {
                deviceId = DeviceUtils.getDeviceIdByMacAddress(mContext);
                prefs.edit().putString(PushConstants.KEY_DEVICE_ID, deviceId).apply();
            }

            GetPushInterfaceRequestBody reqBodyObj = new GetPushInterfaceRequestBody();
            reqBodyObj.deviceId = deviceId;
            reqBodyObj.os = Build.VERSION.RELEASE;
            reqBodyObj.pushVersion = Integer.toString(BuildConfig.VERSION_CODE);
            reqBodyObj.vendor = Build.MANUFACTURER;
            reqBodyObj.model = DeviceUtils.getDeviceName();
            reqBodyObj.os = Build.ID + " " + Build.VERSION.RELEASE;
            reqBodyObj.deviceType = "android";

            Gson gson = new GsonBuilder().create();
            mRequestBody = gson.toJson(reqBodyObj, new TypeToken<GetPushInterfaceRequestBody>() {
            }.getType());
        }

        if (mRequestQueue == null) {
            mRequestQueue = Volley.newRequestQueue(mContext, new HurlStack() {
                @Override
                protected HttpURLConnection createConnection(URL url) throws IOException {
                    HttpURLConnection connection = super.createConnection(url);
                    // Fix for bug in Android runtime(!!!):
                    // https://code.google.com/p/android/issues/detail?id=24672
                    connection.setRequestProperty("Accept-Encoding", "");

                    return connection;
                }
            });

            mGwRequestFuture = RequestFuture.newFuture();
            GsonRequest request = new GsonRequest(Request.Method.POST, url, mRequestBody, PushInterfaceData.class,
                    mGwRequestFuture, mGwRequestFuture);
            request.setRetryPolicy(new DefaultRetryPolicy(20 * 1000, 3, 1.0f));
            mRequestQueue.add(request);
        }
    }

    /**
     * volley connection ENETUNREACH  ? ??..
     * async  ?.
     * @param url
     */
    private static int mApiRetryCount = 0;

    private void getFromServer(final String url) {
        if (mRequestBody == null) {
            String prefName = mContext.getApplicationContext().getPackageName() + "_preferences";
            SharedPreferences prefs = mContext.getSharedPreferences(prefName, Context.MODE_PRIVATE);

            // load from preferences..
            String deviceId = prefs.getString(PushConstants.KEY_DEVICE_ID, "");
            if (StringUtils.isEmptyString(deviceId)) {
                deviceId = DeviceUtils.getDeviceIdByMacAddress(mContext);
                prefs.edit().putString(PushConstants.KEY_DEVICE_ID, deviceId).apply();
            }

            GetPushInterfaceRequestBody reqBodyObj = new GetPushInterfaceRequestBody();
            reqBodyObj.deviceId = deviceId;
            reqBodyObj.os = Build.VERSION.RELEASE;
            reqBodyObj.pushVersion = Integer.toString(BuildConfig.VERSION_CODE);
            reqBodyObj.vendor = Build.MANUFACTURER;
            reqBodyObj.model = DeviceUtils.getDeviceName();
            reqBodyObj.os = Build.ID + " " + Build.VERSION.RELEASE;
            reqBodyObj.deviceType = "android";

            Gson gson = new GsonBuilder().create();
            mRequestBody = gson.toJson(reqBodyObj, new TypeToken<GetPushInterfaceRequestBody>() {
            }.getType());
        }

        //        RequestQueue requestQueue = Volley.newRequestQueue(mContext, new HurlStack() {
        //            @Override
        //            protected HttpURLConnection createConnection(URL url) throws IOException {
        //                HttpURLConnection connection = super.createConnection(url);
        //                // Fix for bug in Android runtime(!!!):
        //                // https://code.google.com/p/android/issues/detail?id=24672
        //                connection.setRequestProperty("Accept-Encoding", "");
        //
        //                return connection;
        //            }
        //        });
        final GsonRequest gsonRequest = new GsonRequest(Request.Method.POST, url, mRequestBody,
                PushInterfaceData.class, new Response.Listener<PushInterfaceData>() {

                    @Override
                    public void onResponse(PushInterfaceData response) {
                        Log.d(TAG, ">>> get PushInterfaceData success !!!");
                        mApiRetryCount = 0;

                        Message message = new Message();
                        message.what = PushConstants.HANDLER_MESSAGE_GET_PUSH_GATEWAY_DATA;
                        message.obj = response;
                        mHandler.sendMessage(message);
                    }
                }, new Response.ErrorListener() {

                    @Override
                    public void onErrorResponse(VolleyError error) {
                        /**
                         * 2015.10.05
                         *  1? ? ?? .
                         */
                        Log.d(TAG, ">>> get PushInterfaceData error... retry connection after 1 minutes...");
                        if (mPushRunnable.getState() != PushRunnable.State.Stopped) {
                            mPushRunnable.releasePushClientSocket(false);
                        }
                        mHandler.removeMessages(PushConstants.HANDLER_MESSAGE_RETRY_MESSAGE);
                        mHandler.sendEmptyMessageDelayed(PushConstants.HANDLER_MESSAGE_RETRY_MESSAGE,
                                PushService.MILLISECONDS * PushService.mNextRetryPeriodTerm);
                    }
                });

        if (mRequestQueue == null) {
            mRequestQueue = Volley.newRequestQueue(mContext, new HurlStack() {
                @Override
                protected HttpURLConnection createConnection(URL url) throws IOException {
                    HttpURLConnection connection = super.createConnection(url);
                    // Fix for bug in Android runtime(!!!):
                    // https://code.google.com/p/android/issues/detail?id=24672
                    connection.setRequestProperty("Accept-Encoding", "");

                    return connection;
                }
            });
        }
        gsonRequest.setRetryPolicy(new DefaultRetryPolicy(20 * 1000, 3, 1.0f));
        mRequestQueue.add(gsonRequest);
    }

    private void getPushGatewayInformationFromServer() {
        mPushRunnable.releasePushClientSocket(false);
        mHandler.removeMessages(PushConstants.HANDLER_MESSAGE_RETRY_MESSAGE);

        mPushRunnable.setState(PushRunnable.State.IfRetrieving);
        //initPushGatewayTaskSettings(mPushInterfaceServerAddress + PUSH_IF_CONTEXT);
        getFromServer(mPushInterfaceServerAddress + PUSH_IF_CONTEXT);
        //mIfTask = new GetPushInterfaceTask(this, mHandler, mGwRequestFuture);
        //mIfTask.execute();
    }

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

    /**
     * Configures service as a foreground service. A foreground service is a service that's doing
     * something the user is actively aware of (such as playing music), and must appear to the
     * user as a notification. That's why we create the notification here.
     */
    void setUpAsForeground() {
        // notification's layout
        mBuilder = new NotificationCompat.Builder(this);

        CharSequence ticker = getString(R.string.push_noti_body);
        int apiVersion = Build.VERSION.SDK_INT;

        mBuilder.setSmallIcon(R.drawable.ic_notification_push).setAutoCancel(false).setOngoing(true)
                .setContentTitle(getString(R.string.push_name)).setContentText(ticker)
                .setPriority(NotificationCompat.PRIORITY_MAX).setTicker(ticker);

        mNotification = mBuilder.build();
        mNotification.flags |= Notification.FLAG_ONGOING_EVENT;
        mNotification.defaults |= Notification.DEFAULT_LIGHTS;
        startForeground(NOTIFICATION_ID, mNotification);
    }

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

        String prefName = getApplicationContext().getPackageName() + "_preferences";
        mPrefs = getSharedPreferences(prefName, Context.MODE_PRIVATE);

        // check network status
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
        registerReceiver(mBroadcastReceiver, intentFilter);

        mLastConnectionStatus = NetworkUtils.isConnected(this);

        /**
         * ?  ...
         * 2015.07.28
         */
        //setUpAsForeground();
        mPushRunnable = new PushRunnable(this, mHandler, null);

        if (mPushThread != null) {
            mPushThread.interrupt();
        }

        mPushThread = new Thread(mPushRunnable);
        mPushThread.start();

        mContext = this;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "PushService onDestroy()......");
        unregisterReceiver(mBroadcastReceiver);
        /**
         * ?  ...
         * 2015.07.28
         */
        //stopForeground(true);
        if (mPushRunnable != null) {
            mPushRunnable.releasePushClientSocket(false);
        }
        if (mPushThread != null) {
            mPushThread.interrupt();
        }
        mHandler = null;
    }
}